На предприятии, где работает автор статьи, возникла необходимость мониторинга серверов без возможности доступа к ним, т.е. чтобы можно было отслеживать происходящие события, но нельзя было никак на них повлиять. Наименее экзотическим решением, которое пришло в голову, стал прозрачный хранитель экрана, который по выходу блокирует систему.
Поиски в интернете дали два возможных решения — хранитель экрана Microsoft Clear ScreenSaver и программа Transparent Screen Lock.
По ряду причин использовать программу мы не могли, поэтому остался только хранитель от Microsoft.
Последний можно было настроить на нечувствительность к мыши, но на клавиатуру он реагирует безусловным выходом. Это было неприемлемо. Тогда было принято решение написать такой хранитель самостоятельно.
Итак, ставим задачу.
Требуется написать хранитель экрана, который умеет следующее:
За основу хранителя был взят Random Images Screen Saver (автор Corbin Dunn) с сайта www.codegear.com.
С полупрозрачностью все более-менее ясно — используем свойство AlphaBlend формы в Delphi.
Пункт 4 постановки задачи кажется избыточным, ведь Windows и так блокирует станцию. Но все не так просто. Первый же запуск хранителя выявил первую проблему. Если в свойствах хранителя выставить «Защита паролем», то при старте хранителя видим нашу красивую картинку на рабочем столе и больше ничего. Ни окошек, ни рабочего стола, ни кнопки «Пуск». Чтение документации показало, что если выставлена опция «Защита паролем», то хранитель запускается в т.н. «secure desktop». Операционная система создает новый desktop с названием «Screen-saver», делает его текущим и на нем уже запускает хранитель. Подробности можно посмотреть в MSDN в разделе «Window Stations and Desktops».
Снимаем опцию. Хранитель запускается на рабочем столе. Первая проблема решена. Настраиваем приложение так, чтобы при любой попытке выхода из него (хоть по Alt-Tab, хоть по Win, хоть штатно по заданной комбинации клавиш) станция блокируется. Ура! Хранитель получился что надо. Все работает как задумано. Вроде бы…
Но! Запускается хранитель экрана. Нажимаем Ctrl-Alt-Del (появляется окно Winlogon), нажимаем Esc и попадаем на рабочий стол! Без блокировки станции!!!
Встраиваем в программу отладочный код и выясняется, что в подобной ситуации программа просто не доходит до кода, завершающего работу приложения. Автор до конца не уверен, поскольку документация на сей счет довольно скудная, но похоже, что в ситуации с Ctrl-Alt-Del и Esc хранитель уничтожается с помощью TerminateProcess.
Поскольку написанный нами хранитель должен блокировать станцию по выходу, нужно как минимум в этот выход попасть. Но этого не происходит.
В SDK в главе «Terminating a Process» есть такой абзац (в переводе автора):
«Если процесс завершается с помощью TerminateProcess, все потоки процесса немедленно завершаются без возможности запуска дополнительного кода. Это означает, что поток не исполняет код из завершающего блока. Более того, подключенные DLL не уведомляются об отключении». Очень похоже на нашу ситуацию.
Но это означает что мы не можем обеспечить блокирование станции по выходу. Можно, конечно, про «дырку» никому не рассказывать, но коллеги обнаружили ее примерно через 15 минут экспериментов…
Если мы не можем решить проблему «в лоб», то ищем обходной путь. Хранитель экрана от Microsoft помимо файла *.scr содержит еще и исполняемый модуль сервиса. Зачем он нужен, автор так и не разобрался. Может для переключения desktop'а, а может еще для чего.
Вариант с переключением хранителя на другой desktop из сервиса не прошел — два дня экспериментов окончились неудачей. Возможно, сервис используется для закрытия хранителя (с точки зрения системы) и открытия приложения. Но идея использования сервиса в голове осела.
Решение оказалось довольно простым.
Хранитель экрана состоит из двух частей — собственно хранителя, и сервиса, единственным назначением которого является блокировка станции по завершению работы хранителя.
Сервис постоянно проверяет состояние системного параметра SPI_GETSCREENSAVERRUNNING и если он меняет свое состояние с True на False, вызывает функцию LockWorkStation();
Код потока сервиса выглядит так:
Полный код находится во вложении.
Важными частями процедуры являются команды Sleep(10) и вызов функции LoadConfigEventFired. Первая возвращает управление системе, за счет чего цикл в потоке не забирает все процессорное время. Вторая проверяет, не выставлено ли событие LoadConfigEvent (см. определение в коде). Если последнее выставлено, то вызывается процедура загрузки конфигурационных параметров. Этим способом можно связать между собой изменение параметров хранителя экрана и получение этих параметров сервисом, его обслуживающим.
Строго говоря, полученный сервис можно использовать с любым хранителем. Как только система отрапортует, что хранитель экрана закрыт, в следующей итерации цикла сервис заблокирует станцию. Но такой код дублирует поведение системы и применение его с другим хранителем неразумно.
В процессе эксплуатации выяснилась одна особенность работы Windows, давшая неожиданный красивый результат. Если станция уже блокирована, то система для запуска хранителя все-таки создает еще один desktop и на нем запускает хранитель экрана. В случае с прозрачным окном это приводит к тому, что на экране показываются только красивые обои рабочего стола, а окошка Winlogon'a не видно, что визуально воспринимается очень хорошо.
Вложение
Замечания, предложения, вопросы направляйте по адресу dmitrys99/mail/ру
Поиски в интернете дали два возможных решения — хранитель экрана Microsoft Clear ScreenSaver и программа Transparent Screen Lock.
По ряду причин использовать программу мы не могли, поэтому остался только хранитель от Microsoft.
Последний можно было настроить на нечувствительность к мыши, но на клавиатуру он реагирует безусловным выходом. Это было неприемлемо. Тогда было принято решение написать такой хранитель самостоятельно.
Итак, ставим задачу.
Постановка задачи
Требуется написать хранитель экрана, который умеет следующее:
- выводит на экран прозрачное (полупрозрачное) окно, под которым видно происходящее в системе;
- закрывается только по определенной комбинации клавиш;
- игнорирует события от мыши;
- при выходе из хранителя блокирует рабочую станцию.
Решение «в лоб»
За основу хранителя был взят Random Images Screen Saver (автор Corbin Dunn) с сайта www.codegear.com.
С полупрозрачностью все более-менее ясно — используем свойство AlphaBlend формы в Delphi.
Пункт 4 постановки задачи кажется избыточным, ведь Windows и так блокирует станцию. Но все не так просто. Первый же запуск хранителя выявил первую проблему. Если в свойствах хранителя выставить «Защита паролем», то при старте хранителя видим нашу красивую картинку на рабочем столе и больше ничего. Ни окошек, ни рабочего стола, ни кнопки «Пуск». Чтение документации показало, что если выставлена опция «Защита паролем», то хранитель запускается в т.н. «secure desktop». Операционная система создает новый desktop с названием «Screen-saver», делает его текущим и на нем уже запускает хранитель. Подробности можно посмотреть в MSDN в разделе «Window Stations and Desktops».
Secure desktop
Снимаем опцию. Хранитель запускается на рабочем столе. Первая проблема решена. Настраиваем приложение так, чтобы при любой попытке выхода из него (хоть по Alt-Tab, хоть по Win, хоть штатно по заданной комбинации клавиш) станция блокируется. Ура! Хранитель получился что надо. Все работает как задумано. Вроде бы…
Но! Запускается хранитель экрана. Нажимаем Ctrl-Alt-Del (появляется окно Winlogon), нажимаем Esc и попадаем на рабочий стол! Без блокировки станции!!!
Встраиваем в программу отладочный код и выясняется, что в подобной ситуации программа просто не доходит до кода, завершающего работу приложения. Автор до конца не уверен, поскольку документация на сей счет довольно скудная, но похоже, что в ситуации с Ctrl-Alt-Del и Esc хранитель уничтожается с помощью TerminateProcess.
TerminateProcess
Поскольку написанный нами хранитель должен блокировать станцию по выходу, нужно как минимум в этот выход попасть. Но этого не происходит.
В SDK в главе «Terminating a Process» есть такой абзац (в переводе автора):
«Если процесс завершается с помощью TerminateProcess, все потоки процесса немедленно завершаются без возможности запуска дополнительного кода. Это означает, что поток не исполняет код из завершающего блока. Более того, подключенные DLL не уведомляются об отключении». Очень похоже на нашу ситуацию.
Но это означает что мы не можем обеспечить блокирование станции по выходу. Можно, конечно, про «дырку» никому не рассказывать, но коллеги обнаружили ее примерно через 15 минут экспериментов…
Решение с сервисом
Если мы не можем решить проблему «в лоб», то ищем обходной путь. Хранитель экрана от Microsoft помимо файла *.scr содержит еще и исполняемый модуль сервиса. Зачем он нужен, автор так и не разобрался. Может для переключения desktop'а, а может еще для чего.
Вариант с переключением хранителя на другой desktop из сервиса не прошел — два дня экспериментов окончились неудачей. Возможно, сервис используется для закрытия хранителя (с точки зрения системы) и открытия приложения. Но идея использования сервиса в голове осела.
Решение оказалось довольно простым.
Хранитель экрана состоит из двух частей — собственно хранителя, и сервиса, единственным назначением которого является блокировка станции по завершению работы хранителя.
Сервис постоянно проверяет состояние системного параметра SPI_GETSCREENSAVERRUNNING и если он меняет свое состояние с True на False, вызывает функцию LockWorkStation();
Код потока сервиса выглядит так:
procedure TSSThread.Execute;
var
PrevSSRunning,
CurrSSRunning: Boolean;
b: bool;
begin
CurrSSRunning := False;
while not Terminated do
begin
if LoadConfigEventFired() then
begin
LoadConfig();
ResetEvent(E);
end;
PrevSSRunning := CurrSSRunning;
SystemParametersInfo(SPI_GETSCREENSAVERRUNNING, 0, @b, 0);
CurrSSRunning := b;
if PrevSSRunning and (not CurrSSRunning) then
if LockWS then LockWorkStation();
Sleep(10);
end;
end;
Полный код находится во вложении.
Важными частями процедуры являются команды Sleep(10) и вызов функции LoadConfigEventFired. Первая возвращает управление системе, за счет чего цикл в потоке не забирает все процессорное время. Вторая проверяет, не выставлено ли событие LoadConfigEvent (см. определение в коде). Если последнее выставлено, то вызывается процедура загрузки конфигурационных параметров. Этим способом можно связать между собой изменение параметров хранителя экрана и получение этих параметров сервисом, его обслуживающим.
Строго говоря, полученный сервис можно использовать с любым хранителем. Как только система отрапортует, что хранитель экрана закрыт, в следующей итерации цикла сервис заблокирует станцию. Но такой код дублирует поведение системы и применение его с другим хранителем неразумно.
Красивая заставка блокированного компьютера
В процессе эксплуатации выяснилась одна особенность работы Windows, давшая неожиданный красивый результат. Если станция уже блокирована, то система для запуска хранителя все-таки создает еще один desktop и на нем запускает хранитель экрана. В случае с прозрачным окном это приводит к тому, что на экране показываются только красивые обои рабочего стола, а окошка Winlogon'a не видно, что визуально воспринимается очень хорошо.
Вложение
Замечания, предложения, вопросы направляйте по адресу dmitrys99/mail/ру