|
| 1 | +// AdaLight.cpp |
| 2 | +// |
| 3 | +// This is a port of the Processing (Java) driver for the AdaLight project to a C++ Windows Application. |
| 4 | +// This version runs without any UI, so you might want to use the preview UI in the original Processing |
| 5 | +// application first to figure out your settings and then copy those variables to settings.* and recompile. |
| 6 | +// |
| 7 | +// AdaLight guide: https://learn.adafruit.com/adalight-diy-ambient-tv-lighting/pieces?view=all#download-and-install |
| 8 | +// Serial port programming sample: https://code.msdn.microsoft.com/windowsdesktop/Serial-Port-Sample-e8accf30/sourcecode?fileId=67164&pathId=1394200469 |
| 9 | +// DirectX 11 C++ references: https://msdn.microsoft.com/en-us/library/windows/desktop/ff476082(v=vs.85).aspx |
| 10 | + |
| 11 | +#pragma region ORIGINAL ADALIGHT.PDE COMMENTS |
| 12 | + |
| 13 | +// "Adalight" is a do-it-yourself facsimile of the Philips Ambilight concept |
| 14 | +// for desktop computers and home theater PCs. This is the host PC-side code |
| 15 | +// written in Processing, intended for use with a USB-connected Arduino |
| 16 | +// microcontroller running the accompanying LED streaming code. Requires one |
| 17 | +// or more strands of Digital RGB LED Pixels (Adafruit product ID #322, |
| 18 | +// specifically the newer WS2801-based type, strand of 25) and a 5 Volt power |
| 19 | +// supply (such as Adafruit #276). You may need to adapt the code and the |
| 20 | +// hardware arrangement for your specific display configuration. |
| 21 | +// Screen capture adapted from code by Cedrik Kiefer (processing.org forum) |
| 22 | + |
| 23 | +// -------------------------------------------------------------------- |
| 24 | +// This file is part of Adalight. |
| 25 | + |
| 26 | +// Adalight is free software: you can redistribute it and/or modify |
| 27 | +// it under the terms of the GNU Lesser General Public License as |
| 28 | +// published by the Free Software Foundation, either version 3 of |
| 29 | +// the License, or (at your option) any later version. |
| 30 | + |
| 31 | +// Adalight is distributed in the hope that it will be useful, |
| 32 | +// but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 33 | +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 34 | +// GNU Lesser General Public License for more details. |
| 35 | + |
| 36 | +// You should have received a copy of the GNU Lesser General Public |
| 37 | +// License along with Adalight. If not, see |
| 38 | +// <http://www.gnu.org/licenses/>. |
| 39 | +// -------------------------------------------------------------------- |
| 40 | + |
| 41 | +#pragma endregion |
| 42 | + |
| 43 | +#include "stdafx.h" |
| 44 | + |
| 45 | +#include <cmath> |
| 46 | +#include <string> |
| 47 | +#include <sstream> |
| 48 | + |
| 49 | +#include "settings.h" |
| 50 | +#include "gamma_correction.h" |
| 51 | +#include "serial_buffer.h" |
| 52 | +#include "screen_samples.h" |
| 53 | +#include "serial_port.h" |
| 54 | + |
| 55 | +// TODO: Put this in some sort of persistence? Create configuration UI? Parse the settings from the command line? |
| 56 | +static const settings parameters; |
| 57 | + |
| 58 | +static serial_buffer serial(parameters); |
| 59 | +static gamma_correction gamma; |
| 60 | +static screen_samples samples(parameters, gamma); |
| 61 | +static serial_port port(parameters); |
| 62 | + |
| 63 | +// Reset the LED strip. |
| 64 | +static void ResetLEDs() |
| 65 | +{ |
| 66 | + serial.clear(); |
| 67 | + port.send(serial); |
| 68 | +} |
| 69 | + |
| 70 | +// Update the LED strip. |
| 71 | +static void UpdateLEDs() |
| 72 | +{ |
| 73 | + samples.take_samples(serial); |
| 74 | + port.send(serial); |
| 75 | +} |
| 76 | + |
| 77 | +// Hidden window class name |
| 78 | +static PCWSTR s_windowClassName = L"AdaLightListener"; |
| 79 | + |
| 80 | +static bool s_timerSet = false; |
| 81 | + |
| 82 | +// Start the timer if it's not running. |
| 83 | +static void StartTimer(HWND hwnd) |
| 84 | +{ |
| 85 | + if (!s_timerSet) |
| 86 | + { |
| 87 | + SetTimer(hwnd, 0, parameters.delay, nullptr); |
| 88 | + s_timerSet = true; |
| 89 | + } |
| 90 | +} |
| 91 | + |
| 92 | +// Stop the timer if it is running. |
| 93 | +static void StopTimer(HWND hwnd) |
| 94 | +{ |
| 95 | + if (s_timerSet) |
| 96 | + { |
| 97 | + KillTimer(hwnd, 0); |
| 98 | + s_timerSet = false; |
| 99 | + |
| 100 | + ResetLEDs(); |
| 101 | + } |
| 102 | +} |
| 103 | + |
| 104 | +static bool s_connectedToConsole = !GetSystemMetrics(SM_REMOTESESSION); |
| 105 | + |
| 106 | +// Handle attaching and reattaching to the console. |
| 107 | +static void AttachToConsole(HWND hwnd) |
| 108 | +{ |
| 109 | + if (s_connectedToConsole) |
| 110 | + { |
| 111 | + port.open(); |
| 112 | + samples.create_resources(); |
| 113 | + StartTimer(hwnd); |
| 114 | + } |
| 115 | +} |
| 116 | + |
| 117 | +// Handle detaching from the console. |
| 118 | +static void DetachFromConsole(HWND hwnd) |
| 119 | +{ |
| 120 | + if (s_connectedToConsole) |
| 121 | + { |
| 122 | + StopTimer(hwnd); |
| 123 | + samples.free_resources(); |
| 124 | + port.close(); |
| 125 | + } |
| 126 | +} |
| 127 | + |
| 128 | +// Process window messages to the hidden window. |
| 129 | +static LRESULT CALLBACK HiddenWindowProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) |
| 130 | +{ |
| 131 | + switch (message) |
| 132 | + { |
| 133 | + case WM_CREATE: |
| 134 | + WTSRegisterSessionNotification(hwnd, NOTIFY_FOR_THIS_SESSION); |
| 135 | + break; |
| 136 | + |
| 137 | + case WM_DESTROY: |
| 138 | + StopTimer(hwnd); |
| 139 | + WTSUnRegisterSessionNotification(hwnd); |
| 140 | + PostQuitMessage(0); |
| 141 | + break; |
| 142 | + |
| 143 | + case WM_WTSSESSION_CHANGE: |
| 144 | + switch (wParam) |
| 145 | + { |
| 146 | + case WTS_CONSOLE_CONNECT: |
| 147 | + s_connectedToConsole = true; |
| 148 | + AttachToConsole(hwnd); |
| 149 | + break; |
| 150 | + |
| 151 | + case WTS_CONSOLE_DISCONNECT: |
| 152 | + DetachFromConsole(hwnd); |
| 153 | + s_connectedToConsole = false; |
| 154 | + break; |
| 155 | + |
| 156 | + case WTS_SESSION_LOCK: |
| 157 | + DetachFromConsole(hwnd); |
| 158 | + break; |
| 159 | + |
| 160 | + case WTS_SESSION_UNLOCK: |
| 161 | + AttachToConsole(hwnd); |
| 162 | + break; |
| 163 | + |
| 164 | + default: |
| 165 | + break; |
| 166 | + } |
| 167 | + |
| 168 | + break; |
| 169 | + |
| 170 | + case WM_DISPLAYCHANGE: |
| 171 | + if (s_connectedToConsole) |
| 172 | + { |
| 173 | + samples.free_resources(); |
| 174 | + samples.create_resources(); |
| 175 | + } |
| 176 | + break; |
| 177 | + |
| 178 | + case WM_TIMER: |
| 179 | + if (samples.empty()) |
| 180 | + { |
| 181 | + samples.create_resources(); |
| 182 | + } |
| 183 | + |
| 184 | + UpdateLEDs(); |
| 185 | + break; |
| 186 | + |
| 187 | + default: |
| 188 | + return DefWindowProcW(hwnd, message, wParam, lParam); |
| 189 | + } |
| 190 | + |
| 191 | + return 0; |
| 192 | +} |
| 193 | + |
| 194 | +// Show a message box for any errors. |
| 195 | +static void DisplayLastError() |
| 196 | +{ |
| 197 | + DWORD errorCode = GetLastError(); |
| 198 | + PWSTR errorString = nullptr; |
| 199 | + |
| 200 | + FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, nullptr, errorCode, 0, reinterpret_cast<PWSTR>(&errorString), 0, nullptr); |
| 201 | + MessageBoxW(HWND_DESKTOP, errorString, nullptr, MB_ICONERROR); |
| 202 | + LocalFree(reinterpret_cast<HLOCAL>(errorString)); |
| 203 | +} |
| 204 | + |
| 205 | +// Run the program. |
| 206 | +int WINAPI wWinMain(HINSTANCE hinstExe, HINSTANCE /*hinstPrev*/, PWSTR /*wzCmdLine*/, int /*nCmdShow*/) |
| 207 | +{ |
| 208 | + // TODO: Run in an interactive service? Tough to deal with user switching. Need to handle WM_DISPLAYCHANGE with a hidden top-level window. |
| 209 | + // Windows Service C++ sample: https://www.codeproject.com/articles/499465/simple-windows-service-in-cplusplus |
| 210 | + |
| 211 | + // Create the hidden window. |
| 212 | + WNDCLASSEXW windowClass = { |
| 213 | + sizeof(windowClass), // cbSize |
| 214 | + 0, // style |
| 215 | + HiddenWindowProc, // lpfnWndProc |
| 216 | + 0, // cbClsExtra |
| 217 | + 0, // cbWndExtra |
| 218 | + hinstExe, // hInstance |
| 219 | + NULL, // hIcon |
| 220 | + NULL, // hCursor |
| 221 | + NULL, // hbrBackground |
| 222 | + nullptr, // lpszMenuName |
| 223 | + s_windowClassName, // lpszClassName |
| 224 | + NULL // hIconSm |
| 225 | + }; |
| 226 | + |
| 227 | + if (!RegisterClassExW(&windowClass)) |
| 228 | + { |
| 229 | + DisplayLastError(); |
| 230 | + return 1; |
| 231 | + } |
| 232 | + |
| 233 | + HWND hwnd = CreateWindowExW(0, s_windowClassName, nullptr, 0, 0, 0, 0, 0, HWND_DESKTOP, NULL, hinstExe, nullptr); |
| 234 | + |
| 235 | + // Start processing messages/timers. |
| 236 | + AttachToConsole(hwnd); |
| 237 | + |
| 238 | + BOOL result; |
| 239 | + MSG msg; |
| 240 | + |
| 241 | + while (result = GetMessageW(&msg, NULL, 0, 0)) |
| 242 | + { |
| 243 | + if (-1 == result) |
| 244 | + { |
| 245 | + DisplayLastError(); |
| 246 | + return 2; |
| 247 | + } |
| 248 | + |
| 249 | + TranslateMessage(&msg); |
| 250 | + DispatchMessageW(&msg); |
| 251 | + } |
| 252 | + |
| 253 | + return msg.wParam; |
| 254 | +} |
0 commit comments