//---------------------------------------------------------------------------
#include <vcl.h>
#include <windows.h>
#include <stdio.h>

#pragma hdrstop

#include "Unit1.h"
#include "Unit2.h"              // для использования окна About
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1          *Form1;
DESKTOPS        Desktops;
//---------------------------------------------------------------------------
//--- функция, запускающая новый процесс на нужном рабочем столе  -----------
//---------------------------------------------------------------------------
BOOL StartMyProc(AnsiString CmdLine, WORD WinShowState, AnsiString DesktopName)
{       STARTUPINFO             si;     // структура, описывающая создаваемый процесс (используется для его создания)
        PROCESS_INFORMATION     pi;     // структура, описывающая созданный процесс (заполняется после его создания)

        ZeroMemory(&si, sizeof(STARTUPINFO));
        ZeroMemory(&pi, sizeof(PROCESS_INFORMATION));
        si.cb = sizeof(STARTUPINFO);                    // записываем размер структуры
        si.lpDesktop = DesktopName.c_str();             // записываем с каким рабочим столом ассоциировать запускаемый процесс (на каком рабочем столе нам нужна командная строка)
        si.dwFlags = STARTF_USESHOWWINDOW;              // сообщаем, что при формировании окна программы нужно использовать дополнительную информацию из параметра wShowWindow (при вызове функции ShowWindow)
        si.wShowWindow = WinShowState;                  // активируем создаваемое окно и показываем его в текущей позиции и размере (здесь же можно прописать, чтобы окно создавалось скрытым, свёрнутым и т.д.)

        // если запустить не удалось возвращаем FALSE, если удалось TRUE
        if(!CreateProcess(
                NULL,
                CmdLine.c_str(),
                NULL,
                NULL,
                FALSE,
                /*CREATE_NEW_CONSOLE |*/NORMAL_PRIORITY_CLASS,
                NULL,
                NULL,
                &si,
                &pi
                ))
                return FALSE;
        else    return TRUE;
}
//---------------------------------------------------------------------------
//--- функция, получающая имя объекта (windowstation, desktop) из описания --
//---------------------------------------------------------------------------
AnsiString GetUserObjectName(HANDLE hUserObject)
{       CHAR sResult[MAXOBJNAMEL];      // результат работы функции
        DWORD dwSize;                   // размер нужной информации

        // сначала определяем размер нужной нам информации
        GetUserObjectInformation(hUserObject, UOI_NAME, &sResult, 0, &dwSize);
        // если размер больше, чем нужно - читаем часть
        if(dwSize > MAXOBJNAMEL)        dwSize = MAXOBJNAMEL;
        // теперь читаем эту информацию
        if(!GetUserObjectInformation(hUserObject, UOI_NAME, &sResult, dwSize, &dwSize))
                sResult[0]=0;

        return AnsiString(sResult);
}
//---------------------------------------------------------------------------
//--- callback-функция для энумерации рабочих столов ------------------------
//--- процесс должен иметь права WINSTA_ENUMDESKTOPS к текущей windowstation
//---------------------------------------------------------------------------
BOOL CALLBACK EnumDesktopProc(LPTSTR lpszDesktop, LPARAM lParam)
{       Desktops.Names[Desktops.Counter] = lpszDesktop;                                                 // сохраняем имя рабочего стола
        Desktops.hDesktops[Desktops.Counter] = OpenDesktop(lpszDesktop,NULL,FALSE,MAXIMUM_ALLOWED);     // и указатель на него

        Desktops.Counter ++;    // номера в таблице на 1 больше, чем в структуре, из-за нулевой строки с заголовками                                                    

        Form1->StringGrid1->RowCount += 1;                                      // увеличиваем количество строк в таблице
        Form1->StringGrid1->Cells[0][Desktops.Counter] = Desktops.Counter;      // записываем номер рабочего стола в таблицу
        Form1->StringGrid1->Cells[1][Desktops.Counter] = lpszDesktop;           // записываем имя рабочего стола в таблицу
        return TRUE;
}
//---------------------------------------------------------------------------
//--- функция, выполняющая при помощи EnumDesktops энумерацию всех рабочих столов, ассоциированных с
//--- текущей WindowStation функция EnumDesktops будет вызывать callback-функцию пока та не вернёт false
//---------------------------------------------------------------------------
BOOL ReEnumAllDesktops()
{       BOOL dwResult = FALSE;

        // попробуем узнать все столы, ассоциированные с нашей windowstation
        if(hWinStaStart != NULL)        // если у нас есть указатель на windowstation
        {       // первым делом реинициализируем таблицу, в которой будем хранить имена рабочих столов
                // оставляем только строку с заголовками
                Form1->StringGrid1->RowCount = 1;
                // инициализируем заголовки таблицы
                Form1->StringGrid1->Cells[0][0]="Number";
                Form1->StringGrid1->Cells[1][0]="Desktop Name";
                // инициализируем размеры колонок
                Form1->StringGrid1->ColWidths[0]=60;
                Form1->StringGrid1->ColWidths[1]=373;
                // инициализируем счётчик рабочих столов
                Desktops.Counter = 0;
                // чтобы получить правильный указатель на callback-функцию, нужно обязательно в явном виде указывать его тип
                // (DESKTOPENUMPROC), иначе будет ошибка (билдер сам не сможет правильно преобразовать, "&" не прокатит)
                // аналогично для других callback-функций энумерации (например, для EnumWindowsProc нужен тип WNDENUMPROC)
                // какой нужен тип можно посмотреть на docs.microsoft.com в описании callback-функции
                EnumDesktops(hWinStaStart, (DESKTOPENUMPROC)EnumDesktopProc, NULL);     // находим все рабочие столы
                // делаем верхнюю строку фиксированной
                Form1->StringGrid1->FixedRows = 1;
        }
        else
        {       Form1->RichEdit1->Lines->Add("FAIL! There isn't a handle to windowstation!");
        }
        return dwResult;
}
//---------------------------------------------------------------------------
//--- функция переключения на выбранный рабочий стол ------------------------
//---------------------------------------------------------------------------
void SwitchToSelDesktop(int Selected, DWORD dwFlags)      // функция переключения на выбранный рабочий стол
{       AnsiString      TmpStr;                 // здесь будем писать путь к запускаемому приложению
        BOOL            IsExcept = TRUE;        // DWORD, показывающий был Exception или нет

        // защищаемая секция, если в ней произойдёт ошибка - нас выкинет в секцию __exception,
        // но у нас её нет, так что прога сразу перейдёт к секции __finally
        __try
       {        // пробуем переключиться на рабочий стол с именем из поля ввода
                if(!SwitchDesktop(Desktops.hDesktops[Selected]))        // если не смогли переключиться на нужный рабочий стол
                {       Form1->RichEdit1->Lines->Add("SwitchDesktop() failed, error " + IntToStr(GetLastError()) + ": " + SysErrorMessage(GetLastError()) + "!");
                        IsExcept =  FALSE;
                        return;
                }

                // если нужно - пробуем запустить на столе, на который мы переключились, новый экземпляр нашего приложения
                if((dwFlags & RUN_NEW) && (Form1->CheckBox2->Checked))
                {       if(!StartMyProc(ParamStr(0), SW_SHOW, Desktops.Names[Selected]))
                        {       Form1->RichEdit1->Lines->Add("CreateProcess() failed, error " + IntToStr(GetLastError()) + ": " + SysErrorMessage(GetLastError()) + "!");
                                IsExcept =  FALSE;
                                return;
                        }
                }
                // если нужно - пробуем запустить на столе, на который мы переключились, командную строку и выполнить tasklist
                if((dwFlags & RUN_CMD) && (Form1->CheckBox3->Checked))
                {       TmpStr = "C:\\Windows\\system32\\cmd.exe";              // прописываем путь к приложению, которое мы будем запускать
                        if(!StartMyProc(TmpStr, SW_SHOW, Desktops.Names[Selected]))
                        {       Form1->RichEdit1->Lines->Add("CreateProcess() failed, error " + IntToStr(GetLastError()) + ": " + SysErrorMessage(GetLastError()) + "!");
                                IsExcept =  FALSE;
                                return;
                        }
                }
                // если нужно - пробуем запустить указанную прогу на столе, на который мы переключились
                if((dwFlags & RUN_APP) && (Form1->CheckBox5->Checked))
                {       TmpStr = Form1->Edit3->Text;            // получаем путь к приложению, которое нужно запустить
                        if(!StartMyProc(TmpStr, SW_SHOW, Desktops.Names[Selected]))
                        {       Form1->RichEdit1->Lines->Add("CreateProcess() failed, error " + IntToStr(GetLastError()) + ": " + SysErrorMessage(GetLastError()) + "!");
                                IsExcept =  FALSE;
                                return;
                        }
                }
                // пробуем увидеть наш новый стол
                if(!SetThreadDesktop(Desktops.hDesktops[Selected]))     // если не смогли переключить приложение на нужный рабочий стол
                {       Form1->RichEdit1->Lines->Add("SetThreadDesktop() failed, error " + IntToStr(GetLastError()) + ": " + SysErrorMessage(GetLastError()) + "!");
                        IsExcept =  FALSE;
                        return;
                }

       }
       __finally
       {        if(IsExcept)    // если был эксепшен
                {       Form1->RichEdit1->Lines->Add("Exception occured, error " + IntToStr(GetLastError()) + ": " + SysErrorMessage(GetLastError()) + "!");
                        SetThreadDesktop(hDesktopStart);        // переключаем приложение на первоначальный рабочий стол
                        SwitchDesktop(hDesktopStart);           // восстанавливаем первоначальный рабочий стол
                }
       }
}
//---------------------------------------------------------------------------
//--- конструктор формы -----------------------------------------------------
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
        : TForm(Owner)
{
        // запоминаем стартовые параметры
        hWinStaStart = GetProcessWindowStation();               // запоминаем указатель на windowstation, ассоциированную с процессом нашей проги
        // понятно, что это должна быть winsta0, раз у нас приложуха с окном и всё по-умолчанию
        iThreadStart = GetCurrentThreadId();                    // запоминаем идентификатор процесса нашей проги
        hDesktopStart = GetThreadDesktop(iThreadStart);         // запоминаем стартовый активный рабочий стол нашей проги
        sWinStaStart = GetUserObjectName(hWinStaStart);         // запоминаем имя стартовой windowstation
        sDesktopStart = GetUserObjectName(hDesktopStart);       // запоминаем имя стартового рабочего стола

        // настраиваем форму
        Form1->AutoSize = true;                                 // устанавливаем авторазмер
        // выводим в лог текущую windowstation и desktop
        Form1->RichEdit1->Lines->Add("Start windowstation is: " + sWinStaStart);
        Form1->RichEdit1->Lines->Add("Start desktop is: " + sDesktopStart);
        Form1->RichEdit1->Lines->Add("Application path: " + ParamStr(0));
        // находим все рабочие столы нашей windowstation
        ReEnumAllDesktops();
        // регистрируем сочетание в качестве горячих клавиш под номером 1 (чтобы переключаться по ним на начальный рабочий стол)
        RegisterHotKey(Handle, 1, MOD_ALT + MOD_CONTROL, 'S');  // Ctrl + Alt + S
}
//---------------------------------------------------------------------------
//--- при уничтожении формы нужно отписаться от горячих клавиш --------------
//---------------------------------------------------------------------------
void __fastcall TForm1::FormDestroy(TObject *Sender)
{
        // снимаем регистрацию горячих клавиш, которую мы для себя устанавливали
        UnregisterHotKey(Handle, 1);    // снимаем регистрацию с id = 1 на горячие клавиши
}
//---------------------------------------------------------------------------
//--- кнопка создания нового рабочего стола ---------------------------------
//---------------------------------------------------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{       AnsiString      TmpStr;                 // сюда будем читать поле ввода
        BOOL            IsExcept = TRUE;        // DWORD, показывающий был Exception или нет
        HDESK           hNewDesktop;            // указатель на новый рабочий стол
        int             i;


        // если рабочих столов уже создано слишком много - выходим
        if(Desktops.Counter >= MAXDESKTOPS)
        {       Form1->RichEdit1->Lines->Add("Oh no! You have already created too many desktops!");     // пишем результат в log
                IsExcept = FALSE;
                return;
        }

        TmpStr = Form1->Edit1->Text;    // получаем имя стола, который нужно создать
        // если хотим создать стол с пустым именем - тоже выходим
        if(TmpStr == "")
        {       Form1->RichEdit1->Lines->Add("Oh no! You can't create a desktop with an empty name!");  // пишем результат в log
                IsExcept =  FALSE;
                return;
        }
        // имя создаваемого десктопа не должно содержать слэш
        if(TmpStr.Pos("\\"))
        {       Form1->RichEdit1->Lines->Add("Oh no! The name of desktop being created can't contain a slash!");        // пишем результат в log
                IsExcept =  FALSE;
                return;
        }
        // а нет ли у нас уже десктопа с таким именем
        for(i=0; i<Desktops.Counter; i++)
        {       if(TmpStr == Desktops.Names[i])
                {       Form1->RichEdit1->Lines->Add("Oh no! We already have a desktop with that name!");       // пишем результат в log
                        IsExcept =  FALSE;
                        return;
                }
        }
        __try
        {       // пробуем создать новый рабочий стол с именем из поля ввода
                hNewDesktop = CreateDesktop(TmpStr.c_str(), NULL, NULL, 0, GENERIC_ALL, NULL);
                if(hNewDesktop == NULL) // если стол не создан
                {       Form1->RichEdit1->Lines->Add("CreateDesktop() failed, error " + IntToStr(GetLastError()) + ": " + SysErrorMessage(GetLastError()) + "!");
                        IsExcept = FALSE;
                        return;         // и выходим
                }
                Form1->RichEdit1->Lines->Add("The new desktop has been successfully created!");
                IsExcept = FALSE;
                CloseHandle(hNewDesktop);       // указатель нам больше не нужен, мы всё равно при энумерации новый получим
        }
        // эта секция выполнится в любом случае, независимо от наличия эксепшенов или выходов по return
        __finally
        {       if(IsExcept)    Form1->RichEdit1->Lines->Add("Exception occured, error " + IntToStr(GetLastError()) + ": " + SysErrorMessage(GetLastError()) + "!");
                Desktops.Counter = 0;   // переинициализируем счётчик столов
                ReEnumAllDesktops();    // переенумеруем все столы
        }
}
//---------------------------------------------------------------------------
//--- кнопка переключения на созданный рабочий стол -------------------------
//--- при переключении на этом столе запускается cmd.exe --------------------
//---------------------------------------------------------------------------
void __fastcall TForm1::Button2Click(TObject *Sender)
{       AnsiString      TmpStr;         // сюда будем читать поле ввода
        int             Selected = -1;  // номер выбранного рабочего стола
        int             i;

        TmpStr = Form1->Edit2->Text;    // получаем имя стола, на который мы хотим переключиться

        // если хотим переключиться на стол с пустым именем - выходим
        if(TmpStr == "")
        {       Form1->RichEdit1->Lines->Add("Oh no! You can't swith to a desktop with an empty name!");  // пишем результат в log
                return;
        }
        // пробуем найти нужный стол среди всех известных нам столов
        i = 0;                          // инициализируем счётчик для перебора столов
        while(i < Desktops.Counter)     // перебираем все известные нам рабочие столы
        {       if(TmpStr == Desktops.Names[i])
                {       Selected = i;           // запоминаем номер найденного стола
                        i = Desktops.Counter;   // завершаем поиск
                }
                else    i++;
        }
        // если не нашли - выходим
        if(Selected == -1)
        {       Form1->RichEdit1->Lines->Add("Oh no! We don't have a handle for this table!");  // пишем результат в log
                return;
        }
        // пробуем переключиться на выбранный рабочий стол
        SwitchToSelDesktop(Selected, USE_ALL);
        // если нужно - пробуем закрыть этот экземпляр приложения
        if(Form1->CheckBox1->Checked)   Close();
}
//---------------------------------------------------------------------------
//--- меню выбора рабочего стола из таблицы ---------------------------------
//---------------------------------------------------------------------------
void __fastcall TForm1::Selectdesktop1Click(TObject *Sender)
{       int CurRow;

        CurRow=StringGrid1->Row;        // номер текущей выделенной строки
        if(CurRow > 0)
        {       // копируем имя выбранного стола на форму
                Form1->Edit2->Text = Desktops.Names[CurRow-1];
        }
}
//---------------------------------------------------------------------------
//--- обработчик закрытия окна программы (для переключения при выходе) ------
//---------------------------------------------------------------------------
void __fastcall TForm1::FormClose(TObject *Sender, TCloseAction &Action)
{
        // если нужно - пробуем переключиться на указанный рабочий стол
        if(Form1->CheckBox4->Checked == true)
        {       Form1->Button2Click(this);      // нажимаем на кнопку переключения стола
        }
}
//---------------------------------------------------------------------------
//--- обработчик события, когда процессу нашей проги прилетит мессадж -------
//---------------------------------------------------------------------------
void __fastcall TForm1::ApplicationEvents1Message(tagMSG &Msg, bool &Handled)
{       int             Selected = -1;  // номер выбранного рабочего стола
        int             i;

        // убеждаемся, что нам прилетел мессадж о сработке горячих клавиш
        if(Msg.message == WM_HOTKEY)
        {       // если идентификатор тот, который мы регистрировали
                if(Msg.wParam == 1)
                {       // возвращаемся на первоначальный рабочий стол, для чего:
                        // заново составляем список столов
                        ReEnumAllDesktops();
                        // пробуем найти наш стартовый стол среди всех известных нам столов
                        i = 0;                          // инициализируем счётчик для перебора столов
                        while(i < Desktops.Counter)     // перебираем все известные нам рабочие столы
                        {       if(sDesktopStart == Desktops.Names[i])
                                {       Selected = i;           // запоминаем номер найденного стола
                                        i = Desktops.Counter;   // завершаем поиск
                                }
                                else    i++;
                        }
                        // если не нашли - выходим
                        if(Selected == -1)
                        {       Form1->RichEdit1->Lines->Add("Oh no! We don't have a handle for this table!");  // пишем результат в log
                                return;
                        }
                        // пробуем переключиться на выбранный рабочий стол
                        SwitchToSelDesktop(Selected, NULL);
                }
        }
}
//---------------------------------------------------------------------------
//--- выбор файла, который нужно запустить на новом рабочем столе -----------
//---------------------------------------------------------------------------
void __fastcall TForm1::Button3Click(TObject *Sender)
{       if(OpenDialog1->Execute())
        {       Form1->Edit3->Text = OpenDialog1->FileName;     // получаем имя загружаемого файла
        }
}
//---------------------------------------------------------------------------
//--- открытие окна "О программе" -------------------------------------------
//---------------------------------------------------------------------------
void __fastcall TForm1::About2Click(TObject *Sender)
{       AboutBox->Show();
}
//---------------------------------------------------------------------------

