unit _frmSmartWindows;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Vcl.ExtCtrls, uRwSha1, uRwClasses,
  Vcl.Menus, _WindowList, _WindowLastSeen, Vcl.AppEvnts, _SingleInstance, Commctrl, _DesktopIconList,
  System.Types, System.UITypes, cxClasses, cxLookAndFeels, dxSkinsForm,
  cxGraphics, cxControls, cxLookAndFeelPainters, cxContainer, cxEdit, cxCheckBox,
  cxButtons, dxAlertWindow, delightWhiteSmartWindows, siComp, dxBar, Registry,
  cxTextEdit, cxMaskEdit, cxDropDownEdit, _JSONConfig;

const
  WM_AFTER_SHOW = WM_USER + 300;

type
  TAction = (aRestore, aSave);

  TNotificationType = (ntResolutionChanged, ntWindowsRestored, ntDesktopIconsRestored, ntSnapshotCreated, ntSnapshotRestored, ntSnapshotRemoved, ntEnvironmentSettingsRestored);

  THashedStringInfo = record
    HashedString: string;
    Icon: HICON;
    ClassName: string;
    WindowCaption: string;
    IsClassNameIgnored: Boolean;
    GenClassNameWithCaption: Boolean;
  end;

  TUserSettings = record
    Value: Boolean;
    UserControlled: Boolean;
    PolicyControlled: Boolean;
  end;

type
   TString = class(TObject)
   private
    FStr: string;
    procedure SetStr(const Value: string);
   public
     constructor Create(const AStr: String);
     property Str: string read FStr write SetStr;
   end;

type
  TfrmSmartWindows = class(TForm)
    tmCheckForegroundWindow: TTimer;
    tmSaveInterval: TTimer;
    TrayIcon: TTrayIcon;
    ApplicationEvents: TApplicationEvents;
    PopupMenu: TPopupMenu;
    mnuRestore: TMenuItem;
    N1: TMenuItem;
    mnuClose: TMenuItem;
    Panel1: TPanel;
    Label1: TLabel;
    Image1: TImage;
    dxSkinController: TdxSkinController;
    chkStartWithWindows: TcxCheckBox;
    AlertWindows: TdxAlertWindowManager;
    siLang: TsiLang;
    siLangDispatcher: TsiLangDispatcher;
    dxBarManager: TdxBarManager;
    dxBarManagerBar1: TdxBar;
    dxBarSubItem1: TdxBarSubItem;
    mnubtnMinimize: TdxBarButton;
    mnubtnClose: TdxBarButton;
    dxBarSubItem2: TdxBarSubItem;
    mnubtnOnlineUpdate: TdxBarButton;
    mnubtnOnlineRegister: TdxBarButton;
    mnubtnAbout: TdxBarButton;
    mnubtnAdvanced: TdxBarButton;
    mnubtnHelp: TdxBarButton;
    chkMinimized: TcxCheckBox;
    chkShowNotifications: TcxCheckBox;
    chkCheckForUpdates: TcxCheckBox;
    chkShowTray: TcxCheckBox;
    dxBarSubItem3: TdxBarSubItem;
    mnubtnEnglish: TdxBarButton;
    mnubtnGerman: TdxBarButton;
    lblSysAdmin: TLabel;
    mnubtnSupportForum: TdxBarButton;
    Label2: TLabel;
    cbSnapshots: TcxComboBox;
    btnCreateSnapshot: TcxButton;
    btnRestoreSnapshot: TcxButton;
    Snapshots1: TMenuItem;
    Create1: TMenuItem;
    N2: TMenuItem;
    btnRemoveSnapshot: TcxButton;
    pmnuRestore: TMenuItem;
    chkRestoreMinimizedOnlyWhenOpen: TcxCheckBox;
    chkSaveRestoreDesktopWallpaper: TcxCheckBox;
    lblDebugBuild: TLabel;
    chkInactivitySnapshots: TcxCheckBox;
    pmnuRestoreInactivitySnapshot: TMenuItem;
    tmIdleCheck: TTimer;
    procedure tmCheckForegroundWindowTimer(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure tmSaveIntervalTimer(Sender: TObject);
    procedure ApplicationEventsMinimize(Sender: TObject);
    procedure mnuCloseClick(Sender: TObject);
    procedure mnuRestoreClick(Sender: TObject);
    procedure Image1DblClick(Sender: TObject);
    procedure TrayIconClick(Sender: TObject);
    procedure FormCloseQuery(Sender: TObject; var CanClose: Boolean);
    procedure FormShow(Sender: TObject);
    procedure chkStartWithWindowsClick(Sender: TObject);
    procedure AlertWindowsClick(Sender: TObject; AAlertWindow: TdxAlertWindow);
    procedure mnubtnAboutClick(Sender: TObject);
    procedure mnubtnCloseClick(Sender: TObject);
    procedure mnubtnMinimizeClick(Sender: TObject);
    procedure mnubtnAdvancedClick(Sender: TObject);
    procedure mnubtnEnglishClick(Sender: TObject);
    procedure mnubtnGermanClick(Sender: TObject);
    procedure chkMinimizedClick(Sender: TObject);
    procedure chkShowTrayClick(Sender: TObject);
    procedure chkShowNotificationsClick(Sender: TObject);
    procedure chkCheckForUpdatesClick(Sender: TObject);
    procedure mnubtnSupportForumClick(Sender: TObject);
    procedure mnubtnHelpClick(Sender: TObject);
    procedure mnubtnOnlineUpdateClick(Sender: TObject);
    procedure btnCreateSnapshotClick(Sender: TObject);
    procedure btnRestoreSnapshotClick(Sender: TObject);
    procedure btnRemoveSnapshotClick(Sender: TObject);
    procedure pbtnRestoreSnapshotClick(Sender: TObject);
    procedure cbSnapshotsPropertiesChange(Sender: TObject);
    procedure chkRestoreMinimizedOnlyWhenOpenClick(Sender: TObject);
    procedure ApplicationEventsException(Sender: TObject; E: Exception);
    procedure chkSaveRestoreDesktopWallpaperClick(Sender: TObject);
    procedure lblDebugBuildClick(Sender: TObject);
    procedure chkInactivitySnapshotsClick(Sender: TObject);
    procedure pmnuRestoreInactivitySnapshotClick(Sender: TObject);
    procedure tmIdleCheckTimer(Sender: TObject);
    procedure siLangChangeLanguage(Sender: TObject);
  private
    FLastScannedForegroundWindow: Hwnd;
    FForegroundWindow: Hwnd;
    FSingleInstance: TSingleInstance;
    FWindowsWantShutdown: Boolean;
    FStartWithWindows: Boolean;
    FRegPolicy: TRegistry;
    FIsInEnumWindowProgress: Boolean;
    FRestoreSnapshowNow: Boolean;
    FDisplayChangedNow: Boolean;
    FSystemIsIdle: Boolean;
    procedure WMDisplayChange(var Msg: TWMDisplayChange); message WM_DISPLAYCHANGE;
    procedure WMPowerBroadcast(var Msg: TMessage); message WM_POWERBROADCAST;
    procedure WMQueryEndSession(var Msg: TWMQueryEndSession); message WM_QUERYENDSESSION;
    procedure WMSettingChange(var Msg: TMessage); message WM_SETTINGCHANGE;
    procedure WMWTSSessionChange(var Msg: TMessage); message WM_WTSSESSION_CHANGE;
    procedure LoadWindowListFromFile;
    procedure LoadDesktopIconListFromFile;
    procedure SaveDesktopIconListToFile;
    procedure LoadLastSeenFromFile;
    procedure SaveLastSeenToFile;
    function GetPCName: string;
    function ExpandEnvVars(const AStr: string): string;
    procedure SaveRestoreDesktopIcons(const AAction: TAction);
    function GetDesktopListViewHandle: HWND;
    procedure SetStartWithWindows(const Value: Boolean);
    procedure InitEnum;
    procedure FinalizeEnum;
    function RemoveConfigPrefix(const AStr: string): string;
    procedure UpdateGUIElements;
    function GetSettingStartMinimized: TUserSettings;
    function GetSettingStartWithWindows: TUserSettings;
    function GetSettingShowTrayIcon: TUserSettings;
    function GetSettingShowNotifications: TUserSettings;
    function GetSettingCheckForUpdates: TUserSettings;
    function GetSettingUpdatesEnabled: TUserSettings;
    function GetSettingShowFirstStart: TUserSettings;
    function GetRestoreMinimizedOnlyWhenOpen: TUserSettings;
    function GetSaveRestoreDesktopWallpaper: TUserSettings;
    procedure WmAfterShow(var Msg: TMessage); message WM_AFTER_SHOW;
    procedure RefreshSnapshotList;
    procedure CreateSnapshot(const AName: string; const IsInactivitySnapshot: Boolean);
    procedure RestoreSnapshot(const ASnapshotID: string; const IsInactivitySnapshot: Boolean);
    procedure SaveEnvironmentConfig;
    procedure RestoreEnvironmentConfig;
    procedure LoadEnvironmentConfig;
    function GetSettingInactivitySnapshotsEnabled: TUserSettings;
  public
    WindowList: TWindowList;
    WindowListConfig: TJSONConfig;
    WindowLastSeen: TWindowLastSeen;
    WindowLastSeenConfig: TJSONConfig;
    DesktopIconListConfig: TJSONConfig;
    SnapshotListConfig: TJSONConfig;
    InactivitySnapshotListConfig: TJSONConfig;
    DesktopIconList: TDesktopIconList;
    AppConfig: TJSONConfig;
    AppUserConfig: TJSONConfig;
    EnvironmentConfig: TJSONConfig;
    IgnoreListHashedClassNames: TStringList;
    GenClassNameWithCaptionList: TStringList;
    OpenWindowsHashedList: TStringList;
    LastKnownResolution: string;
    ConfigPath: string;
    SnapshotPath: string;
    InactivitySnapshotPath: string;
    procedure SaveWindowListToFile;
    procedure SaveAppUserConfig;
    function AddConfigPrefix(const AStr: string): string;
    function GetHashedStringFromWindow(const AHWND: HWND; const AWantDebugLog: Boolean = True): THashedStringInfo;
    function GetCurrentResolution: string;
    function GetIsSaveableWindow(const AHWND: HWND): Boolean;
    function IsModalWindow(const AHWND: Hwnd): Boolean;
    procedure CheckRestoringWindows(const AHashedClassName: string);
    procedure UpdateWindowPos(const AHashedClassName: string);
    procedure SetLanguage(const ALangName: string);
    function IsHashedClassNameIgnored(const AHashedClassName: string): Boolean;
    function IsGenClassNameWithCaption(const AClassName: string): Boolean;
    function IsWindowSizeable(const AHWND: HWND): Boolean;
    procedure ShowNotification(const ANotificationType: TNotificationType; const AWindowTitle: string = ''; const AOldRes: string = ''; const ANewRes: string = '');

    property SettingStartMinimized: TUserSettings read GetSettingStartMinimized;
    property SettingStartWithWindows: TUserSettings read GetSettingStartWithWindows;
    property SettingShowTrayIcon: TUserSettings read GetSettingShowTrayIcon;
    property SettingShowNotifications: TUserSettings read GetSettingShowNotifications;
    property SettingCheckForUpdates: TUserSettings read GetSettingCheckForUpdates;
    property SettingUpdatesEnabled: TUserSettings read GetSettingUpdatesEnabled;
    property SettingShowFirstStart: TUserSettings read GetSettingShowFirstStart;
    property SettingRestoreMinimizedOnlyWhenOpen: TUserSettings read GetRestoreMinimizedOnlyWhenOpen;
    property SettingSaveRestoreDesktopWallpaper: TUserSettings read GetSaveRestoreDesktopWallpaper;
    property SettingInactivitySnapshotsEnabled: TUserSettings read GetSettingInactivitySnapshotsEnabled;
  end;

  function EnumWindowsProc(AHWND: HWND): Bool; stdcall; export;


var
  frmSmartWindows: TfrmSmartWindows;

implementation

{$R *.dfm}

uses _frmDebug, _frmAdvanced, _frmAbout, _frmFirstStart, Shellapi;

function EnumWindowsProc(AHWND: HWND): Bool;
var
  wli: TWindowListItem;
  wls: TWindowLastSeenItem;
  HashedClassName: string;
begin
  Result := True;

  //Run only if LastKnownResolution is the same. We set LastKnownResolution after Resolution change message
  //When setting there, we are sure we loaded the new config file
  if frmSmartWindows.LastKnownResolution = frmSmartWindows.GetCurrentResolution then
  begin
    if frmSmartWindows.GetIsSaveableWindow(AHWND) then
    begin
      HashedClassName := frmSmartWindows.GetHashedStringFromWindow(AHWND).HashedString;
      if frmSmartWindows.IsHashedClassNameIgnored(HashedClassName) then
      begin
        frmDebug.AddToDebugLog('===> Ignored hashed ClassName: ' + HashedClassName);
      end
      else
      begin
        wli := frmSmartWindows.WindowList.GetItemByHashedClassName(HashedClassName);
        if wli <> nil then
        begin
          if wli.Handle = 0 then
          begin
            wli.IsWindowNewOpen := True;
          end
          else
          begin
            wli.IsWindowNewOpen := False;
          end;

          wli.Handle := AHWND;

          frmSmartWindows.OpenWindowsHashedList.Add(HashedClassName);

          //Check whenever to restore windows
          frmSmartWindows.CheckRestoringWindows(wli.HashedClassName);
        end
        else
        begin
          wli := TWindowListItem.Create;
          wli.Handle := AHWND;
          wli.HashedClassName := HashedClassName;
          wli.IsWindowNewOpen := True;
          frmSmartWindows.WindowList.Add(wli);
        end;


        //update window list with current values
        frmSmartWindows.UpdateWindowPos(wli.HashedClassName);

        frmDebug.AddToDebugLog('Window Title: "' + wli.Title + '"');

        wls := frmSmartWindows.WindowLastSeen.GetItemByHashedClassName(HashedClassName);
        if wls = nil then
        begin
          wls := TWindowLastSeenItem.Create;
          wls.LastSeen := now;
          wls.HashedClassName := HashedClassName;
          wls.LastSeenResolution := frmSmartWindows.LastKnownResolution;
          frmSmartWindows.WindowLastSeen.Add(wls);
          wls.HasChanged := True;
        end
        else
        begin
          wls.LastSeen := now;
          wls.LastSeenResolution := frmSmartWindows.LastKnownResolution;
          wls.HasChanged := True;
        end;
      end;
    end;
  end;
end;

function TfrmSmartWindows.AddConfigPrefix(const AStr: string): string;
begin
  Result := 'Item_' + AStr;
end;

procedure TfrmSmartWindows.AlertWindowsClick(Sender: TObject;
  AAlertWindow: TdxAlertWindow);
begin
  mnuRestoreClick(Sender);
end;

procedure TfrmSmartWindows.ApplicationEventsException(Sender: TObject;
  E: Exception);
begin
  frmDebug.AddToDebugLog('');
  frmDebug.AddToDebugLog('::::::::::::: EXCEPTION :::::::::::::');
  frmDebug.AddToDebugLog(E.Message);
  frmDebug.AddToDebugLog(':::::::::::::::::!:::::::::::::::::::');
  frmDebug.AddToDebugLog('');
end;

procedure TfrmSmartWindows.ApplicationEventsMinimize(Sender: TObject);
begin
  Hide;
  WindowState := wsMinimized;
end;

procedure TfrmSmartWindows.btnCreateSnapshotClick(Sender: TObject);
var
  dmy: string;
  default: string;
begin
  dmy := 'Snapshot @ ' + FormatDateTime('c', now);
  if siLang.InputQuery(siLang.GetTextOrDefault('S_SnapshotName'), siLang.GetTextOrDefault('S_SnapshotEnterName'), dmy) then
  begin
    if dmy <> '' then
    begin
      CreateSnapshot(dmy, False);
      ShowNotification(ntSnapshotCreated, dmy);
      RefreshSnapshotList;
    end
    else
    begin
      siLang.MessageDlg(siLang.GetTextOrDefault('S_SnapshotEnterName2'), mtError, [mbOK], 0);
    end;
  end;
end;

procedure TfrmSmartWindows.btnRemoveSnapshotClick(Sender: TObject);
begin
  if siLang.MessageDlg(siLang.GetTextOrDefault('S_SnapshotReallyRemove'), mtConfirmation, [mbYes, mbNo], 0) = mrYes then
  begin
    SnapshotListConfig.DeleteSection(TString(cbSnapshots.ItemObject).Str);
    DeleteFile(SnapshotPath + TString(cbSnapshots.ItemObject).Str + '\DesktopIcons.json');
    DeleteFile(SnapshotPath + TString(cbSnapshots.ItemObject).Str + '\WindowList.json');
    DeleteFile(SnapshotPath + TString(cbSnapshots.ItemObject).Str + '\Environment.json');
    RemoveDir(SnapshotPath + TString(cbSnapshots.ItemObject).Str);

    SnapshotListConfig.Save;

    ShowNotification(ntSnapshotRemoved, cbSnapshots.Text);
    RefreshSnapshotList;
  end;
end;

procedure TfrmSmartWindows.btnRestoreSnapshotClick(Sender: TObject);
begin
  RestoreSnapshot(TString(cbSnapshots.ItemObject).Str, False);
  cbSnapshots.ItemIndex := -1;
  btnRestoreSnapshot.Enabled := False;
  btnRemoveSnapshot.Enabled := False;
end;

procedure TfrmSmartWindows.cbSnapshotsPropertiesChange(Sender: TObject);
begin
  btnRemoveSnapshot.Enabled := cbSnapshots.ItemIndex > -1;
  btnRestoreSnapshot.Enabled := cbSnapshots.ItemIndex > -1;
end;

procedure TfrmSmartWindows.CheckRestoringWindows(const AHashedClassName: string);

  function HasRestoredPosition(const AWinPlacement: TWindowPlacement; const AWindowListItem: TWindowListItem): Boolean;
  begin
    if IsWindowSizeable(AWindowListItem.Handle) then
    begin
      Result := (AWinPlacement.showCmd = AWindowListItem.State) and
                 (AWinPlacement.flags = AWindowListItem.RestoreFlags) and
                 (AWinPlacement.rcNormalPosition.Top = AWindowListItem.Top) and
                 (AWinPlacement.rcNormalPosition.Left = AWindowListItem.Left) and
                 (AWinPlacement.rcNormalPosition.Width = AWindowListItem.Width) and
                 (AWinPlacement.rcNormalPosition.Height = AWindowListItem.Height) and
                 (AWinPlacement.ptMinPosition.X = AWindowListItem.MinX) and
                 (AWinPlacement.ptMinPosition.Y = AWindowListItem.MinY) and
                 (AWinPlacement.ptMaxPosition.X = AWindowListItem.MaxX) and
                 (AWinPlacement.ptMaxPosition.Y = AWindowListItem.MaxY);
      frmDebug.AddToDebugLog('Window IS sizeable');
    end
    else
    begin
      Result := (AWinPlacement.rcNormalPosition.Top = AWindowListItem.Top) and
                 (AWinPlacement.rcNormalPosition.Left = AWindowListItem.Left);
      frmDebug.AddToDebugLog('Window IS NOT sizeable');
    end;
  end;

var
  wincheck: TWindowPlacement;
  win: TWindowPlacement;
  wli: TWindowListItem;
  wls: TWindowLastSeenItem;
  FNeedRestore: Boolean;
  Title: array[0..255] of char;
begin
  wls := WindowLastSeen.GetItemByHashedClassName(AHashedClassName);

  if wls <> nil then
  begin
    frmDebug.AddToDebugLog('Checking restore action: Last seen config found...');

    if (wls.LastSeenResolution <> LastKnownResolution) then
    begin
      frmDebug.AddToDebugLog('Last seen resolution is ' + wls.LastSeenResolution + ' :: Current Resolution is ' + LastKnownResolution);
      frmDebug.AddToDebugLog('Restoring window position');
      FNeedRestore := True;
    end
    else
    begin
      frmDebug.AddToDebugLog('Last seen config is the same as current resolution. No action.');
      FNeedRestore := False;
    end;

    wli := WindowList.GetItemByHashedClassName(AHashedClassName);
    if wli <> nil then //we have a config for this window
    begin
      if (wli.AlwaysRestore and wli.IsWindowNewOpen) or FNeedRestore then
      begin
        //Restore Window Position from config
        wincheck.length := SizeOf(TWindowPlacement);
        GetWindowPlacement(wli.Handle, wincheck);

        if not HasRestoredPosition(wincheck, wli) then
        begin
          if IsWindowSizeable(wli.Handle) then
          begin
            win.rcNormalPosition.Top := wli.Top;
            win.rcNormalPosition.Left := wli.Left;
            win.rcNormalPosition.Width := wli.Width;
            win.rcNormalPosition.Height := wli.Height;
            win.ptMinPosition.X := wli.MinX;
            win.ptMinPosition.Y := wli.MinY;
            win.ptMaxPosition.X := wli.MaxX;
            win.ptMaxPosition.Y := wli.MaxY;
          end
          else
          begin
            win.rcNormalPosition.Top := wli.Top;
            win.rcNormalPosition.Left := wli.Left;
            win.rcNormalPosition.Width := wincheck.rcNormalPosition.Width;
            win.rcNormalPosition.Height := wincheck.rcNormalPosition.Height;
            win.ptMinPosition.X := wincheck.ptMinPosition.X;
            win.ptMinPosition.Y := wincheck.ptMinPosition.Y;
            win.ptMaxPosition.X := wincheck.ptMaxPosition.X;
            win.ptMaxPosition.Y := wincheck.ptMaxPosition.Y;
          end;

          // Restore windows to original minimized state only if option is not checked and is a new window
          if (SettingRestoreMinimizedOnlyWhenOpen.Value and wli.IsWindowNewOpen) then
          begin
            // Manipulate State and restore flags only if not the display changed at runtime
            if not FDisplayChangedNow then
            begin
              //Check if last state was minimized - if yes restore to normal, because we don't want minimized restore here
              if wli.State = SW_SHOWMINIMIZED then
              begin
                if wli.RestoreFlags = WPF_RESTORETOMAXIMIZED then
                begin
                  wli.State := SW_SHOWMAXIMIZED;
                  wli.RestoreFlags := 0;
                end
                else
                begin
                  wli.State := SW_SHOWNORMAL;
                  wli.RestoreFlags := 0;
                end;
              end;
            end;
          end;

          win.showCmd := wli.State;
          win.flags := wli.RestoreFlags;

          win.length := SizeOf(TWindowPlacement);
          SetWindowPlacement(wli.Handle, win);

          GetWindowText(wli.Handle, Title, 255);

          if wli.AlwaysRestore then
          begin
            frmDebug.AddToDebugLog('Window configured to always restore: Hashed ClassName: "' + AHashedClassName + '" :: ClassName: "' + wli.ClassName + '" :: Title: "' + Title + '"');
          end
          else
          begin
            frmDebug.AddToDebugLog('Window position restored: Hashed ClassName: "' + AHashedClassName + '" :: ClassName: "' + wli.ClassName + '" :: Title: "' + Title + '"');
          end;
          frmDebug.AddToDebugLog('Restore:: Top: ' + IntToStr(wli.Top) + ' Left: ' + IntToStr(wli.Left) + ' Width: ' + IntToStr(wli.Width) + ' Height: ' + IntToStr(wli.Height) + ' MinX: ' + IntToStr(wli.MinX) + ' MinY: ' + IntToStr(wli.MinY) + ' MaxX: ' + IntToStr(wli.MaxX) + ' MaxY: ' + IntToStr(wli.MaxY) + ' State: ' + IntToStr(wli.State) + ' RestoreFlags: ' + IntToStr(wli.RestoreFlags));

          ShowNotification(ntWindowsRestored, Title);
        end;

        Sleep(100);


        wincheck.length := SizeOf(TWindowPlacement);
        GetWindowPlacement(wli.Handle, wincheck);
        if not HasRestoredPosition(wincheck, wli) then
        begin
          frmDebug.AddToDebugLog('Window position not yet correctly restored. Waiting one 200ms...');
          Sleep(200);
          SetWindowPlacement(wli.Handle, win);
          frmDebug.AddToDebugLog('Second try to restore window.');
        end;
      end;
    end;
  end
  else
  begin
    frmDebug.AddToDebugLog('Checking restore action: No last seen config found for window. No action.');
  end;
end;

procedure TfrmSmartWindows.chkCheckForUpdatesClick(Sender: TObject);
begin
  AppUserConfig.WriteBool('Main', 'CheckForUpdates', chkCheckForUpdates.Checked);
end;

procedure TfrmSmartWindows.chkInactivitySnapshotsClick(Sender: TObject);
begin
  AppUserConfig.WriteBool('Main', 'InactivitySnapshots', chkInactivitySnapshots.Checked);
  tmIdleCheck.Enabled := SettingInactivitySnapshotsEnabled.Value;
end;

procedure TfrmSmartWindows.chkMinimizedClick(Sender: TObject);
begin
  AppUserConfig.WriteBool('Main', 'MinimizeOnStart', chkMinimized.Checked);
end;

procedure TfrmSmartWindows.chkRestoreMinimizedOnlyWhenOpenClick(
  Sender: TObject);
begin
  AppUserConfig.WriteBool('Main', 'RestoreMinimizedOnlyWhenOpen', chkRestoreMinimizedOnlyWhenOpen.Checked);
end;

procedure TfrmSmartWindows.chkSaveRestoreDesktopWallpaperClick(Sender: TObject);
begin
  AppUserConfig.WriteBool('Main', 'SaveRestoreDesktopWallpaper', chkSaveRestoreDesktopWallpaper.Checked);
end;

procedure TfrmSmartWindows.chkShowNotificationsClick(Sender: TObject);
begin
  AppUserConfig.WriteBool('Main', 'ShowNotifications', chkShowNotifications.Checked);
end;

procedure TfrmSmartWindows.chkShowTrayClick(Sender: TObject);
begin
  AppUserConfig.WriteBool('Main', 'ShowTrayIcon', chkShowTray.Checked);
end;

procedure TfrmSmartWindows.chkStartWithWindowsClick(Sender: TObject);
begin
  SetStartWithWindows(chkStartWithWindows.Checked);
  AppUserConfig.WriteBool('Main', 'StartWithWindows', chkStartWithWindows.Checked);
end;

procedure TfrmSmartWindows.CreateSnapshot(const AName: string; const IsInactivitySnapshot: Boolean);
var
  item: string;
  i: Integer;
begin
  tmCheckForegroundWindow.Enabled := False;

  if not IsInactivitySnapshot then
  begin
    i := 0;
    while SnapshotListConfig.SectionExists('Snapshot_' + IntToStr(i)) do
    begin
      Inc(i, 1);
    end;

    item := 'Snapshot_' + IntToStr(i);

    SnapshotListConfig.WriteString(item, 'SnapshotName', AName);
    SnapshotListConfig.WriteString(item, 'Resolution', LastKnownResolution);
    SnapshotListConfig.WriteString(item, 'ComputerName', GetPCName);
    ForceDirectories(SnapshotPath + item);

    SaveRestoreDesktopIcons(aSave);
    SaveDesktopIconListToFile;
    SaveWindowListToFile;
    CopyFile(PChar(DesktopIconListConfig.FileName), PChar(SnapshotPath + item + '\DesktopIcons.json'), False);
    CopyFile(PChar(WindowListConfig.FileName), PChar(SnapshotPath + item + '\WindowList.json'), False);
    CopyFile(PChar(EnvironmentConfig.FileName), PChar(SnapshotPath + item + '\Environment.json'), False);

    SnapshotListConfig.Save;
  end
  else
  begin
    SaveRestoreDesktopIcons(aSave);
    SaveDesktopIconListToFile;
    SaveWindowListToFile;
    item := GetPCName + '_' + LastKnownResolution;
    ForceDirectories(InactivitySnapshotPath + item);
    CopyFile(PChar(DesktopIconListConfig.FileName), PChar(InactivitySnapshotPath + item + '\DesktopIcons.json'), False);
    CopyFile(PChar(WindowListConfig.FileName), PChar(InactivitySnapshotPath + item + '\WindowList.json'), False);
    CopyFile(PChar(EnvironmentConfig.FileName), PChar(InactivitySnapshotPath + item + '\Environment.json'), False);

    InactivitySnapshotListConfig.WriteDateTime(item, 'LastSnapshot', now);
    InactivitySnapshotListConfig.Save;

    RefreshSnapshotList;
  end;

  tmCheckForegroundWindow.Enabled := True;
end;

procedure TfrmSmartWindows.mnubtnAboutClick(Sender: TObject);
begin
  frmAbout.ShowModal;
end;

procedure TfrmSmartWindows.mnubtnAdvancedClick(Sender: TObject);
begin
  frmAdvanced.ShowModal;
end;

procedure TfrmSmartWindows.mnubtnCloseClick(Sender: TObject);
begin
  mnuCloseClick(Sender);
end;

procedure TfrmSmartWindows.mnubtnEnglishClick(Sender: TObject);
begin
  SetLanguage('English');
end;

procedure TfrmSmartWindows.mnubtnGermanClick(Sender: TObject);
begin
  SetLanguage('German');
end;

procedure TfrmSmartWindows.mnubtnHelpClick(Sender: TObject);
begin
  ShellExecute(handle, 'open', 'http://www.delight.ch', nil, nil, SW_SHOWNORMAL);
end;

procedure TfrmSmartWindows.mnubtnMinimizeClick(Sender: TObject);
begin
  Hide;
  WindowState := wsMinimized;
end;

procedure TfrmSmartWindows.mnubtnOnlineUpdateClick(Sender: TObject);
{var
  OU: TOnlineUpdate;    }
begin
{  OU := TOnlineUpdate.Create;
  try
    with OU do begin
      ApplicationRootPath := ExtractFilePath(ParamStr(0));
      ApplicationName := 'delight SmartWindows';
      UpdatePath := 'onlineupdate.delightsoftware.com';
      ApplicationExecutable := ParamStr(0);

      LicenseNumber := LicenseManager.LicenseNumber;
      CryptKey := LicenseManager.LicenseUserInfos.OUKey;

      WriteParamsToFile := false;

      DownloadSpeedLimit := 0;

      OnCloseMainApplication := DoOnCloseMainApplication;

      OU.ActiveLanguage := siLangDispatcher.language;

      OU.ShowDialog;
    end;
  finally
    OU.Free;
  end;     }
end;

procedure TfrmSmartWindows.mnubtnSupportForumClick(Sender: TObject);
begin
  ShellExecute(handle, 'open', 'http://forum.delight.ch/index.php?board/28-delight-smartwindows/', nil, nil, SW_SHOWNORMAL);
end;

procedure TfrmSmartWindows.mnuCloseClick(Sender: TObject);
begin
  Close;
end;

function TfrmSmartWindows.ExpandEnvVars(const AStr: string): string;
var
  BufSize: Integer; // size of expanded string
begin
  // Get required buffer size
  BufSize := ExpandEnvironmentStrings(
    PChar(AStr), nil, 0);
  if BufSize > 0 then
  begin
    // Read expanded string into result string
    SetLength(Result, BufSize - 1);
    ExpandEnvironmentStrings(PChar(AStr),
      PChar(Result), BufSize);
  end
  else
    // Trying to expand empty string
    Result := '';
end;

procedure TfrmSmartWindows.FinalizeEnum;
var
  i: Integer;
begin
  for i := 0 to WindowList.Count - 1 do
  begin
    if OpenWindowsHashedList.IndexOf(WindowList.Items[i].HashedClassName) = -1 then
    begin
      WindowList.Items[i].Handle := 0;
    end;
  end;

  FIsInEnumWindowProgress := False;
end;

procedure TfrmSmartWindows.FormCloseQuery(Sender: TObject;
  var CanClose: Boolean);
begin
  if FWindowsWantShutdown then
  begin
    CanClose := True;
  end
  else
  begin
    if siLang.MessageDlg(siLang.GetTextOrDefault('S_WantExit'), mtConfirmation, [mbYes, mbNo], 0) = mrYes then
    begin
      CanClose := True;
      frmDebug.AddToDebugLog('');
      frmDebug.AddToDebugLog('Shuting down application.');
    end
    else
    begin
      CanClose := False;
    end;
  end;
end;

procedure TfrmSmartWindows.FormCreate(Sender: TObject);
var
  IgnoreConfig, AdvancedConfig: TJSONConfig;
  i: Integer;
  strlist: TStringList;
begin
  FSingleInstance := TSingleInstance.Create(nil);

  {$IFDEF DEBUG}
    lblDebugBuild.Visible := True;
  {$ELSE}
    lblDebugBuild.Visible := False;
  {$ENDIF}

  FRestoreSnapshowNow := False;
  FDisplayChangedNow := False;

  AppConfig := TJSONConfig.Create(ExtractFilePath(ParamStr(0)) + 'AppConfig.json', False);
  ConfigPath := IncludeTrailingPathDelimiter(ExpandEnvVars(AppConfig.ReadString('Main', 'ConfigPath', '')));
  ForceDirectories(ConfigPath);

  SnapshotPath := ConfigPath + 'Snapshots\';
  ForceDirectories(SnapshotPath);

  InactivitySnapshotPath := ConfigPath + 'InactivitySnapshots\';
  ForceDirectories(InactivitySnapshotPath);

  SnapshotListConfig := TJSONConfig.Create(SnapshotPath + 'Snapshots.json', False);
  InactivitySnapshotListConfig := TJSONConfig.Create(InactivitySnapshotPath + 'InactivitySnapshots.json', False);

  FRegPolicy := TRegistry.Create;
  FRegPolicy.RootKey := HKEY_CURRENT_USER;
  FRegPolicy.OpenKeyReadOnly('Software\Policies\delight software gmbh\delight SmartWindows');

  AppUserConfig := TJSONConfig.Create(ConfigPath + 'AppUserConfig.json', False);

  if AppConfig.ReadInteger('UserDefaults', 'Version') > AppUserConfig.ReadInteger('Main', 'Version') then
  begin
    //Global Version of config is higher or user config don't exist. So write userdefaults
    AppUserConfig.WriteInteger('Main', 'Version', AppConfig.ReadInteger('UserDefaults', 'Version'));
    AppUserConfig.WriteBool('Main', 'MinimizeOnStart', AppConfig.ReadBool('UserDefaults', 'MinimizeOnStart'));
    AppUserConfig.WriteBool('Main', 'StartWithWindows', AppConfig.ReadBool('UserDefaults', 'StartWithWindows'));
    AppUserConfig.WriteBool('Main', 'ShowTrayIcon', AppConfig.ReadBool('UserDefaults', 'ShowTrayIcon'));
    AppUserConfig.WriteBool('Main', 'ShowNotifications', AppConfig.ReadBool('UserDefaults', 'ShowNotifications'));
    AppUserConfig.WriteBool('Main', 'CheckForUpdates', AppConfig.ReadBool('UserDefaults', 'CheckForUpdates'));
    AppUserConfig.WriteBool('Main', 'RestoreMinimizedOnlyWhenOpen', AppConfig.ReadBool('UserDefaults', 'RestoreMinimizedOnlyWhenOpen'));
    AppUserConfig.WriteBool('Main', 'SaveRestoreDesktopWallpaper', AppConfig.ReadBool('UserDefaults', 'SaveRestoreDesktopWallpaper'));
    AppUserConfig.WriteBool('Main', 'InactivitySnapshots', AppConfig.ReadBool('UserDefaults', 'InactivitySnapshots'));
    AppUserConfig.WriteString('Main', 'Language', AppConfig.ReadString('UserDefaults', 'Language'));
  end;

  if siLangDispatcher.LangNames.IndexOf(AppUserConfig.ReadString('Main', 'Language')) > -1 then
    SetLanguage(AppUserConfig.ReadString('Main', 'Language'))
  else
    siLangDispatcher.Language := 'English';


  frmDebug := TfrmDebug.Create(nil);
  frmDebug.DebugToFile := AppConfig.ReadBool('Main', 'DebugToFile');
  frmDebug.DebugActive := AppConfig.ReadBool('Main', 'Debug');

  OpenWindowsHashedList := TStringList.Create;

  frmDebug.AddToDebugLog('');
  frmDebug.AddToDebugLog('Starting application.');

  WindowList := TWindowList.Create;
  WindowLastSeen := TWindowLastSeen.Create;
  DesktopIconList := TDesktopIconList.Create;

  LastKnownResolution := GetCurrentResolution;

  frmDebug.AddToDebugLog('');
  frmDebug.AddToDebugLog('Current Resolution: ' + LastKnownResolution);

  LoadLastSeenFromFile;
  LoadWindowListFromFile;

  LoadDesktopIconListFromFile;
  SaveRestoreDesktopIcons(aRestore);

  IgnoreListHashedClassNames := TStringList.Create;
  IgnoreConfig := TJSONConfig.Create(ExtractFilePath(ParamStr(0)) + 'IgnoreList.json', False);
  try
    IgnoreConfig.ReadSections(IgnoreListHashedClassNames);
    frmDebug.AddToDebugLog('');
    frmDebug.AddToDebugLog('Ignorelist file "' + IgnoreConfig.FileName + '" loaded from disk.');
  finally
    IgnoreConfig.Free;
  end;

  GenClassNameWithCaptionList := TStringList.Create;
  AdvancedConfig := TJSONConfig.Create(ExtractFilePath(ParamStr(0)) + 'AdvancedConfig.json', False);
  strlist := TStringList.Create;
  try
    AdvancedConfig.ReadSections(strlist);
    for i := 0 to strlist.Count - 1 do
    begin
      if AdvancedConfig.ReadBool(strlist[i], 'UseCaptionForWindowID') then
      begin
        GenClassNameWithCaptionList.Add(AdvancedConfig.ReadString(strlist[i], 'ClassName'));
      end;
    end;

    frmDebug.AddToDebugLog('');
    frmDebug.AddToDebugLog('AdvancedConfig file "' + AdvancedConfig.FileName + '" loaded from disk.');
  finally
    strlist.Free;
    AdvancedConfig.Free;
  end;

  //Strip "Item_" Tag from Ignorelist
  for i := 0 to IgnoreListHashedClassNames.Count - 1 do
  begin
    IgnoreListHashedClassNames[i] := RemoveConfigPrefix(IgnoreListHashedClassNames[i]);
  end;

  TrayIcon.Visible := SettingShowTrayIcon.Value;
  TrayIcon.Icon.Handle := Application.Icon.Handle;

  if SettingStartMinimized.Value then
  begin
    Hide;
    WindowState := wsMinimized;
  end
  else
  begin
    WindowState := wsNormal;
  end;


  SetStartWithWindows(SettingStartWithWindows.Value);


  LoadEnvironmentConfig;

  RestoreEnvironmentConfig;


  //Autosave config files each 5min
  tmSaveInterval.Interval := 300000;
  tmSaveInterval.Enabled := True;

  //Check each 200ms for foreground window change
  tmCheckForegroundWindow.Interval := 200;
  tmCheckForegroundWindow.Enabled := True;

  RefreshSnapshotList;

  //Register session notification that we will receive messages for locking, disconnect
  WTSRegisterSessionNotification(WindowHandle, NOTIFY_FOR_THIS_SESSION);
  FSystemIsIdle := False;
  tmIdleCheck.Enabled := SettingInactivitySnapshotsEnabled.Value;
end;

procedure TfrmSmartWindows.FormDestroy(Sender: TObject);
var
  i: Integer;
begin
  WTSUnRegisterSessionNotification(WindowHandle);

  for i := 0 to cbSnapshots.Properties.Items.Count - 1 do
  begin
    cbSnapshots.Properties.Items.Objects[i].Free;
  end;

  SaveEnvironmentConfig;
  EnvironmentConfig.Free;

  SnapshotListConfig.Save;
  SnapshotListConfig.Free;

  InactivitySnapshotListConfig.Save;
  InactivitySnapshotListConfig.Free;

  IgnoreListHashedClassNames.Free;
  GenClassNameWithCaptionList.Free;

  SaveWindowListToFile;
  SaveLastSeenToFile;

  SaveRestoreDesktopIcons(aSave);
  SaveDesktopIconListToFile;

  SaveAppUserConfig;

  WindowListConfig.Free;
  DesktopIconList.Free;
  DesktopIconListConfig.Free;
  WindowLastSeen.Free;
  WindowLastSeenConfig.Free;
  WindowList.Free;
  AppUserConfig.Free;
  AppConfig.Free;

  OpenWindowsHashedList.Free;

  frmDebug.Free;
  FSingleInstance.Free;

  FRegPolicy.CloseKey;
  FRegPolicy.Free;
end;

procedure TfrmSmartWindows.FormShow(Sender: TObject);
begin
  UpdateGUIElements;

  PostMessage(Self.Handle, WM_AFTER_SHOW, 0, 0);
end;

function TfrmSmartWindows.GetPCName: string;
var
  ComputerName: array[0..256] of char;
  Size: DWord;
begin
  Size := 256;
  GetComputerName(ComputerName, Size);
  Result := LowerCase(ComputerName);
end;

function TfrmSmartWindows.GetRestoreMinimizedOnlyWhenOpen: TUserSettings;
begin
  if FRegPolicy.ValueExists('RestoreMinimizedOnlyWhenOpen') then
  begin
    Result.PolicyControlled := True;
    Result.UserControlled := False;
    Result.Value := FRegPolicy.ReadBool('RestoreMinimizedOnlyWhenOpen');
  end
  else
  begin
    if AppConfig.KeyExists('Policy', 'RestoreMinimizedOnlyWhenOpen') then
    begin
      Result.PolicyControlled := True;
      Result.UserControlled := False;
      Result.Value := AppConfig.ReadBool('Policy', 'RestoreMinimizedOnlyWhenOpen');
    end
    else
    begin
      Result.PolicyControlled := False;
      Result.UserControlled := True;
      Result.Value := AppUserConfig.ReadBool('Main', 'RestoreMinimizedOnlyWhenOpen');
    end;
  end;
end;

function TfrmSmartWindows.GetSaveRestoreDesktopWallpaper: TUserSettings;
begin
  if FRegPolicy.ValueExists('SaveRestoreDesktopWallpaper') then
  begin
    Result.PolicyControlled := True;
    Result.UserControlled := False;
    Result.Value := FRegPolicy.ReadBool('SaveRestoreDesktopWallpaper');
  end
  else
  begin
    if AppConfig.KeyExists('Policy', 'SaveRestoreDesktopWallpaper') then
    begin
      Result.PolicyControlled := True;
      Result.UserControlled := False;
      Result.Value := AppConfig.ReadBool('Policy', 'SaveRestoreDesktopWallpaper');
    end
    else
    begin
      Result.PolicyControlled := False;
      Result.UserControlled := True;
      Result.Value := AppUserConfig.ReadBool('Main', 'SaveRestoreDesktopWallpaper');
    end;
  end;
end;

function TfrmSmartWindows.GetSettingCheckForUpdates: TUserSettings;
begin
  if FRegPolicy.ValueExists('CheckForUpdates') then
  begin
    Result.PolicyControlled := True;
    Result.UserControlled := False;
    Result.Value := FRegPolicy.ReadBool('CheckForUpdates');
  end
  else
  begin
    if AppConfig.KeyExists('Policy', 'CheckForUpdates') then
    begin
      Result.PolicyControlled := True;
      Result.UserControlled := False;
      Result.Value := AppConfig.ReadBool('Policy', 'CheckForUpdates');
    end
    else
    begin
      Result.PolicyControlled := False;
      Result.UserControlled := True;
      Result.Value := AppUserConfig.ReadBool('Main', 'CheckForUpdates');
    end;
  end;
end;

function TfrmSmartWindows.GetSettingInactivitySnapshotsEnabled: TUserSettings;
begin
  if FRegPolicy.ValueExists('InactivitySnapshots') then
  begin
    Result.PolicyControlled := True;
    Result.UserControlled := False;
    Result.Value := FRegPolicy.ReadBool('InactivitySnapshots');
  end
  else
  begin
    if AppConfig.KeyExists('Policy', 'InactivitySnapshots') then
    begin
      Result.PolicyControlled := True;
      Result.UserControlled := False;
      Result.Value := AppConfig.ReadBool('Policy', 'InactivitySnapshots');
    end
    else
    begin
      Result.PolicyControlled := False;
      Result.UserControlled := True;
      Result.Value := AppUserConfig.ReadBool('Main', 'InactivitySnapshots');
    end;
  end;
end;

function TfrmSmartWindows.GetSettingShowFirstStart: TUserSettings;
begin
  if FRegPolicy.ValueExists('DisableFirstStart') then
  begin
    Result.PolicyControlled := True;
    Result.UserControlled := False;
    Result.Value := not FRegPolicy.ReadBool('DisableFirstStart');
  end
  else
  begin
    if AppConfig.KeyExists('Policy', 'DisableFirstStart') then
    begin
      Result.PolicyControlled := True;
      Result.UserControlled := False;
      Result.Value := not AppConfig.ReadBool('Policy', 'DisableFirstStart');
    end
    else
    begin
      Result.PolicyControlled := False;
      Result.UserControlled := True;
      Result.Value := not AppUserConfig.ReadBool('Main', 'FirstStartShown');
    end;
  end;
end;

function TfrmSmartWindows.GetSettingShowNotifications: TUserSettings;
begin
  if FRegPolicy.ValueExists('ShowNotifications') then
  begin
    Result.PolicyControlled := True;
    Result.UserControlled := False;
    Result.Value := FRegPolicy.ReadBool('ShowNotifications');
  end
  else
  begin
    if AppConfig.KeyExists('Policy', 'ShowNotifications') then
    begin
      Result.PolicyControlled := True;
      Result.UserControlled := False;
      Result.Value := AppConfig.ReadBool('Policy', 'ShowNotifications');
    end
    else
    begin
      Result.PolicyControlled := False;
      Result.UserControlled := True;
      Result.Value := AppUserConfig.ReadBool('Main', 'ShowNotifications');
    end;
  end;
end;

function TfrmSmartWindows.GetSettingShowTrayIcon: TUserSettings;
begin
  if FRegPolicy.ValueExists('ShowTrayIcon') then
  begin
    Result.PolicyControlled := True;
    Result.UserControlled := False;
    Result.Value := FRegPolicy.ReadBool('ShowTrayIcon');
  end
  else
  begin
    if AppConfig.KeyExists('Policy', 'ShowTrayIcon') then
    begin
      Result.PolicyControlled := True;
      Result.UserControlled := False;
      Result.Value := AppConfig.ReadBool('Policy', 'ShowTrayIcon');
    end
    else
    begin
      Result.PolicyControlled := False;
      Result.UserControlled := True;
      Result.Value := AppUserConfig.ReadBool('Main', 'ShowTrayIcon');
    end;
  end;
end;

function TfrmSmartWindows.GetSettingStartMinimized: TUserSettings;
begin
  if FRegPolicy.ValueExists('MinimizeOnStart') then
  begin
    Result.PolicyControlled := True;
    Result.UserControlled := False;
    Result.Value := FRegPolicy.ReadBool('MinimizeOnStart');
  end
  else
  begin
    if AppConfig.KeyExists('Policy', 'MinimizeOnStart') then
    begin
      Result.PolicyControlled := True;
      Result.UserControlled := False;
      Result.Value := AppConfig.ReadBool('Policy', 'MinimizeOnStart');
    end
    else
    begin
      Result.PolicyControlled := False;
      Result.UserControlled := True;
      Result.Value := AppUserConfig.ReadBool('Main', 'MinimizeOnStart');
    end;
  end;
end;

function TfrmSmartWindows.GetSettingStartWithWindows: TUserSettings;
begin
  if FRegPolicy.ValueExists('StartWithWindows') then
  begin
    Result.PolicyControlled := True;
    Result.UserControlled := False;
    Result.Value := FRegPolicy.ReadBool('StartWithWindows');
  end
  else
  begin
    if AppConfig.KeyExists('Policy', 'StartWithWindows') then
    begin
      Result.PolicyControlled := True;
      Result.UserControlled := False;
      Result.Value := AppConfig.ReadBool('Policy', 'StartWithWindows');
    end
    else
    begin
      Result.PolicyControlled := False;
      Result.UserControlled := True;
      Result.Value := AppUserConfig.ReadBool('Main', 'StartWithWindows');
    end;
  end;
end;

function TfrmSmartWindows.GetSettingUpdatesEnabled: TUserSettings;
begin
  if FRegPolicy.ValueExists('UpdatesEnabled') then
  begin
    Result.PolicyControlled := True;
    Result.UserControlled := False;
    Result.Value := FRegPolicy.ReadBool('UpdatesEnabled');
  end
  else
  begin
    if AppConfig.KeyExists('Policy', 'UpdatesEnabled') then
    begin
      Result.PolicyControlled := True;
      Result.UserControlled := False;
      Result.Value := AppConfig.ReadBool('Policy', 'UpdatesEnabled');
    end
    else
    begin
      Result.PolicyControlled := False;
      Result.UserControlled := True;
      Result.Value := True;
    end;
  end;
end;

function TfrmSmartWindows.GetCurrentResolution: string;
var
  DPI: string;
  i: Integer;
begin
  //Always get DPI of primary screen
  DPI := '96';
  for i := 0 to Screen.MonitorCount - 1 do
  begin
    if Screen.Monitors[i].Primary then
    begin
      DPI := Screen.Monitors[i].PixelsPerInch.ToString;
      Break;
    end;
  end;

  Result := Screen.DesktopWidth.ToString + 'x' + Screen.DesktopHeight.ToString + 'x' + DPI;
end;

function TfrmSmartWindows.GetDesktopListViewHandle: HWND;
var
  hShellViewWin, hWorkerWin: HWND;
begin
  //Windows 8 and Windows 7 with rotating wallpapers
  hWorkerWin := 0;
  hShellViewWin := 0;

  hWorkerWin := FindWindowEx(GetDesktopWindow, 0, 'WorkerW', nil);
  while (hShellViewWin = 0) and (hWorkerWin > 0) do
  begin
    hWorkerWin := FindWindowEx(GetDesktopWindow, hWorkerWin, 'WorkerW', nil);
    hShellViewWin := FindWindowEx(hWorkerWin, 0, 'SHELLDLL_DefView', 0);
  end;

  Result := FindWindowEx(hShellViewWin, 0, 'SysListView32', 0);

  //Default Windows 7
  if Result = 0 then
  begin
    Result := FindWindow('Progman', nil);
    Result := FindWindowEx(Result, 0, 'SHELLDLL_DefView', nil);
    Result := FindWindowEx(Result, 0, 'SysListView32', nil);
  end;
end;

function TfrmSmartWindows.GetHashedStringFromWindow(const AHWND: HWND; const AWantDebugLog: Boolean = True): THashedStringInfo;

  procedure EnumChildCount(const AHWND: HWND; var count: Integer);
  var
    prev, curr: HWND;
  begin
    prev := 0;
    curr := FindWindowEx(AHWND, 0, nil, nil);
    while curr > 0 do
    begin
      Inc(count);
      EnumChildCount(curr, count);
      prev := curr;
      curr := FindWindowEx(AHWND, prev, nil, nil);
    end;
  end;

var
  ico: HICON;
  icon: TIcon;
  iconstring: string;
  Title, ClassName: array[0..255] of char;
  stream: TMemoryStream;
begin
  GetClassName(AHWND, ClassName, 255);
  GetWindowText(AHWND, Title, 255);

  ico := SendMessage(AHWND, WM_GETICON, 0, 0); //small icon of window record
  if ico = 0 then
  begin
    ico := SendMessage(AHWND, WM_GETICON, 1, 0); //large icon of window record
    if ico = 0 then
    begin
      ico := SendMessage(AHWND, WM_GETICON, 2, 0); //sys icon of window record
      if ico = 0 then
      begin
        ico := GetClassLongPtr(AHWND, GCL_HICON); //large icon of class record
        if ico = 0 then
        begin
          ico := GetClassLongPtr(AHWND, GCL_HICONSM); //small icon of class record
        end;
      end;
    end;
  end;


  if ico = 0 then
  begin
    iconstring := '';
  end
  else
  begin
    icon := TIcon.Create;
    stream := TMemoryStream.Create;
    try
      icon.Handle := ico;
      icon.SaveToStream(stream);
      stream.Position := 0;
      iconstring := RwHashStream(stream);
    finally
      stream.Free;
      icon.Free;
      DestroyIcon(ico);
    end;
  end;

  if AWantDebugLog then frmDebug.AddToDebugLog('');
  if AWantDebugLog then frmDebug.AddToDebugLog('Hashing Window: ClassName: "' + ClassName + '" :: Iconstring: ' + iconstring);

  if IsGenClassNameWithCaption(ClassName) then
  begin
    Result.HashedString := RwHashString(ClassName + '::' + iconstring + '::' + Title, beHexEncoded);
  end
  else
  begin
    Result.HashedString := RwHashString(ClassName + '::' + iconstring, beHexEncoded);
  end;

  Result.Icon := ico;
  Result.ClassName := ClassName;
  Result.WindowCaption := Title;
  Result.IsClassNameIgnored := IsHashedClassNameIgnored(Result.HashedString);
  Result.GenClassNameWithCaption := IsGenClassNameWithCaption(ClassName);


  if AWantDebugLog then frmDebug.AddToDebugLog('Hashed Window Value: ' + Result.HashedString);
end;

function TfrmSmartWindows.GetIsSaveableWindow(const AHWND: HWND): Boolean;
var
  Title: array[0..255] of char;
begin
  GetWindowText(AHWND, Title, 255);
  //Don't save and restore modal windows. Too much problems with Z-Order. Sometimes modal windows restored in background so the app is freezing.
  Result := (IsWindowVisible(AHWND)) and (Title <> '') and (not IsModalWindow(AHWND));
end;

procedure TfrmSmartWindows.Image1DblClick(Sender: TObject);
begin
  frmDebug.Show;
end;

procedure TfrmSmartWindows.InitEnum;
begin
  OpenWindowsHashedList.Clear;
  FIsInEnumWindowProgress := True;
end;

function TfrmSmartWindows.IsGenClassNameWithCaption(
  const AClassName: string): Boolean;
begin
  Result := GenClassNameWithCaptionList.IndexOf(AClassName) > -1;
end;

function TfrmSmartWindows.IsHashedClassNameIgnored(
  const AHashedClassName: string): Boolean;
begin
  Result := IgnoreListHashedClassNames.IndexOf(AHashedClassName) > -1;
end;

function TfrmSmartWindows.IsModalWindow(const AHWND: Hwnd): Boolean;
var
  owner: HWND;
begin
  owner := GetWindow(AHWND, GW_OWNER);
  if owner = 0 then
  begin
    // Window has no owner, so it's not modal. Modal windows always has an owner
    Result := False;
  end
  else
  begin
    // If owner is disabled - the hwnd is modal
    Result := not IsWindowEnabled(owner);
  end;
end;

function TfrmSmartWindows.IsWindowSizeable(const AHWND: HWND): Boolean;
begin
  Result := (GetWindowLongPtr(AHWND, GWL_STYLE) and WS_THICKFRAME) = WS_THICKFRAME;
end;

procedure TfrmSmartWindows.lblDebugBuildClick(Sender: TObject);
begin
  TObject.Create;
end;

procedure TfrmSmartWindows.LoadDesktopIconListFromFile;
var
  i: Integer;
  dii: TDesktopIconItem;
  strlist: TStringList;
  FileName: string;
begin
  if Assigned(DesktopIconListConfig) then
  begin
    DesktopIconListConfig.Free;
  end;

  FileName := ConfigPath + 'DesktopIcons_' + LastKnownResolution + '.json';

  frmDebug.AddToDebugLog('');
  frmDebug.AddToDebugLog('Loading DesktopIcons file "' + FileName + '"');

  DesktopIconListConfig := TJSONConfig.Create(FileName, False);
  DesktopIconList.Clear;

  strlist := TStringList.Create;
  try
    DesktopIconListConfig.ReadSections(strlist);
    for i := 0 to strlist.Count - 1 do
    begin
      dii := TDesktopIconItem.Create;
      dii.IconText := DesktopIconListConfig.ReadString(strlist[i], 'IconText');
      dii.X := DesktopIconListConfig.ReadInteger(strlist[i], 'X');
      dii.Y := DesktopIconListConfig.ReadInteger(strlist[i], 'Y');
      DesktopIconList.Add(dii);
    end;
  finally
    strlist.Free;
  end;

  frmDebug.AddToDebugLog('');
  frmDebug.AddToDebugLog('DesktopIcons file "' + FileName + '" loaded from disk.');
end;

procedure TfrmSmartWindows.LoadEnvironmentConfig;
var
  FileName: string;
begin
  if Assigned(EnvironmentConfig) then
  begin
    EnvironmentConfig.Free;
  end;

  FileName := ConfigPath + 'Environment_' + LastKnownResolution + '.json';

  frmDebug.AddToDebugLog('');
  frmDebug.AddToDebugLog('Loading Environment file "' + FileName + '"');

  EnvironmentConfig := TJSONConfig.Create(FileName, False);

  frmDebug.AddToDebugLog('');
  frmDebug.AddToDebugLog('Environment file "' + FileName + '" loaded from disk.');
end;

procedure TfrmSmartWindows.LoadLastSeenFromFile;
var
  wls: TWindowLastSeenItem;
  strlist: TStringList;
  i: Integer;
  FileName: string;
begin
  FileName := ConfigPath + 'WindowsLastSeen_' + GetPCName + '.json';

  frmDebug.AddToDebugLog('');
  frmDebug.AddToDebugLog('Loading WindowsLastSeen file "' + FileName + '"');

  WindowLastSeenConfig := TJSONConfig.Create(FileName, False);
  strlist := TStringList.Create;
  try
    WindowLastSeenConfig.ReadSections(strlist);
    for i := 0 to strlist.Count - 1 do
    begin
      wls := TWindowLastSeenItem.Create;
      wls.HashedClassName := RemoveConfigPrefix(strlist[i]);
      wls.LastSeen := WindowLastSeenConfig.ReadDateTime(strlist[i], 'LastSeen');
      wls.LastSeenResolution := WindowLastSeenConfig.ReadString(strlist[i], 'LastSeenResolution');
      WindowLastSeen.Add(wls);
    end;
  finally
    strlist.Free;
  end;

  frmDebug.AddToDebugLog('');
  frmDebug.AddToDebugLog('WindowsLastSeen file "' + FileName + '" loaded from disk.');
end;

procedure TfrmSmartWindows.LoadWindowListFromFile;
var
  i: Integer;
  wli: TWindowListItem;
  strlist: TStringList;
  FileName: string;
begin
  if Assigned(WindowListConfig) then
  begin
    WindowListConfig.Free;
  end;

  FileName := ConfigPath + 'WindowList_' + LastKnownResolution + '.json';

  frmDebug.AddToDebugLog('');
  frmDebug.AddToDebugLog('Loading WindowList file "' + FileName + '"');

  WindowListConfig := TJSONConfig.Create(FileName, False);

  WindowList.Clear;

  strlist := TStringList.Create;
  try
    WindowListConfig.ReadSections(strlist);
    for i := 0 to strlist.Count - 1 do
    begin
      wli := TWindowListItem.Create;
      wli.HashedClassName := RemoveConfigPrefix(strlist[i]);
      wli.ClassName := WindowListConfig.ReadString(strlist[i], 'ClassName');
      wli.Top := WindowListConfig.ReadInteger(strlist[i], 'Top');
      wli.Left := WindowListConfig.ReadInteger(strlist[i], 'Left');
      wli.Width := WindowListConfig.ReadInteger(strlist[i], 'Width');
      wli.Height := WindowListConfig.ReadInteger(strlist[i], 'Height');
      wli.State := WindowListConfig.ReadInteger(strlist[i], 'State');
      wli.MinX := WindowListConfig.ReadInteger(strlist[i], 'MinX');
      wli.MinY := WindowListConfig.ReadInteger(strlist[i], 'MinY');
      wli.MaxX := WindowListConfig.ReadInteger(strlist[i], 'MaxX');
      wli.MaxY := WindowListConfig.ReadInteger(strlist[i], 'MaxY');
      wli.IsSizeable := WindowListConfig.ReadBool(strlist[i], 'Sizeable');
      wli.RestoreFlags := WindowListConfig.ReadInteger(strlist[i], 'RestoreFlags');

      wli.AlwaysRestore := AppUserConfig.ReadBool(strlist[i], 'AlwaysRestore');

      WindowList.Add(wli);
    end;
  finally
    strlist.Free;
  end;

  frmDebug.AddToDebugLog('');
  frmDebug.AddToDebugLog('WindowList file "' + FileName + '" loaded from disk.');
end;

procedure TfrmSmartWindows.mnuRestoreClick(Sender: TObject);
begin
  TrayIconClick(Sender);
end;

procedure TfrmSmartWindows.pbtnRestoreSnapshotClick(Sender: TObject);
begin
  RestoreSnapshot(TString(cbSnapshots.Properties.Items.Objects[TMenuItem(Sender).Tag]).Str, False);
end;

procedure TfrmSmartWindows.pmnuRestoreInactivitySnapshotClick(Sender: TObject);
begin
  RestoreSnapshot('', True);
end;

procedure TfrmSmartWindows.RefreshSnapshotList;
var
  i: Integer;
  strlist: TStringList;
  str: TString;
  item: TMenuItem;
  idx: Integer;
begin
  for i := 0 to cbSnapshots.Properties.Items.Count - 1 do
  begin
    cbSnapshots.Properties.Items.Objects[i].Free;
  end;
  cbSnapshots.Properties.Items.Clear;

  pmnuRestore.Clear;
  pmnuRestore.Enabled := False;

  btnRemoveSnapshot.Enabled := False;
  btnRestoreSnapshot.Enabled := False;

  strlist := TStringList.Create;
  try
    SnapshotListConfig.ReadSections(strlist);
    for i := 0 to strlist.Count - 1 do
    begin
      if (SnapshotListConfig.ReadString(strlist[i], 'ComputerName') = GetPCName) and (SnapshotListConfig.ReadString(strlist[i], 'Resolution') = LastKnownResolution) then
      begin
        str := TString.Create(strlist[i]);
        idx := cbSnapshots.Properties.Items.AddObject(SnapshotListConfig.ReadString(strlist[i], 'SnapshotName'), str);

        item := TMenuItem.Create(pmnuRestore);
        item.Caption := SnapshotListConfig.ReadString(strlist[i], 'SnapshotName');
        item.Tag := idx;
        item.OnClick := pbtnRestoreSnapshotClick;
        pmnuRestore.Add(item);
        pmnuRestore.Enabled := True;
      end;
    end;
  finally
    strlist.Free;
  end;

  pmnuRestoreInactivitySnapshot.Enabled := InactivitySnapshotListConfig.SectionExists(GetPCName + '_' + LastKnownResolution);
  pmnuRestoreInactivitySnapshot.Caption := siLang.GetTextOrDefault('S_RestoreInactivitySnapshot');
  if pmnuRestoreInactivitySnapshot.Enabled then
  begin
    pmnuRestoreInactivitySnapshot.Caption := pmnuRestoreInactivitySnapshot.Caption + ' (' + InactivitySnapshotListConfig.ReadString(GetPCName + '_' + LastKnownResolution, 'LastSnapshot') + ')';
  end;
end;

function TfrmSmartWindows.RemoveConfigPrefix(const AStr: string): string;
begin
  Result := StringReplace(AStr, 'Item_', '', [rfReplaceAll]);
end;

procedure TfrmSmartWindows.RestoreSnapshot(const ASnapshotID: string; const IsInactivitySnapshot: Boolean);
var
  WindowListFile, DesktopIconsFile, EnvironmentFile: string;
  i: Integer;
  wls: TWindowLastSeenItem;
  RestorePath: string;
  SnapshotName: string;
begin
  tmCheckForegroundWindow.Enabled := False;

  WindowListFile := WindowListConfig.FileName;
  DesktopIconsFile := DesktopIconListConfig.FileName;
  EnvironmentFile := EnvironmentConfig.FileName;

  FreeAndNil(DesktopIconListConfig);
  FreeAndNil(WindowListConfig);
  FreeAndNil(EnvironmentConfig);

  if IsInactivitySnapshot then
  begin
    RestorePath := InactivitySnapshotPath + GetPCName + '_' + LastKnownResolution;
    SnapshotName := InactivitySnapshotListConfig.ReadString(GetPCName + '_' + LastKnownResolution, 'LastSnapshot');
  end
  else
  begin
    RestorePath := SnapshotPath + ASnapshotID;
    SnapshotName := SnapshotListConfig.ReadString(ASnapshotID, 'SnapshotName');
  end;

  CopyFile(PChar(RestorePath + '\WindowList.json'), PChar(WindowListFile), False);
  CopyFile(PChar(RestorePath + '\DesktopIcons.json'), PChar(DesktopIconsFile), False);
  CopyFile(PChar(RestorePath + '\Environment.json'), PChar(EnvironmentFile), False);

  LoadDesktopIconListFromFile;
  LoadWindowListFromFile;

  //Override LastSeen flags to force a restore of desktopicons and window list
  for i := 0 to WindowList.Count - 1 do
  begin
    wls := WindowLastSeen.GetItemByHashedClassName(WindowList.Items[i].HashedClassName);
    if Assigned(wls) then  // if LastSeen config for Window found, set to 'SnapshotRestore' to force window restore
    begin
      wls.LastSeenResolution := 'SnapshotRestore';
    end;
  end;
  AppUserConfig.WriteString('DesktopIcons_' + GetPCName, 'LastSavedResolution', 'SnapshotRestore');

  AppUserConfig.WriteString('Environment_' + GetPCName, 'LastSavedResolution', 'SnapshotRestore');
  LoadEnvironmentConfig;
  RestoreEnvironmentConfig;

  SaveRestoreDesktopIcons(aRestore);

  ShowNotification(ntSnapshotRestored, SnapshotName);

  //Set current foreground window to invalid value, forces the timer to check for restoring
  FForegroundWindow := 0;
  FLastScannedForegroundWindow := 0;
  FRestoreSnapshowNow := True;
  tmCheckForegroundWindow.Enabled := True;
end;

procedure TfrmSmartWindows.RestoreEnvironmentConfig;
var
  reg: TRegistry;
begin
  if SettingSaveRestoreDesktopWallpaper.Value then
  begin
    if AppUserConfig.SectionExists('Environment_' + GetPCName) then
    begin
      if AppUserConfig.ReadString('Environment_' + GetPCName, 'LastSavedResolution') <> LastKnownResolution then
      begin
        // Restore environment settings
        frmDebug.AddToDebugLog('');
        frmDebug.AddToDebugLog('Restoring environment config');

        // Restore desktop wallpaper
        reg := TRegistry.Create;
        try
          reg.RootKey := HKEY_CURRENT_USER;
          reg.OpenKey('Control Panel\Desktop', True);
          try
            reg.WriteString('Wallpaper', EnvironmentConfig.ReadString('Wallpaper', 'Desktop-Wallpaper'));
            reg.WriteString('TileWallpaper', EnvironmentConfig.ReadString('Wallpaper', 'Desktop-TileWallpaper'));
            reg.WriteString('WallpaperStyle', EnvironmentConfig.ReadString('Wallpaper', 'Desktop-WallpaperStyle'));

            if reg.ValueExists('ImageColor') then
            begin
              reg.WriteInteger('ImageColor', EnvironmentConfig.ReadInteger('Wallpaper', 'Desktop-ImageColor'));
            end;
          finally
            reg.CloseKey;
          end;

          reg.OpenKey('Control Panel\Colors', True);
          try
            reg.WriteString('Background', EnvironmentConfig.ReadString('Wallpaper', 'Colors-Background'));
          finally
            reg.CloseKey;
          end;

          //Notify system to reload desktop wallpaper
          SystemParametersInfo(SPI_SETDESKWALLPAPER, 0, nil, SPIF_SENDCHANGE);

          ShowNotification(ntEnvironmentSettingsRestored);
        finally
          reg.Free;
        end;
      end;
    end;

    SaveEnvironmentConfig;
  end;
end;

procedure TfrmSmartWindows.SaveAppUserConfig;
begin
  AppUserConfig.Save;
  frmDebug.AddToDebugLog('');
  frmDebug.AddToDebugLog('AppUserConfig file "' + AppUserConfig.FileName + '" written to disk.');
end;

procedure TfrmSmartWindows.SaveDesktopIconListToFile;
var
  i: Integer;
  dii: TDesktopIconItem;
  section: string;
begin
  DesktopIconListConfig.ClearDocument;

  for i := 0 to DesktopIconList.Count - 1 do
  begin
    dii := DesktopIconList.Items[i];
    section := AddConfigPrefix(IntToStr(i));
    DesktopIconListConfig.WriteInteger(section, 'X', dii.X);
    DesktopIconListConfig.WriteInteger(section, 'Y', dii.Y);
    DesktopIconListConfig.WriteString(section, 'IconText', dii.IconText);
  end;

  DesktopIconListConfig.Save;

  AppUserConfig.WriteString('DesktopIcons_' + GetPCName, 'LastSavedResolution', LastKnownResolution);

  frmDebug.AddToDebugLog('');
  frmDebug.AddToDebugLog('DesktopIcons file "' + DesktopIconListConfig.FileName + '" written to disk.');
end;

procedure TfrmSmartWindows.SaveLastSeenToFile;
var
  i: Integer;
  wls: TWindowLastSeenItem;
begin
  for i := 0 to WindowLastSeen.Count - 1 do
  begin
    wls := WindowLastSeen.Items[i];
    if wls.HasChanged then
    begin
      WindowLastSeenConfig.WriteDateTime(AddConfigPrefix(wls.HashedClassName), 'LastSeen', wls.LastSeen);
      WindowLastSeenConfig.WriteString(AddConfigPrefix(wls.HashedClassName), 'LastSeenResolution', wls.LastSeenResolution);
      wls.HasChanged := False;
    end;
  end;

  WindowLastSeenConfig.Save;

  frmDebug.AddToDebugLog('');
  frmDebug.AddToDebugLog('Config file "' + WindowLastSeenConfig.FileName + '" written to disk.');
end;

procedure TfrmSmartWindows.SaveRestoreDesktopIcons(const AAction: TAction);
var
  hWnd1: HWND;
  dwProcessId: DWORD;
  hProcess: THandle;
  Pointer1: Pointer;
  PLVItem1: PLVITEM;
  PChar1: PChar;
  NumberOfBytesRead: SIZE_T;
  NumberOfItems: Integer;
  i: Integer;
  myIconPoint: TPoint;
  dii: TDesktopIconItem;
begin
  if AAction = aRestore then
  begin
    if AppUserConfig.ReadString('DesktopIcons_' + GetPCName, 'LastSavedResolution') = '' then
    begin
      //We have no config to restore
      exit;
    end
    else
    begin
      if AppUserConfig.ReadString('DesktopIcons_' + GetPCName, 'LastSavedResolution') = LastKnownResolution then
      begin
        //Last saved resolution = current resolution. do nothing
        exit;
      end
      else
      begin
        //Restore desktop icons - write current resolution
        frmDebug.AddToDebugLog('');
        frmDebug.AddToDebugLog('Restoring desktop icons. Last Resolution was ' + AppUserConfig.ReadString('DesktopIcons_' + GetPCName, 'LastSavedResolution') + ' : Current resolution is: ' + LastKnownResolution);

        AppUserConfig.WriteString('DesktopIcons_' + GetPCName, 'LastSavedResolution', LastKnownResolution);
        ShowNotification(ntDesktopIconsRestored);
      end;
    end;
  end;

  if AAction = aSave then
  begin
    frmDebug.AddToDebugLog('');
    frmDebug.AddToDebugLog('Saving Desktop icons.');
  end;


  hWnd1 := GetDesktopListViewHandle;

  if hWnd1 > 0 then
  begin
    GetWindowThreadProcessId(hWnd1, @dwProcessId);
    hProcess := OpenProcess(
      PROCESS_VM_OPERATION or PROCESS_VM_READ or PROCESS_VM_WRITE,
      false, dwProcessId);
    Pointer1 := VirtualAllocEx(hProcess, nil, 4096,
                        MEM_RESERVE or MEM_COMMIT, PAGE_READWRITE);
    PLVItem1 := AllocMem(SizeOf(TLVItem));
    PChar1 := StrAlloc(256);

    NumberOfItems := ListView_GetItemCount(hWnd1);

    if AAction = aSave then
    begin
      DesktopIconList.Clear;
    end;

    for i := 0 to NumberOfItems - 1 do
    begin
      with PLVItem1^ do
      begin
        cchTextMax := 256;
        pszText := Pointer(Cardinal(Pointer1) + SizeOf(TLVItem));
        mask := LVIF_TEXT;
        iItem := i;
        iSubItem := 0;
      end;

      //Because of this function platform (x86 or x64) must match
      WriteProcessMemory(hProcess, Pointer1, PLVItem1, SizeOf(TLVItem), NumberOfBytesRead);
      SendMessage(hWnd1, LVM_GETITEM, i, lparam(Pointer1));
      ReadProcessMemory( hProcess, Pointer(Cardinal(Pointer1) +
         SizeOf(TLVItem)), PChar1, DWORD(256),
         NumberOfBytesRead);

      ListView_GetItemPosition(hWnd1, i, PPoint(Pointer1)^);
             ReadProcessMemory( hProcess, Pointer1, @myIconPoint,
             DWORD(sizeof(TPoint)), NumberOfBytesRead);

      case AAction of
        aRestore:
        begin
          dii := DesktopIconList.GetItemByIconText(PChar1);

          if dii <> nil then
          begin
            ListView_SetItemPosition(hWnd1, i, dii.X, dii.Y);
          end;
        end;

        aSave:
        begin
          dii := TDesktopIconItem.Create;
          dii.IconText := PChar1;
          dii.X := myIconPoint.X;
          dii.Y := myIconPoint.Y;
          DesktopIconList.Add(dii);
        end;
      end;
    end;

    StrDispose(PChar1);
    FreeMem(PLVItem1);
    VirtualFreeEx(hProcess, Pointer1, 0, MEM_RELEASE);
    CloseHandle(hProcess);
  end;
end;

procedure TfrmSmartWindows.SaveEnvironmentConfig;
var
  reg: TRegistry;
begin
  if SettingSaveRestoreDesktopWallpaper.Value then
  begin
    reg := TRegistry.Create;
    try
      reg.RootKey := HKEY_CURRENT_USER;
      reg.OpenKey('Control Panel\Desktop', True);
      try
        EnvironmentConfig.WriteString('Wallpaper', 'Desktop-Wallpaper', reg.ReadString('Wallpaper'));
        EnvironmentConfig.WriteString('Wallpaper', 'Desktop-TileWallpaper', reg.ReadString('TileWallpaper'));
        EnvironmentConfig.WriteString('Wallpaper', 'Desktop-WallpaperStyle', reg.ReadString('WallpaperStyle'));

        if reg.ValueExists('ImageColor') then
        begin
          EnvironmentConfig.WriteInteger('Wallpaper', 'Desktop-ImageColor', reg.ReadInteger('ImageColor'));
        end;
      finally
        reg.CloseKey;
      end;

      reg.OpenKey('Control Panel\Colors', True);
      try
        EnvironmentConfig.WriteString('Wallpaper', 'Colors-Background', reg.ReadString('Background'));
      finally
        reg.CloseKey;
      end;
    finally
      reg.Free;
    end;

    EnvironmentConfig.Save;

    AppUserConfig.WriteString('Environment_' + GetPCName, 'LastSavedResolution', LastKnownResolution);

    frmDebug.AddToDebugLog('');
    frmDebug.AddToDebugLog('Environment file "' + EnvironmentConfig.FileName + '" written to disk.');
  end;
end;

procedure TfrmSmartWindows.SaveWindowListToFile;
var
  i: Integer;
  wli: TWindowListItem;
  section: string;
begin
  for i := 0 to WindowList.Count - 1 do
  begin
    wli := WindowList.Items[i];
    if wli.HasChanged then
    begin
      section := AddConfigPrefix(wli.HashedClassName);
      WindowListConfig.WriteString(section, 'ClassName', wli.ClassName);
      WindowListConfig.WriteInteger(section, 'Top', wli.Top);
      WindowListConfig.WriteInteger(section, 'Left', wli.Left);
      WindowListConfig.WriteInteger(section, 'Width', wli.Width);
      WindowListConfig.WriteInteger(section, 'Height', wli.Height);
      WindowListConfig.WriteInteger(section, 'MinX', wli.MinX);
      WindowListConfig.WriteInteger(section, 'MinY', wli.MinY);
      WindowListConfig.WriteInteger(section, 'MaxX', wli.MaxX);
      WindowListConfig.WriteInteger(section, 'MaxY', wli.MaxY);
      WindowListConfig.WriteInteger(section, 'State', wli.State);
      WindowListConfig.WriteInteger(section, 'RestoreFlags', wli.RestoreFlags);
      WindowListConfig.WriteBool(section, 'Sizeable', wli.IsSizeable);

      wli.HasChanged := False;
    end;
  end;

  WindowListConfig.Save;

  frmDebug.AddToDebugLog('');
  frmDebug.AddToDebugLog('Config file "' + WindowListConfig.FileName + '" written to disk.');
end;

procedure TfrmSmartWindows.SetLanguage(const ALangName: string);
begin
  siLangDispatcher.Language := ALangName;
  mnubtnGerman.Down := siLangDispatcher.Language = 'German';
  mnubtnEnglish.Down := siLangDispatcher.Language = 'English';
  AppUserConfig.WriteString('Main', 'Language', ALangName);
end;

procedure TfrmSmartWindows.SetStartWithWindows(const Value: Boolean);
var
  reg: TRegistry;
begin
  FStartWithWindows := Value;

  reg := TRegistry.Create;
  reg.RootKey := HKEY_CURRENT_USER;
  reg.OpenKey('Software\Microsoft\Windows\CurrentVersion\Run', True);
  try
    if FStartWithWindows then
    begin
      reg.WriteString('delight SmartWindows', ParamStr(0));
    end
    else
    begin
      if reg.ValueExists('delight SmartWindows') then
      begin
        reg.DeleteValue('delight SmartWindows');
      end;
    end;
  finally
    reg.CloseKey;
    reg.Free;
  end;
end;

procedure TfrmSmartWindows.ShowNotification(const ANotificationType: TNotificationType; const AWindowTitle: string = ''; const AOldRes: string = ''; const ANewRes: string = '');
begin
  if SettingShowNotifications.Value then
  begin
    case ANotificationType of
      ntResolutionChanged:
      begin
        AlertWindows.Show(Application.Title, Format(siLang.GetTextOrDefault('S_NotificationScreenChanged'), [AOldRes, ANewRes]));
      end;

      ntWindowsRestored:
      begin
        AlertWindows.Show(Application.Title, Format(siLang.GetTextOrDefault('S_NotificationWindowRestored'), [AWindowTitle]));
      end;

      ntDesktopIconsRestored:
      begin
        AlertWindows.Show(Application.Title, siLang.GetTextOrDefault('S_NotificationDesktopIconsRestored'));
      end;

      ntSnapshotCreated:
      begin
        AlertWindows.Show(Application.Title, Format(siLang.GetTextOrDefault('S_NotificationSnapshotCreated'), [AWindowTitle]));
      end;

      ntSnapshotRestored:
      begin
        AlertWindows.Show(Application.Title, Format(siLang.GetTextOrDefault('S_NotificationSnapshotRestored'), [AWindowTitle]));
      end;

      ntSnapshotRemoved:
      begin
        AlertWindows.Show(Application.Title, Format(siLang.GetTextOrDefault('S_NotificationSnapshotRemoved'), [AWindowTitle]));
      end;

      ntEnvironmentSettingsRestored:
      begin
        AlertWindows.Show(Application.Title, siLang.GetTextOrDefault('S_NotificationEnvironmentSettingsRestored'));
      end;
    end;
  end;
end;

procedure TfrmSmartWindows.siLangChangeLanguage(Sender: TObject);
begin
  RefreshSnapshotList;
end;

procedure TfrmSmartWindows.tmCheckForegroundWindowTimer(Sender: TObject);
var
  Classname, Title: array[0..255] of char;
  wli: TWindowListItem;
  hash: string;
begin
  GetClassName(GetForegroundWindow, Classname, 255);
  if (Classname = 'TdxAlertWindow') or (Classname = 'TfrmDebug') then
  begin
    //if foreground window is our debug form or notification forms - do nothing here!
    Exit;
  end;

  //if foreground window change, scan all windows for changes
  if FForegroundWindow <> GetForegroundWindow then
  begin
    //Check if last active window was the desktop. If yes - save desktop icons!
    //Saving here is for better user experience. If someone test the software and changes often the resolution
    //withou saving here the user means the software doesn't work.
    if FForegroundWindow <> 0 then
    begin
      GetClassName(FForegroundWindow, Classname, 255);
      if (Classname = 'WorkerW') or (Classname = 'Progman') then
      begin
        SaveRestoreDesktopIcons(aSave);
      end;
    end;

    FForegroundWindow := GetForegroundWindow;
  end;


  if FLastScannedForegroundWindow <> GetForegroundWindow then
  begin
    tmCheckForegroundWindow.Enabled := False;

    //Set foreground window only if its a window we want to save or restore. This prevents looping for each window if its only
    //an unusable window. This prevents also that when a new window opens - but not yet visible become the active window. In this
    //case the window restoration don't work.
    //Restore always if FRestoreSnapshoNow is true. This guarantees the window restoration asap when restoring a snapshot.
    if GetIsSaveableWindow(GetForegroundWindow) or FRestoreSnapshowNow then
    begin
      FRestoreSnapshowNow := False;

      //Scan last foreground window before
      if FLastScannedForegroundWindow <> 0 then
      begin
        //Scan last foreground window for changes
        hash := GetHashedStringFromWindow(FLastScannedForegroundWindow).HashedString;
        wli := WindowList.GetItemByHashedClassName(hash);
        if wli <> nil then
        begin
          if wli.Handle = 0 then
          begin
            wli.IsWindowNewOpen := True;
          end
          else
          begin
            wli.IsWindowNewOpen := False;
          end;

          wli.Handle := FLastScannedForegroundWindow;

          UpdateWindowPos(hash);
        end;
      end;

      FLastScannedForegroundWindow := GetForegroundWindow;
      GetClassName(FLastScannedForegroundWindow, Classname, 255);
      GetWindowText(FLastScannedForegroundWindow, Title, 255);
      frmDebug.AddToDebugLog('');
      frmDebug.AddToDebugLog('Active window changed to: Classname: "' + Classname + '" :: Title: "' + Title + '"');

      if frmDebug.DebugActive or frmDebug.Visible then
      begin
        frmDebug.UpdateForegroundWindow(GetHashedStringFromWindow(FLastScannedForegroundWindow, False), LastKnownResolution, GetPCName);

        //Update List sizes
        frmDebug.AddToListSizesLog('');
        frmDebug.AddToListSizesLog('IgnoreListHashedClassNames Count: ' + IntToStr(IgnoreListHashedClassNames.Count));
        frmDebug.AddToListSizesLog('OpenWindowsHashedList Count: ' + IntToStr(OpenWindowsHashedList.Count));
        frmDebug.AddToListSizesLog('DesktopIconList Count: ' + IntToStr(DesktopIconList.Count));
        frmDebug.AddToListSizesLog('WindowList Count: ' + IntToStr(WindowList.Count));
        frmDebug.AddToListSizesLog('WindowLastSeen Count: ' + IntToStr(WindowLastSeen.Count));
      end;

      InitEnum;
      EnumWindows(@EnumWindowsProc, 0);
      FinalizeEnum;
    end;
    tmCheckForegroundWindow.Enabled := True;
  end;


  //Update Modal info on debug window
  if frmDebug.DebugActive or frmDebug.Visible then
  begin
    frmDebug.chkWindowModal.Checked := IsModalWindow(GetForegroundWindow);
  end;
end;

procedure TfrmSmartWindows.tmIdleCheckTimer(Sender: TObject);
var
  liInfo: TLastInputInfo;
  Idle: DWORD;
begin
  liInfo.cbSize := SizeOf(TLastInputInfo);
  GetLastInputInfo(liInfo);
  Idle := (GetTickCount - liInfo.dwTime) div 1000;

  if Idle >= 300 then  // Idle 5 min
  begin
    if not FSystemIsIdle then
    begin
      frmDebug.AddToDebugLog('');
      frmDebug.AddToDebugLog('Create inactivity snapshot. System is Idle since ' + Idle.ToString + ' seconds');
      FSystemIsIdle := True;
      CreateSnapshot('', True);
    end;
  end
  else
  begin
    FSystemIsIdle := False;
  end;
end;

procedure TfrmSmartWindows.tmSaveIntervalTimer(Sender: TObject);
begin
  if not FIsInEnumWindowProgress then
  begin
    SaveWindowListToFile;
    SaveLastSeenToFile;
    SaveRestoreDesktopIcons(aSave);
    SaveDesktopIconListToFile;
    SaveAppUserConfig;
    SaveEnvironmentConfig;
  end;
end;

procedure TfrmSmartWindows.TrayIconClick(Sender: TObject);
begin
  Show;
  WindowState := wsNormal;
  Application.BringToFront;
end;

procedure TfrmSmartWindows.UpdateGUIElements;
begin
  chkMinimized.Checked := SettingStartMinimized.Value;
  chkMinimized.Enabled := not SettingStartMinimized.PolicyControlled;

  chkStartWithWindows.Checked := SettingStartWithWindows.Value;
  chkStartWithWindows.Enabled := not SettingStartWithWindows.PolicyControlled;

  chkShowNotifications.Checked := SettingShowNotifications.Value;
  chkShowNotifications.Enabled := not SettingShowNotifications.PolicyControlled;

  chkShowTray.Checked := SettingShowTrayIcon.Value;
  chkShowTray.Enabled := not SettingShowTrayIcon.PolicyControlled;

  chkCheckForUpdates.Checked := SettingCheckForUpdates.Value;
  chkCheckForUpdates.Enabled := not SettingCheckForUpdates.PolicyControlled;

  chkRestoreMinimizedOnlyWhenOpen.Checked := SettingRestoreMinimizedOnlyWhenOpen.Value;
  chkRestoreMinimizedOnlyWhenOpen.Enabled := not SettingRestoreMinimizedOnlyWhenOpen.PolicyControlled;

  chkSaveRestoreDesktopWallpaper.Checked := SettingSaveRestoreDesktopWallpaper.Value;
  chkSaveRestoreDesktopWallpaper.Enabled := not SettingSaveRestoreDesktopWallpaper.PolicyControlled;

  chkInactivitySnapshots.Checked := SettingInactivitySnapshotsEnabled.Value;
  chkInactivitySnapshots.Enabled := not SettingInactivitySnapshotsEnabled.PolicyControlled;

  mnubtnOnlineUpdate.Enabled := not SettingUpdatesEnabled.PolicyControlled;
  mnubtnOnlineRegister.Enabled := not SettingUpdatesEnabled.PolicyControlled;

  lblSysAdmin.Visible := SettingStartMinimized.PolicyControlled or SettingStartWithWindows.PolicyControlled or SettingShowNotifications.PolicyControlled or SettingShowTrayIcon.PolicyControlled or SettingCheckForUpdates.PolicyControlled or SettingUpdatesEnabled.PolicyControlled or SettingRestoreMinimizedOnlyWhenOpen.PolicyControlled or SettingSaveRestoreDesktopWallpaper.PolicyControlled;
end;

procedure TfrmSmartWindows.UpdateWindowPos(const AHashedClassName: string);
var
  wli: TWindowListItem;
  win: TWindowPlacement;
  Title: array[0..255] of char;
  ClassName: array[0..255] of char;
begin
  wli := WindowList.GetItemByHashedClassName(AHashedClassName);
  win.length := SizeOf(TWindowPlacement);
  GetWindowPlacement(wli.Handle, win);
  GetWindowText(wli.Handle, Title, 255);
  GetClassName(wli.Handle, ClassName, 255);
  wli.Title := Title;
  wli.ClassName := ClassName;
  wli.Top := win.rcNormalPosition.Top;
  wli.Left := win.rcNormalPosition.Left;
  wli.Width := win.rcNormalPosition.Width;
  wli.Height := win.rcNormalPosition.Height;
  wli.MinX := win.ptMinPosition.X;
  wli.MinY := win.ptMinPosition.Y;
  wli.MaxX := win.ptMaxPosition.X;
  wli.MaxY := win.ptMaxPosition.Y;
  wli.State := win.showCmd;
  wli.RestoreFlags := win.flags;
  wli.IsSizeable := IsWindowSizeable(wli.Handle);
  wli.HasChanged := True;

  frmDebug.AddToDebugLog('Write: Top: ' + IntToStr(wli.Top) + ' Left: ' + IntToStr(wli.Left) + ' Width: ' + IntToStr(wli.Width) + ' Height: ' + IntToStr(wli.Height) + ' MinX: ' + IntToStr(wli.MinX) + ' MinY: ' + IntToStr(wli.MinY) + ' MaxX: ' + IntToStr(wli.MaxX) + ' MaxY: ' + IntToStr(wli.MaxY) + ' State: ' + IntToStr(wli.State) + ' RestoreFlags: ' + IntToStr(wli.RestoreFlags) + ' Sizeable: ' + BoolToStr(wli.IsSizeable, True));
end;

procedure TfrmSmartWindows.WmAfterShow(var Msg: TMessage);
begin
  if SettingShowFirstStart.Value then
  begin
    frmFirstStart.ShowModal;
    if not frmFirstStart.ShowAgain then
    begin
      AppUserConfig.WriteBool('Main', 'FirstStartShown', True);
    end;
  end;
end;

procedure TfrmSmartWindows.WMDisplayChange(var Msg: TWMDisplayChange);
var
  OldResolution: string;
begin
  //sometime we receive this message twice on resolution change
  if LastKnownResolution <> GetCurrentResolution then
  begin
    tmCheckForegroundWindow.Enabled := False;

    frmDebug.AddToDebugLog('');
    frmDebug.AddToDebugLog('Resolution changed to: ' + GetCurrentResolution);

    SaveWindowListToFile;
    SaveDesktopIconListToFile;
    SaveEnvironmentConfig;

    OldResolution := LastKnownResolution;
    LastKnownResolution := GetCurrentResolution;

    LoadWindowListFromFile;

    LoadDesktopIconListFromFile;
    SaveRestoreDesktopIcons(aRestore);

    LoadEnvironmentConfig;
    RestoreEnvironmentConfig;

    //Setting helper variable for better restore decisions when resolution changed
    FDisplayChangedNow := True;
    InitEnum;
    EnumWindows(@EnumWindowsProc, 0);
    FinalizeEnum;
    FDisplayChangedNow := False;

    RefreshSnapshotList;

    tmCheckForegroundWindow.Enabled := True;
  end;

  inherited;
end;

procedure TfrmSmartWindows.WMQueryEndSession(var Msg: TWMQueryEndSession);
begin
  tmCheckForegroundWindow.Enabled := False;
  FWindowsWantShutdown := True;
  frmDebug.AddToDebugLog('');
  frmDebug.AddToDebugLog('Windows want to shutdown or user logoff');
end;

procedure TfrmSmartWindows.WMSettingChange(var Msg: TMessage);
begin
  if Msg.WParam = SPI_SETDESKWALLPAPER then
  begin
    SaveEnvironmentConfig;
  end;
end;

procedure TfrmSmartWindows.WMWTSSessionChange(var Msg: TMessage);
var
  session: string;
begin
  case Msg.WParam of
    WTS_CONSOLE_DISCONNECT, WTS_REMOTE_DISCONNECT, WTS_SESSION_LOCK:
    begin
      if SettingInactivitySnapshotsEnabled.Value and not FSystemIsIdle then
      begin
        case Msg.WParam of
          WTS_CONSOLE_DISCONNECT: session := 'Console Disconnect';
          WTS_REMOTE_DISCONNECT: session := 'Remote Disconnect';
          WTS_SESSION_LOCK: session := 'Session Locked';
        end;
        frmDebug.AddToDebugLog('');
        frmDebug.AddToDebugLog('Create inactivity snapshot. Session changed to: ' + session);

        FSystemIsIdle := True;
        CreateSnapshot('', True);
      end;
    end;
  end;
end;

procedure TfrmSmartWindows.WMPowerBroadcast(var Msg: TMessage);
begin
  frmDebug.AddToDebugLog('');
  frmDebug.AddToDebugLog('Powermanagement Message received.');
  SaveWindowListToFile;
  SaveLastSeenToFile;
  SaveRestoreDesktopIcons(aSave);
  SaveDesktopIconListToFile;
  SaveAppUserConfig;
  SaveEnvironmentConfig;
end;

{ TString }

constructor TString.Create(const AStr: String);
begin
  inherited Create;
  FStr := AStr;
end;

procedure TString.SetStr(const Value: string);
begin
  FStr := Value;
end;

end.
