-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 5e28095
Showing
2 changed files
with
307 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
LidLock | ||
====== | ||
Lock Windows laptop when lid is closed | ||
|
||
|
||
# Introduction | ||
LidLock is a simple app written in C for Windows to automatically lock the screen when the lid is closed. This resembles the same behavior as macOS, when one needs the laptop not to sleep but just to be locked when closing the lid. | ||
|
||
The app also works with clamshell mode and external monitors. It will ignore locking if lid is open and external monitors are connected, and re-locks if the lid is closed and external monitors are disconnected. | ||
|
||
# Usage | ||
LidLock is a single portable executable file. Simply double-click the executable and it silently runs as a daemon in the background without any windows, prompts or icons. It listens to relevant events and does not consume CPU when waiting. | ||
|
||
If you want to stop LidLock, you need to use Task Manager to stop the process. | ||
|
||
To make it run at startup, create a shortcut of `lidlock.exe` and copy the shortcut to the startup folder (can be opened by executing `shell:startup` in Win+R). | ||
|
||
# Download and Compilation | ||
The pre-compiled binaries can be found at [Releases][release] page. | ||
|
||
The binary can be cross-compiled using MinGW-w64 as follows: | ||
|
||
```bash | ||
x86_64-w64-mingw32-gcc lidlock.c -o lidlock.exe -lole32 -lksguid -static -O2 -g0 -mwindows -Wall | ||
``` | ||
|
||
For debugging, add a flag `-DDEBUG` during compiling and run the binary with a single argument which is the log file for tracking outputs. | ||
|
||
# Credits | ||
This app is based on [`laplock`][laplock] by @dechamps and rewritten in C. The extra feature is the support of external monitors. | ||
|
||
The license is the same as `laplock` (GPLv3): | ||
|
||
[![License: GPL v3](https://img.shields.io/badge/License-GPL%20v3-blue.svg)](https://www.gnu.org/licenses/gpl-3.0) | ||
|
||
[release]: https://github.com/linusyang92/lidlock/releases | ||
[laplock]: https://github.com/dechamps/laplock |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,270 @@ | ||
// Copyright 2018 (C) Linus Yang | ||
// Copyright 2011 (C) Etienne Dechamps (e-t172) <[email protected]> | ||
/* | ||
* This program is free software: you can redistribute it and/or modify | ||
* it under the terms of the GNU General Public License as published by | ||
* the Free Software Foundation, either version 3 of the License, or | ||
* (at your option) any later version. | ||
* | ||
* This program is distributed in the hope that it will be useful, | ||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
* GNU General Public License for more details. | ||
* | ||
* You should have received a copy of the GNU General Public License | ||
* along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
*/ | ||
|
||
//x86_64-w64-mingw32-gcc lidlock.c -o lidlock.exe -lole32 -lksguid -static -O2 -g0 -mwindows -Wall | ||
|
||
#include <stdio.h> | ||
#include <time.h> | ||
#include <windows.h> | ||
#include <dbt.h> | ||
|
||
#ifdef DEBUG | ||
static FILE *logfile = NULL; | ||
#define TIME_FORMAT "%Y-%m-%d %H:%M:%S" | ||
#define USE_LOGFILE(ident) \ | ||
do { \ | ||
if (ident != NULL) { logfile = fopen(ident, "w+"); } } \ | ||
while (0) | ||
#define CLOSE_LOGFILE() \ | ||
do { \ | ||
if (logfile != NULL) { fclose(logfile); } } \ | ||
while (0) | ||
#define LOG(format, ...) \ | ||
do { \ | ||
if (logfile != NULL) { \ | ||
time_t now = time(NULL); \ | ||
char timestr[20]; \ | ||
strftime(timestr, 20, TIME_FORMAT, localtime(&now)); \ | ||
fprintf(logfile, " [%s] " format "\n", timestr, ## __VA_ARGS__); \ | ||
fflush(logfile); } \ | ||
} \ | ||
while (0) | ||
#define GUID_LOG(fmt, guid) \ | ||
do { \ | ||
wchar_t guid_str[64] = {0}; \ | ||
char str[128] = {0}; \ | ||
StringFromGUID2(&guid, (LPOLESTR)guid_str, sizeof(guid_str)); \ | ||
WideCharToMultiByte(CP_ACP, 0, guid_str, -1, str, sizeof(str), NULL, NULL); \ | ||
LOG(fmt, str); \ | ||
} while(0) | ||
|
||
static void systemError(const char *s) | ||
{ | ||
char *msg = NULL; | ||
DWORD err = GetLastError(); | ||
FormatMessage( | ||
FORMAT_MESSAGE_ALLOCATE_BUFFER | | ||
FORMAT_MESSAGE_FROM_SYSTEM | | ||
FORMAT_MESSAGE_IGNORE_INSERTS, | ||
NULL, err, | ||
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), | ||
(LPTSTR)&msg, 0, NULL); | ||
if (msg != NULL) { | ||
// Remove trailing newline character | ||
ssize_t len = strlen(msg) - 1; | ||
if (len >= 0 && msg[len] == '\n') { | ||
msg[len] = '\0'; | ||
} | ||
LOG("%s: [%ld] %s", s, err, msg); | ||
LocalFree(msg); | ||
} | ||
} | ||
#else | ||
#define LOG(format, ...) | ||
#define GUID_LOG(fmt, guid) | ||
#define USE_LOGFILE(ident) | ||
#define CLOSE_LOGFILE() | ||
#define systemError(s) | ||
#endif | ||
|
||
#define GUID_EQ(a, b) IsEqualGUID(&a, &b) | ||
#define SINGLETON_IDENTIFIER "Global\\{3DA16D16-5F02-4CFD-8C43-11C31127889D}" | ||
#define APP_NAME "lidlock" | ||
static const GUID GUID_DEVINTERFACE_MONITOR = { | ||
0xe6f07b5f, 0xee97, 0x4a90, | ||
{0xb0, 0x76, 0x33, 0xf5, 0x7b, 0xf4, 0xea, 0xa7} | ||
}; | ||
|
||
static int displayCount() | ||
{ | ||
int ret = 0; | ||
DWORD deviceNum = 0; | ||
DISPLAY_DEVICE dd; | ||
dd.cb = sizeof(DISPLAY_DEVICE); | ||
while (EnumDisplayDevices(NULL, deviceNum, &dd, 0)){ | ||
DISPLAY_DEVICE ddmon = {0}; | ||
ddmon.cb = sizeof(DISPLAY_DEVICE); | ||
DWORD monitorNum = 0; | ||
while (EnumDisplayDevices(dd.DeviceName, monitorNum, &ddmon, 0)) { | ||
monitorNum++; | ||
ret++; | ||
} | ||
deviceNum++; | ||
} | ||
return ret; | ||
} | ||
|
||
static HWND createWindow(HINSTANCE instance) | ||
{ | ||
LOG("Creating window"); | ||
HWND hWnd = CreateWindow( | ||
APP_NAME, | ||
NULL, | ||
0, | ||
CW_USEDEFAULT, | ||
CW_USEDEFAULT, | ||
CW_USEDEFAULT, | ||
CW_USEDEFAULT, | ||
HWND_MESSAGE, | ||
NULL, | ||
instance, | ||
NULL | ||
); | ||
|
||
if (hWnd == NULL) { | ||
systemError("creating window"); | ||
} | ||
|
||
return hWnd; | ||
} | ||
|
||
static void registerNotification(HWND window) | ||
{ | ||
GUID_LOG("Registering GUID_MONITOR_POWER_ON (GUID: %s)", GUID_MONITOR_POWER_ON); | ||
if (!RegisterPowerSettingNotification(window, | ||
&GUID_MONITOR_POWER_ON, DEVICE_NOTIFY_WINDOW_HANDLE)) { | ||
systemError("cannot register GUID_MONITOR_POWER_ON power setting notification"); | ||
} | ||
GUID_LOG("Registering GUID_LIDSWITCH_STATE_CHANGE (GUID: %s)", GUID_LIDSWITCH_STATE_CHANGE); | ||
if (!RegisterPowerSettingNotification(window, | ||
&GUID_LIDSWITCH_STATE_CHANGE, DEVICE_NOTIFY_WINDOW_HANDLE)) { | ||
systemError("cannot register GUID_LIDSWITCH_STATE_CHANGE power setting notification"); | ||
} | ||
GUID_LOG("Registering GUID_DEVINTERFACE_MONITOR (GUID: %s)", GUID_DEVINTERFACE_MONITOR); | ||
DEV_BROADCAST_DEVICEINTERFACE filter; | ||
memset(&filter, 0 ,sizeof(filter)); | ||
filter.dbcc_devicetype = DBT_DEVTYP_DEVICEINTERFACE; | ||
filter.dbcc_size = sizeof(DEV_BROADCAST_DEVICEINTERFACE); | ||
filter.dbcc_classguid = GUID_DEVINTERFACE_MONITOR; | ||
if (!RegisterDeviceNotification(window, | ||
&filter, DEVICE_NOTIFY_WINDOW_HANDLE)) { | ||
systemError("cannot register device notification"); | ||
} | ||
} | ||
|
||
static WPARAM messageLoop() | ||
{ | ||
for (;;) | ||
{ | ||
LOG("Awaiting next window message"); | ||
MSG message; | ||
BOOL result = GetMessage(&message, NULL, 0, 0); | ||
if (result == -1) | ||
systemError("getting window message"); | ||
if (result == 0) | ||
return message.wParam; | ||
LOG("Dispatching message"); | ||
DispatchMessage(&message); | ||
} | ||
} | ||
|
||
static LRESULT CALLBACK windowProcedure(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) | ||
{ | ||
int isPowerChanged = 0; | ||
if (uMsg == WM_POWERBROADCAST && wParam == PBT_POWERSETTINGCHANGE) { | ||
LOG("Power status changed"); | ||
isPowerChanged = 1; | ||
} else if (uMsg == WM_DEVICECHANGE && wParam == DBT_DEVICEREMOVECOMPLETE) { | ||
LOG("Monitors removed"); | ||
} else { | ||
LOG("Window received irrelevant message"); | ||
return DefWindowProc(hWnd, uMsg, wParam, lParam); | ||
} | ||
|
||
if (GetSystemMetrics(SM_REMOTESESSION)) { | ||
LOG("Ignoring window message because session is currently remote"); | ||
return DefWindowProc(hWnd, uMsg, wParam, lParam); | ||
} | ||
|
||
if (isPowerChanged) { | ||
POWERBROADCAST_SETTING* setting = (POWERBROADCAST_SETTING *)lParam; | ||
GUID_LOG("Received POWERBROADCAST_SETTING %s", setting->PowerSetting); | ||
if (!GUID_EQ(setting->PowerSetting, GUID_MONITOR_POWER_ON) && | ||
!GUID_EQ(setting->PowerSetting, GUID_LIDSWITCH_STATE_CHANGE)) { | ||
LOG("Received irrelevant POWERBROADCAST_SETTING"); | ||
return DefWindowProc(hWnd, uMsg, wParam, lParam); | ||
} | ||
|
||
DWORD* state = (DWORD *)&setting->Data; | ||
LOG("POWERBROADCAST_SETTING state: %d", (int)*state); | ||
if (*state != 0) { | ||
LOG("Irrelevant POWERBROADCAST_SETTING state"); | ||
return DefWindowProc(hWnd, uMsg, wParam, lParam); | ||
} | ||
} | ||
|
||
if (displayCount() == 0) { | ||
LOG("Locking"); | ||
if (LockWorkStation() == 0) | ||
systemError("locking workstation"); | ||
else | ||
LOG("Locked"); | ||
} else { | ||
LOG("Still have active monitors, ignored locking"); | ||
} | ||
|
||
return 0; | ||
} | ||
|
||
static void registerWindowClass(HINSTANCE instance) | ||
{ | ||
LOG("Registering window class"); | ||
WNDCLASSEX windowClass = { 0 }; | ||
|
||
windowClass.cbSize = sizeof(WNDCLASSEX); | ||
windowClass.lpfnWndProc = &windowProcedure; | ||
windowClass.hInstance = instance; | ||
windowClass.lpszClassName = APP_NAME; | ||
|
||
if (RegisterClassEx(&windowClass) == 0) { | ||
systemError("registering window class"); | ||
} | ||
} | ||
|
||
int CALLBACK WinMain(HINSTANCE instance, HINSTANCE prevInstance, LPSTR commandLine, int cmdShow) | ||
{ | ||
// Detect if an instance is already running | ||
WPARAM ret = 0; | ||
HANDLE mutex = CreateMutex(NULL, FALSE, TEXT(SINGLETON_IDENTIFIER)); | ||
DWORD err = GetLastError(); | ||
if (err == ERROR_ALREADY_EXISTS) { | ||
LOG("instance already exists"); | ||
goto cleanup; | ||
} | ||
|
||
// Initialize log file | ||
if (*commandLine != 0) { | ||
USE_LOGFILE(commandLine); | ||
} | ||
|
||
// Register notifications | ||
registerWindowClass(instance); | ||
HWND window = createWindow(instance); | ||
registerNotification(window); | ||
ret = messageLoop(); | ||
|
||
// Finished | ||
CLOSE_LOGFILE(); | ||
|
||
cleanup: | ||
if (mutex) { | ||
CloseHandle(mutex); | ||
mutex = NULL; | ||
} | ||
|
||
return ret; | ||
} |