Гремлины Хеллoуина

Опубликовано 31 октября 2009 18:37 | Coding4Fun

· Автор: Рэндалл Маас (Randall Maas)

· Сложность : средняя

· Необходимое время : 4 часа

· Затраты : бесплатно

· ПО : Visual Basic или Visual C# Express

· Оборудование: нет

· Попробуйте прямо сейчас: запустить приложение

· Исходный код : загрузить

clip_image001

Введение

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

Развертывание

Немедленныйзапуск. Вы можете скачать и скопировать файлы theGremlin.exe и NAudio.dll на компьютер жертвы (скажем, в каталог c:\), а потом дважды щелкнуть исполняемый файл, чтобы запустить его.

Отложенныйзапуск . Другой вариант — скопировать исполняемый файл и библиотеку (или ярлык на этот файл) в папку Пуск (Startup) на компьютере жертвы и полюбоваться, какое веселье начнется, когда владелец этого компьютера включит его утром!

Если компьютер работает под управлением Windows XP, путь выглядит так:

    1: C:\Documents and Settings\All Users\Start Menu\Programs\Startup

В Vista и Windows 7 путь немного другой:

    1: C:\Users\USERNAME\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup

Не забудьте заменить USERNAME на имя пользователя компьютера.

Командная строка

Gremlin скомпилирована как оконная программа, поэтому при запуске она не будет открывать окно терминала. Но ее можно запускать из командной строки со следующими флагами:

-aggressive — гремлин станет агрессивнее;

-help — выводит список параметров командной строки;

-name ИМЯ — полезен для тестирования. Gremlin будет использовать окна только с данным заголовком или из указанного приложения. (Расширение «.exe» нужно убрать из имени исполняемого файла приложения.)

Чтобы остановить программу, нажмите CTRL+F2.

А теперь… плутоватые биты

Общая структура программы делится на три вида функциональности: действия, триггеры и кое-какую инфраструктуру (склеивающий слой):

Действия:

•    Тряска экрана реализуется специальным окном. Так и хочется проверить, не ослабло ли крепление кабеля на задней панели монитора.

•    Случайное событие может раскидать окна по экрану — и либо их движение замедлится от трения, либо...

•    Gremlin будет печатать бессмысленные сообщения, которые посылаются с виртуальной клавиатуры, или…

•    Двигать курсор мыши или…

•    Нажимать кнопки мыши или…

•    Случайным образом переключать фокус между окнами.

Триггеры:

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

•    В ином случае программа вызывает GetLastInputInfo() через механизм P/Invoke и ожидает, когда компьютер не будет использоваться в течение одной-двух минут. Обнаружив, что пользователь ничего не делает, программа случайным образом выбирает и выполняет свои действия.

Другое:

•    Скрытое окно принимает события нажатия клавиш. Я часто использую этот модуль для остановки экспериментальных программ, способных устроить бедлам на моем компьютере и помешать работе.

Интереса ради опишу в следующих разделах несколько этих модулей.

Модуль тряски экрана

Это мой любимый модуль. Он получает копию изображения на экране, создает окно, охватывающее весь экран, а затем смещает это изображение туда-сюда на несколько пикселей примерно каждые 30 мс; при этом используется вращение на небольшой угол, выбираемый случайным образом. (На более быстрых компьютерах эффект, конечно, получается лучше.)

У модуля тряски экрана четыре особенности:

•    это окно верхнего уровня — другие окна не могут размещаться поверх него;

•    оно никогда не становится окном в фокусе (т. е. окном, получающим события от клавиатуры);

•    события, связанные с кнопками мыши (например, одинарные и двойные щелчки), передаются через него другим окнам и рабочему столу. Это создает иллюзию того, что трясется «настоящий» рабочий стол: пользователь может щелкнуть кнопку или текст, и этот щелчок передается настоящей кнопке или тексту;

•    экран каждого монитора трясется независимо.

Немного базовых сведений: чтобы создать окно, имитирующее трясущийся экран, я использовал два класса. Первый из них — ScrShake (в ScreenShake.cs), который наследует от второго класса, UnfocusableForm. Сначала я опишу ScrShake.

Процесс тряски экрана требует наличия пяти переменных экземпляра:

•    animTimer — это System.Windows.Forms.Timer, используемый для принудительного рисования новой рамки на экране;

•    bx и by — определяют, на сколько пикселей нужно смещать экран вверх и вниз или влево и вправо;

•    angle — определяет, насколько искривляется экран во время тряски;

•    screenBitmap — массив битовых карт (растровых изображений), по одной на каждый монитор.

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

C#

    1: public partial class ScrShake : UnfocusableForm
    2: {
    3:    public ScrShake(double ShakeCoef, double AngleCoef) : base(true)
    4:    {
    5:       this.SuspendLayout();
    6:       … Прочий настроечный код…
    7:       // Это необходимо потому, что наше окно занимает весь экран
    8:       // и мы не хотим, чтобы другие области были смазанными
    9:       TransparencyKey =  BackColor = ForeColor =
   10:          System.Drawing.Color.Fuchsia;
   11:       DoubleBuffered = true;
   12:       TopMost = true;
   13:       FormBorderStyle = System.Windows.Forms.FormBorderStyle.None;
   14:  
   15:       ClientSize = new System.Drawing.Size(300, 300);
   16:       Name = "topimage";
   17:       ResumeLayout(false);
   18:  
   19:       // Это необходимо для сквозной передачи щелчков мыши
   20:       // окнам более низких уровней
   21:       ExStyle |= (int) WS . EX_TRANSPARENT;
   22:  
   23:       … Остальной код будет описан позже…
   24:    }
   25: }

Когда таймер выполнит заданное количество анимаций, он вызовет метод Stop(). Это приведет к прекращению анимации, ее очистке и скрытию окна.

C#

    1: void Stop()
    2: {
    3:    animTimer . Stop();
    4:    Hide();
    5:    screenBitmap = null;
    6: }

В основном цикле, запускающем процесс анимации, которая имитирует тряску экрана, вызывается метод Screens(). Он получает изображения экранов, определяет форму каждого монитора и запускает анимацию из десяти кадров.

C#

    1: internal void Screens()
    2: {
    3:    if (Visible)
    4:    {
    5:       Stop();
    6:       return ;
    7:    }
    8:    // Получает изображения на экранах
    9:    screenBitmap = Program.GrabScreens();
   10:    CountDown = 10;
   11:  
   12:    animTimer.Start();
   13:    angle = 0.0f;
   14:    … Код для вывода окна и получения формы мониторов (см. ниже)…
   15: }

Код, отвечающий за захват изображения на экране, находится в ScreenCapture.cs. Метод GrabScreens() создает индивидуальную битовую карту для каждого монитора и возвращает все такие битовые карты в виде массива.

Код, показанный ниже, определяет границы каждого экрана и вычисляет размер всех экранов, сложенных вместе. Тем самым определяется размер окна.

C #

    1: Screen[] AllScreens = Screen.AllScreens;
    2:  
    3: // Общая ширина/высота всех мониторов
    4: Rectangle fullSize = AllScreens[0].Bounds;
    5:  
    6: // Находим прямоугольник, который будет охватывать
    7: // все мониторы в системе (предполагается, что
    8: // основной монитор находится в левом верхнем углу!)
    9: for (int i = 1; i < AllScreens.Length &&
   10:     i < screenBitmap.Length; i++)
   11: {
   12:     Rectangle Bounds = AllScreens[i].Bounds;
   13:  
   14:     if (Bounds.Left < fullSize.Left)
   15:         fullSize.X = Bounds.X;
   16:  
   17:     if (Bounds.Right > fullSize.Right)
   18:         fullSize.Width = Bounds.Right - fullSize.X;
   19:  
   20:     if (Bounds.Top < fullSize.Top)
   21:         fullSize.Y = Bounds.Y;
   22:  
   23:     if (Bounds.Bottom > fullSize.Bottom)
   24:         fullSize.Height = Bounds.Bottom - fullSize.Y;
   25: }

Код для вывода окна, заполнения всего экрана и перемещения поверх остальных окон выглядит следующим образом.

C#

    1: Show();
    2: WindowState = FormWindowState.Normal;
    3: // Охватываем все мониторы одним гигантским окном
    4: Location = new Point(fullSize.Left, fullSize.Top);
    5: Size = new Size(fullSize.Width, fullSize.Height);
    6:  
    7: // Выводим его поверх остальных окон
    8: BringToFront();

Конструктор также создает таймер, который управляет эффектом тряски экрана. У таймера имеется делегат, который случайным образом выбирает угол поворота и смещение изображения и инициирует перерисовку окна. (Эффект тряски контролируется двумя внешними переменными: AngleCoef и ShakeCoef.)

C #

    1: animTimer = new System.Windows.Forms.Timer();
    2: animTimer.Tick += delegate(object A, EventArgs E)
    3: {
    4:     if (--CountDown < 1)
    5:         Stop();
    6:  
    7:     angle += (float)((Program.Rnd.NextDouble() - 0.5) * AngleCoef);
    8:     bx = (float)((Program.Rnd.NextDouble() - 0.5) * ShakeCoef);
    9:     by = (float)((Program.Rnd.NextDouble() - 0.5) * ShakeCoef);
   10:     Invalidate();
   11: };
   12:  
   13: // Устанавливаем интервал срабатывания таймера на 30 мс
   14: animTimer.Interval = 30;

Хотя экран каждого монитора одинаково смещается вверх-вниз и влево-вправо, а также поворачивается на один и тот же угол, рисование осуществляется независимо. Это создает иллюзию, будто изображение на каждом мониторе трясется. Рисование окна выполняется следующим кодом.

C#

    1: protected override void OnPaint(PaintEventArgs e)
    2: {
    3:     base.OnPaint(e);
    4:     Graphics g = e.Graphics;
    5:     g.CompositingQuality = CompositingQuality.HighQuality;
    6:  
    7:     // Эффект создается для каждого монитора отдельно
    8:     Screen[] AllScreens = Screen.AllScreens;
    9:     for(int i = 0; i < screenBitmap.Length; i++)
   10:     {
   11:         if (null == screenBitmap[i])   continue;
   12:         g.SmoothingMode = SmoothingMode.HighQuality;
   13:  
   14:         // Получаем размер текущего монитора
   15:         Rectangle region = AllScreens[i].Bounds;
   16:  
   17:         double ImWidth = screenBitmap[i].Width  * 
   18:            e.Graphics.DpiX / screenBitmap[i].HorizontalResolution;
   19:         double ImHeight= screenBitmap[i].Height * 
   20:            e.Graphics.DpiY / screenBitmap[i].VerticalResolution;
   21:         Matrix m = new Matrix();
   22:         m.Translate( (float)(- ImWidth  /2), 
   23:            (float)(- ImHeight /2), MatrixOrder.Append);
   24:  
   25:         // Поворачиваем битовую карту вокруг центра
   26:         m.RotateAt(angle, new Point(0,0), MatrixOrder.Append);
   27:  
   28:         // Центрируем изображение независимо от его размера
   29:         m.Translate( region.Width /2 - bx, 
   30:            region.Height/2 - by, MatrixOrder.Append);
   31:  
   32:         // Присваиваем переменной нашу матрицу преобразования
   33:         g.Transform = m;
   34:  
   35:         // Рисуем
   36:         g.DrawImage(screenBitmap[i], region.Left, region.Top);
   37:     }

Форма, которая ничего не делает!

Класс ScrShaker использует вспомогательный класс UnfocusableForm (в UnfocusableForm.cs) для создания окна, которое никогда не получает фокус, а значит, никогда не принимает событий, связанных с нажатиями клавиш и кнопок.

У этого окна нет внешней атрибутики — ни границ, ни маркера изменения размеров, ни значка, ни кнопок сворачивания/разворачивания, ни строки заголовка.

C#

    1: public partial class UnfocusableForm : Form
    2: {
    3:    public UnfocusableForm(bool X) : base()
    4:    {
    5:       if (X)
    6:         {
    7:            ControlBox = false;
    8:            FormBorderStyle =
    9:               System.Windows.Forms.FormBorderStyle.None;
   10:            ShowInTaskbar = false;
   11:            ShowIcon = false;
   12:            MinimizeBox = false;
   13:            SizeGripStyle = System.Windows.Forms.SizeGripStyle.Hide;
   14:            StartPosition =
   15:               System.Windows.Forms.FormStartPosition.Manual;
   16:         }
   17:    }
   18: }

Далее переопределяется метод ShowWithoutActivation, чтобы при отображении окно не становилось активным.

C#

    1: protected override bool ShowWithoutActivation
    2: { get {  return true; } }

Класс UnfocusableForm также переопределяет метод CreateParams() для задания дополнительных стилей при создании окна. Я не нашел гибкого способа для их задания. Вместо этого UnfocusableForm использует переменную ExStyle уровня метода, которая позволяет производным классам указывать нужные флаги. В данном случае класс ScrShake через эту переменную устанавливает флаг для игнорирования щелчков кнопок мыши.

C#

    1: protected override CreateParams CreateParams
    2: {
    3:   get
    4:   {
    5:      CreateParams cp=base.CreateParams;
    6:      cp . ExStyle |= ExStyle;
    7:      return cp;
    8:   }
    9: }

Наконец, он перехватывает несколько событий, позволяющих сделать окно активным. (Это обычно происходит, когда пользователь щелкает неактивное окно.)

C#

    1: internal const int MA_NOACTIVATE = 0x0003;
    2: protected override void WndProc(ref Message m)
    3: {
    4:   if (m.Msg == (int) WM.MOUSEACTIVATE)
    5:   {
    6:      m.Result = (IntPtr) MA_NOACTIVATE;
    7:      return;
    8:   }
    9:   if (m.Msg == (int) WM.FOCUS)
   10:   {
   11:      m.Result = (IntPtr)1;
   12:      return;
   13:   }
   14:   base.WndProc(ref m);
   15: }

Отправка событий мыши

Этот процесс прост, но отнюдь не тривиален. В вики на сайте Pinvoke.net можно найти сигнатуры процедур и структур, которые нам понадобится передавать. (Примечание: эти структуры немного различаются в зависимости от факторов, описанных в блоге, который ведет Реймонд Чен (Raymond Chen) (EN).)

C#

    1: [DllImport("user32.dll", EntryPoint = "SendInput",
    2:    SetLastError = true)]
    3: static extern uint SendInput(uint nInputs, INPUT[] Inputs,
    4:    int cbSize);
    5: [DllImport("user32.dll", EntryPoint = "GetMessageExtraInfo",
    6:    SetLastError = true)]
    7: static extern IntPtr GetMessageExtraInfo();
    8:  
    9: [StructLayout(LayoutKind.Sequential)]
   10: struct INPUT
   11: {
   12:    internal INPUTTYPE   type;
   13:    internal INPUT_UNION i;
   14: }
   15:  
   16:    // Генерируем анонимное объединение
   17: [StructLayout(LayoutKind.Explicit)]
   18: struct INPUT_UNION
   19: {
   20:    [FieldOffset(0)]
   21:    public MOUSEINPUT mi;
   22:    [FieldOffset(0)]
   23:    public KEYBDINPUT ki;
   24:    [FieldOffset(0)]
   25:    public HARDWAREINPUT hi;
   26: };
   27:  
   28: [StructLayout(LayoutKind.Sequential)]
   29: struct MOUSEINPUT
   30: {
   31:    public int    dx;
   32:    public int    dy;
   33:    public int    mouseData;
   34:    public MOUSEEVENTF dwFlags;
   35:    public int    time;
   36:    public IntPtr dwExtraInfo;
   37: }
   38:  
   39: [Flags]
   40: enum MOUSEEVENTF : int
   41: {
   42:    MOVE       = 0x01,
   43:    LEFTDOWN   = 0x02,
   44:    LEFTUP     = 0x04,
   45:    RIGHTDOWN  = 0x08,
   46:    RIGHTUP    = 0x10,
   47:    MIDDLEDOWN = 0x20,
   48:    MIDDLEUP   = 0x40,
   49:    ABSOLUTE   = 0x8000
   50: }

Каждая из этих частей объединяется специфическим образом. Для перемещения курсора мыши нужно создать структуру события мыши. С этой целью в dwFlags указывается тип события мыши (в нашем случае — относительное смещение), а переменным dx и dy присваивается число пикселей, на которое перемещается курсор мыши. Также важно присвоить dwExtraInfo информацию, возвращаемую GetMessageExtraInfo().

C#

    1: MOUSEINPUT MEvent = new MOUSEINPUT();
    2: MEvent.dwFlags = MOUSEEVENTF.MOVE;
    3: MEvent.dx = Rnd.Next(-8, 8) ;
    4: MEvent.dy = Rnd.Next(-8, 8);
    5: MEvent.dwExtraInfo = GetMessageExtraInfo();
    6: Send(MEvent);

Отправка события требует еще нескольких шагов, выполняемых вспомогательным методом Send(). Передавайте эти HID-события (Human Interface Devices) через процедуру SendInput() (была показана ранее). Эта процедура принимает массив событий для разных видов устройств. Поэтому мы должны создать массив, указать тип события, скопировать данные события, а затем выполнить следующий вызов.

C#

    1: public virtual bool Send(MOUSEINPUT Event)
    2: {
    3:   INPUT[] Events = new INPUT[1];
    4:   Events[0].type = INPUTTYPE.MOUSE;
    5:   Events[0].i.mi = MEvent;
    6:   return SendInput((uint)Events.Length, Events,
    7:      Marshal.SizeOf(Events[0])) > 0; 
    8: }

С этого момента передача щелчка кнопки мыши достаточно прямолинейна. Щелчок мыши — это на самом деле два события: нажатия и отпускания кнопки мыши. Чтобы было интереснее, кнопка мыши выбирается случайным образом. Неприятная часть работы заключается в том, что каждой кнопке мыши присваивается свой бит, поэтому для преобразования номера кнопки в соответствующий бит используется двоичное смещение. А отпускание кнопки — это еще один бит, поэтому второе сообщение сдвигает флаги, чтобы сменить состояние битов с «кнопка нажата» на «кнопка отпущена».

C#

    1: MOUSEINPUT MEvent = new MOUSEINPUT();
    2: MEvent.dwFlags = (MOUSEEVENTF) (1 << (2*Rnd.Next(0, 3)+1));
    3: MEvent.dwExtraInfo = GetMessageExtraInfo ();
    4: Send(MEvent);
    5:  
    6: MEvent.dwFlags = (MOUSEEVENTF)((int) MEvent.dwFlags << 1);
    7: MEvent.dwExtraInfo = GetMessageExtraInfo ();
    8: Send(MEvent);

Генерациябессмысленноготекста

Gremlin также может набирать бессмысленные фразы в текстовых редакторах, если пользователь оставит одну из таких программ открытой. Ввод текста осуществляется методом SendWait() класса SendKeys.

C #

    1: SendKeys.SendWait(GenerateSentence());
    2: SendKeys.SendWait("{ENTER}");

clip_image001[5]

Создание фраз — дело нехитрое; я использовал упрощенный генератор Маркова. Этот генератор случайным образом выбирает слово, с которого можно начать предложение. Массив таких слов содержится в переменной Starts. Затем в методе Transition это слово используется для поиска другого слова, которое можно включить в предложение. Для этого применяется словарь NonStart, который, принимая слово как ключ, возвращает список всех слов, которые можно поставить после него. Затем весь процесс повторяется, добавляя слова в конец строки. Процесс прекращается, как только обнаруживается слово, которым можно закончить предложение.

C#

    1: static Dictionary<string,string[]> NonStart ;
    2: static string[] Starts;
    3: static Dictionary<string,string> Terminal ;
    4:  
    5: static string GenerateSentence()
    6: {
    7:   StringBuilder SB = new StringBuilder();
    8:   string Word = Starts[ Rnd.Next(0, Starts.Length) ];
    9:  
   10:   Transition(SB, Word);
   11:   return SB.ToString();
   12: }
   13:  
   14: static void Transition(StringBuilder SB, string Word)
   15: {
   16:   while (true)
   17:   {
   18:      SB.Append(Word);
   19:      SB.Append(' ');
   20:  
   21:      // Ищем следующее слово
   22:      string[] Nexts;
   23:      if (!NonStart.TryGetValue(Word, out Nexts))
   24:        break;
   25:      int Idx = Rnd.Next(Terminal.ContainsKey(Word) ? -1 :
   26:        0, Nexts.Length);
   27:      if (Idx < 0)
   28:        break;
   29:      Word = Nexts[Idx];
   30:   }
   31: }

Создание двух словарей и массива — процесс относительно простой. Метод BuildSuffixTree() принимает строку и разбивает ее на фразы. Затем делит каждую фразу на слова (с буквами в нижнем регистре). Первое слово предложения помещается в конец массива Starts. Если слово не первое, оно используется для поиска списка в словаре и дописывается в этот список. Таким образом этот словарь превращается в словарь NonStarts. Последнее слово предложения также помещается в словарь Terminal, чтобы в дальнейшем знать, что им можно заканчивать фразы.

C#

    1: static void BuildSuffixTree()
    2: {
    3:   // Сначала создаем список слов, которыми начинаются предложения
    4:   List<string> Starts1 = new List<string>();
    5:   Dictionary<string,List<string>> Trans =
    6:     new Dictionary<string,List<string>>();
    7:   foreach(string S1 in S.Split(new char[]{'.','?','!'}))
    8:   {
    9:      string Prev=null;
   10:      foreach(string S2 in S1.Split(new char[]{
   11:        ' ','\n','\r','\t',',',';'}))
   12:      {
   13:         if (S2 . Length < 1)
   14:           continue;
   15:         if (null == Prev)
   16:           {
   17:              Starts1.Add(string.Intern(S2.ToLower()));
   18:              Prev = string.Intern(S2.ToLower());
   19:              continue;
   20:           }
   21:         List<string> Nextsa;
   22:         if (! Trans.TryGetValue(Prev, out Nextsa))
   23:           Trans[Prev] = Nextsa = new List<string>();
   24:         Prev = string.Intern(S2.ToLower());
   25:         Nextsa.Add(Prev);
   26:      }
   27:      if (null != Prev)
   28:        Terminal[Prev] = Prev;
   29:  }
   30:  
   31:  // Создаем простой список слов, с которых можно начинать предложение
   32:  Starts = Starts1.ToArray();
   33:  
   34:  // Выравниваем таблицу переходов
   35:  foreach (string S3 in Trans.Keys)
   36:   NonStart[S3] = Trans [S3].ToArray();
   37: }

Триггеры действий

Аудио-триггер

Аудио-модуль (в файле AudioTrigger.cs) прослушивает все микрофоны. Он использует NAudio в сочетании со структурой делегата и базовой настройкой, заимствованной из статьи Марка Хита (Mark Heath) «.NET Audio Recording». Первое отличие в том, что он регистрирует все аудиоустройства ввода.

C#

    1: static WaveIn[] StartMics()
    2: {
    3:   int NumDevices = WaveIn.DeviceCount;
    4:   WaveIn[] AudIns = new WaveIn[NumDevices];
    5:   for (int waveInDevice = 0; waveInDevice < NumDevices;
    6:      waveInDevice++)
    7:   {
    8:      AudIns[waveInDevice] = new WaveIn();
    9:      AudIns[waveInDevice].DeviceNumber = waveInDevice;
   10:      AudIns[waveInDevice].DataAvailable += waveIn_DataAvailable;
   11:      AudIns[waveInDevice].WaveFormat = new WaveFormat(8000, 1);
   12:      AudIns[waveInDevice].StartRecording();
   13:   }
   14:   return AudIns;
   15: }

Настоящая магия этого триггера кроется в модифицированной версии делегата waveIn_DataAvailable. Вместо записи аудио он делает проверки на наличие любых звуков. Для начала проверяется, не превышают ли амплитуды звуков предопределенного (очень высокого) порога. Далее сэмплы преобразуются в прямоугольные, суммируются, а результат сравнивается с пороговым значением. Если результат превышает пороговое значение, триггер срабатывает. Эти две проверки отлично подходят для реакции на разговоры и звуки, издаваемые при работе за столом или при наборе текста на клавиатуре.

clip_image001[9]

{Текст на иллюстрации:

Will trigger based on RMS threshold — Триггер сработает при пороговом среднеквадратичном значении громкости

Will trigger the amplitude threshold — Триггер сработает при пороговом значении амплитуды

}

C#

    1: static double AudioThresh  = 0.8;
    2: static double AudioThresh2 = 0.09;
    3:  
    4: static void waveIn_DataAvailable(object sender, WaveInEventArgs e)
    5: {
    6:   bool Tr = false;
    7:   double Sum2  = 0;
    8:   int Count = e.BytesRecorded / 2;
    9:   for (int index = 0; index < e.BytesRecorded; index += 2)
   10:   {
   11:      double Tmp = (short)((e.Buffer[index + 1] << 8) |
   12:        e.Buffer[index + 0]);
   13:      Tmp /= 32768.0;
   14:      Sum2 += Tmp*Tmp;
   15:      if (Tmp > AudioThresh)
   16:        Tr = true;
   17:   }
   18:   Sum2 /= Count;
   19:  
   20:   // Если среднеквадратичное значение больше порогового,
   21:   // устанавливаем флаг, указывающий на наличие шума
   22:   if (Tr || Sum2 > AudioThresh2)
   23:     Interlocked.Exchange(ref AudioTrigger, 1);
   24: }

Этот код устанавливает флаг AudioTrigger, который будет обнаружен (и сброшен) в основном цикле следующим блоком кода.

C#

    1: while (0 == Shutdown)
    2: {
    3:     Application.DoEvents();
    4:     Thread.Sleep (20);
    5:  
    6:     //… выполняем какие-то другие операции…
    7:  
    8:     // Проверяем реакцию звуковой системы
    9:     if (0 != AudioTrigger)
   10:     {
   11:         Interlocked.Exchange(ref AudioTrigger, 0);
   12:         …
   13:         ScreenShaker.Screens();
   14:         continue;
   15:     }
   16:     //… другие проверки на простой компьютера…
   17: }

Ожиданиепростоя

Чтобы проверить, делает ли пользователь что-либо, в основном цикле применяется вспомогательный метод LastInputTime(), возвращающий число миллисекунд, в течение которых пользователь ничего не вводил. Цикл ожидает, когда пользователь ничего не делает в течение достаточно длительного времени, и лишь тогда вызывает метод, который случайным образом выбирает действия программы Gremlin.

C#

    1: uint LastTime = Win32.LastInputTime();
    2: if ((uint) Environment.TickCount < IdleTimeTrigger + LastTime)
    3:     continue;

Вспомогательный метод LastInputTime() использует P/Invoke-вызов API-функции GetLastInputInfo(). В вики на сайте Pinvoke.net есть сигнатура этой функции и подходящая структура, которую нужно передать в GetLastInputInfo().

C#

    1: [DllImport("User32.dll")]
    2: static extern bool GetLastInputInfo(ref LASTINPUTINFO LastInfo);
    3:  
    4: [StructLayout(LayoutKind.Sequential)]
    5: struct LASTINPUTINFO
    6: {
    7:   public uint cbSize;
    8:  
    9:   /// <summary>
   10:   /// Число системных тактов
   11:   /// </summary>
   12:   public uint dwTime;
   13: }

Затем эти две структуры обертываются во вспомогательную процедуру LastInputTime(), которая выполняет всю черновую работу, связанную с созданием и инициализацией структуры.

C#

    1: internal static uint LastInputTime()
    2: {
    3:   LASTINPUTINFO lastInput=new LASTINPUTINFO();
    4:   lastInput.cbSize = (uint)Marshal.SizeOf(lastInput);
    5:   GetLastInputInfo(ref lastInput);
    6:   return lastInput.dwTime;
    7: }

Заключение

Изначально предполагается, что гремлины не будут слишком шалить. Но с помощью параметра –aggressive в командной строке гремлины станут гораздо активнее.

Дополнительные материалы и ссылки

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

· Pinvoke.net — сайт в стиле вики, содержащий массу полезных материалов и примеров кода, относящихся к вызову Windows API из .NET.

· .NET Audio Recorder Марка Хита (Mark Heath) — отсюда я украл код, чтобы создать свой аудио-триггер.

· «Possessed PC Pranks for Halloween» Брайена Пика (Brian Peek) — отсюда я позаимствовал базовый код для передачи нажатий клавиш.

· «April Fools’ Day Application» Брайена Пика — отсюда я одолжил код для захвата изображения с экрана и его поворота.

Обавторе

Рэндалл Маас (Randall Maas) пишет микрокод (прошивки) для медицинских устройств и консультирует по вопросам, связанным со встраиваемым ПО. А до этого он много чем занимался…, как и любой другой специалист в индустрии программного обеспечения. Вы можете связаться с ним по адресу randym@acm.org.