Программирование на C++ глазами хакера

Переключение экранов


Помнится, когда появилась первая версия программы Dashboard (она была еще под Windows 3.1), меня очень сильно заинтересовала возможность переключения экранов, и я долго искал готовую WinAPI-функцию, которой достаточно указать, какой экран надо показать, и все готово. Но это оказалось не так.

Немного позже я узнал, что эта возможность была слизана с ОС Linux, где виртуальные консоли (экраны) реализованы на уровне ядра. Я некоторое время помучился, но написал собственную маленькую утилиту для переключения экранов под Windows 9x. Сейчас я воспользуюсь этим нехитрым приемом для написания небольшой программы-шутки.

Как работает переключение экранов? Сразу открою вам секрет, никакого переключения реально не происходит. Просто все видимые окна убираются с Рабочего стола за его пределы так, чтобы вы их не видели. После этого перед пользователем остается чистый Рабочий стол. Когда нужно вернуться к старому экрану, то все возвращается обратно. Как видите, все гениальное — просто.

При переключении окна мгновенно перемещаются за границы видимости. Мы же будем перемещать все плавно, чтобы видеть, как все окна двигаются за левый край экрана. Тем самым будет создаваться эффект, будто окна убегают от нашего взора. Программа будет невидима, поэтому закрыть ее можно только снятием задачи. Самое интересное — наблюдать за этим процессом, потому что если вы не успеете снять задачу за 10 секунд, то окно Диспетчера задач тоже убежит, и придется начинать все заново.

Только вот перемещать окна надо не теми функциями, которые нам уже знакомы. Простые функции установки позиции тут не подойдут, потому что после изменения расположения каждого окна оно перерисовывается и отнимает много процессорного времени. Если у вас открыто 20 программ, то с помощью функции SetWindowPos перемещение будет слишком медленным и заметным.

Для того чтобы лже-переключения происходили быстро, в Windows есть несколько специальных функций, которые перемещают все указанные окна сразу. Рассмотрим пример использования этих функций.


Создайте новый проект Win32 Project и перейдите в функцию _tWinMain. Воспользуйтесь листингом 3.2 и до цикла обработки сообщений напишите необходимый для перемещения окон код.

Листинг 3.2. Код перемещения окон
HANDLE h=CreateEvent(0, true, false, "et");



//Бесконечный цикл while (TRUE) { int windowCount; int index; HWND winlist[10000]; HWND w; RECT WRct;

for (int i=0; iGetSystemMetrics(SM_CXSCREEN); i++) { //Считаем окна windowCount=0; w=GetWindow(GetDesktopWindow(),GW_CHILD); while (w!=0) { if (IsWindowVisible(w)) { winlist[windowCount]=w; windowCount++; } w=GetWindow(w,GW_HWNDNEXT);//Искать следующее окно } // Начало сдвига HDWP MWStruct=BeginDeferWindowPos(windowCount);//Начинаем сдвиг

// Определяем окна, которые надо сдвигать for (int index=0; indexwindowCount; index++) { GetWindowRect(winlist[index], WRct); MWStruct=DeferWindowPos(MWStruct, winlist[index], HWND_BOTTOM, WRct.left-10, WRct.top, WRct.right-WRct.left, WRct.bottom-WRct.top, SWP_NOACTIVATE || SWP_NOZORDER); } // Конец сдвига EndDeferWindowPos(MWStruct);//Конец сдвига }

WaitForSingleObject(h,2000); //Задержка в 2000 миллисекунд }

В самом начале создается пустое событие, которое в дальнейшем будет использоваться для задержки.

После этого запускается бесконечный цикл с помощью вызова while (true). Внутри цикла код делится на три маленькие части: сбор указателей на окна, сдвиг окон и задержка в 10 секунд. С задержкой мы уже сталкивались не один раз, и она вам уже должна быть знакома.

Сбор активных окон происходит следующим образом:

// Считаем окна w=GetWindow(GetDesktopWindow(), GW_CHILD); while (w!=0) { if (IsWindowVisible(w)) { winlist[windowCount]=w; windowCount++; }

w=GetWindow(w, GW_HWNDNEXT); // Искать следующее окно }

В первой строчке получаем указатель первого окна на Рабочем столе и записываем его в переменную w. Потом начинается цикл, который будет выполняться, пока полученный указатель не станет равным нулю, т.е. пока не переберем все окна.

В этом цикле, прежде чем запомнить указатель, происходит проверка видимости окна с помощью функции IsWindowVisible с параметром w. Если окно невидимо или свернуто (функция возвращает FALSE), то нет смысла его перемещать, в противном случае — указатель сохраняется в массиве winlist и увеличивается счетчик windowCount.



Итак, для поиска видимых окон используется функция GetWindow, которая может искать все окна, включая главные и подчиненные. Идентификатор найденного окна сохраняется в переменной w.

В данном случае для хранения указателей на окна используется массив заранее определенной длины (HWND winlist[10000]). В качестве длины я взял 10 000 элементов, и этого достаточно для хранения всех запущенных программ, потому что даже больше 100 окон запускать никто не будет.

В идеальном случае надо использовать динамические массивы (массив с динамически изменяемым количеством элементов), но я не стал применять их, чтобы не усложнять пример. Наша задача — посмотреть интересные алгоритмы, а вы потом можете сами доработать их до универсального вида.

После выполнения этого кода в массиве winlist будут храниться указатели всех запущенных и видимых программ, а в переменной windowCount — количество указателей в массиве.

А теперь о самом сдвиге. Он начинается с вызова API-функции BeginDeferWindowPos. Эта функция выделяет память для нового окна рабочего стола, куда мы будем сдвигать все видимые окна. В качестве параметра нужно указать, сколько окон мы будем двигать.

Для сдвига окна в подготовленную область памяти используется функция DeferWindowPos. В данный момент не происходит никаких реальных перемещений, а изменяется только информация о позиции и размерах. У этой функции следующие параметры:

результат выполнения функции BeginDeferWindowPos;

указатель на окно, которое надо переместить, — очередной элемент из массива;

номер по порядку (после какого окна должно быть помещено указанное);

следующие четыре параметра указывают координаты левой верхней позиции, ширину и высоту окна — получены с помощью функции GetWindowRect, и левая позиция для последующего сдвига уменьшена на 10;

флаги — указываем, что не надо активировать окно и упорядочивать.

После перемещения всех окон вызывается API-функция EndDeferWindowPos. Вот тут окна реально перескакивают в новое место. Это происходит практически моментально. Если бы вы использовали простую API-функцию SetwindowPos для установки позиции каждого окна в отдельности, то отрисовка происходила бы намного медленнее.



Хороший стиль программирования подразумевает, что все переменные, требующие значительной памяти (например, объекты), должны инициализироваться и уничтожаться. Во время инициализации память выделяется, а во время уничтожения — освобождается. Если не освобождать запрошенные ресурсы, то через какое-то время компьютер начнет очень медленно работать или может потребовать перезагрузку.

В примере я создавал объект, но нигде его не уничтожал, потому что программа выполняется бесконечно, и ее работа может прерваться только по двум причинам:

Выключили компьютер. В этом случае, даже если мы будем освобождать память, то она никому не понадобится.

Сняли процесс. Если кто-то додумается до этого, то программа закончит выполнение аварийно, а память все равно не освободится, даже если вставлен соответствующий код.

Получается, что освобождать объект бесполезно. Но все же я не советую вам пренебрегать такими вещами и в любом случае выполнять уничтожение объектов. Лишняя строчка кода никому не помешает, даже если вы думаете, что она никогда не выполнится. Зато это приучит вас всегда писать правильный код.

Попробуйте запустить программу, и все окна моментально улетят влево. Попытайтесь вызвать меню (щелкнуть правой кнопкой на Рабочем столе), и оно тоже улетит влево максимум через 2 секунды. Перемещаться будут любые запущенные программы.

Мне самому так понравился пример, что я целых полчаса играл с окнами. Они так интересно исчезают, что я не мог оторваться от этого глупого занятия. Но больше всего мне понравилось тренировать себя в скорости снятия приложения. Для этого я установил задержку в 5 секунд, потом — 4 и тренировал свои пальцы в быстром нажатии Ctrl+Alt+Del и поиске приложения, которое надо снять. Сложность в том, что окно с процессами тоже улетает, и если не успеть снять задачу, то придется повторять попытку снова.

Таким вот способом реализовано большинство программ, переключающих экраны. Во всяком случае, других методов мне не известно, и готовых функций я тоже не нашел.

Примечание
Исходный код примера, описанного в этом разделе, вы можете найти на компакт - диске в каталоге \Demo\Chapter3\DesktopSwitch.

Содержание раздела