Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ jobs:
run: choco install mingw --yes --no-progress

- name: Clone Paho MQTT C
run: git clone --depth 1 https://github.com/eclipse-paho/paho.mqtt.c.git paho.mqtt.c
run: git clone --branch v1.3.15 --depth 1 https://github.com/eclipse-paho/paho.mqtt.c.git paho.mqtt.c

- name: Build Paho MQTT C
run: |
Expand Down
28 changes: 28 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# Build
build/
CMakeFiles/
CMakeCache.txt
cmake_install.cmake
compile_commands.json
Makefile
*.exe
*.dll
*.lib
*.a
*.obj
*.o
*.pdb
*.ilk

# Logs
*.log

# IDE
.vscode/
.idea/
*.swp
*.swo

# AI generated files
.claude/
.cursor/
80 changes: 80 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
# AGENTS.md

This file provides guidance to Codex (Codex.ai/code) when working with code in this repository.

## Project Overview

pc-control is a minimalistic Windows 11 command-line tool that listens to MQTT commands to put the PC to sleep or turn off the monitor for energy saving.

## Build Commands

```bash
# Configure (MinGW installed via choco at default location)
cmake -B build -G "MinGW Makefiles" ^
-DCMAKE_C_COMPILER="C:/ProgramData/mingw64/mingw64/bin/gcc.exe" ^
-DCMAKE_MAKE_PROGRAM="C:/ProgramData/mingw64/mingw64/bin/mingw32-make.exe"

# Build
mingw32-make -C build

# Clean
mingw32-make -C build clean
```

Requires: CMake 3.16+, MinGW-w64 (via `choco install mingw`), Paho MQTT C library built and installed at `../paho.mqtt.c/install/`.

See README.md for full setup instructions including Paho MQTT C build steps.

## Usage

```bash
# Console version (shows output)
pc-control.exe [--hide] <broker_ip> <username> <password> [port] [hostname]

# Hidden version (no window at all)
pc-control-hidden.exe <broker_ip> <username> <password> [port] [hostname]
```

- `port` defaults to 1883
- `hostname` defaults to system hostname (used in topics and client ID)

MQTT topics (hostname-based for multi-PC support):
- `pc-control/<hostname>/sleep` - Put PC to sleep (command payload: `1`, `true`, `on`, or `yes`; retained messages are ignored)
- `pc-control/<hostname>/monitor-off` - Turn off monitor (command payload: `1`, `true`, `on`, or `yes`; retained messages are ignored)
- `pc-control/<hostname>/status` - `online`/`offline` (retained, LWT)
- `pc-control/<hostname>/version` - Version string (retained)

Logs are written to `pc-control.log` in the working directory.

## Architecture

Two executables built from `src/main.c`:
- `pc-control.exe` - Console subsystem, shows terminal output
- `pc-control-hidden.exe` - GUI subsystem with `mainCRTStartup` entry point, no window

Both use the same source code. The hidden version is built with `-mwindows -Wl,-e,mainCRTStartup` flags, which creates a GUI app that can still use regular `main()` function.

Features:
- MQTT connection and message handling via Paho MQTT C
- LWT (Last Will and Testament) for offline detection
- Birth messages: publishes `online` status and version on connect
- Automatic reconnection with exponential backoff (1s to 30s)
- Hostname sanitization (lowercase, special chars handled)
- Windows API calls: `SetSuspendState()` for sleep, `SendMessageTimeout(HWND_BROADCAST, WM_SYSCOMMAND, SC_MONITORPOWER, 2, ...)` for monitor off
- Simple file-based logging with timestamps

## Version

Update `VERSION` and version number components in `src/version.h` when releasing.

## Key Windows APIs

- `SetSuspendState(FALSE, FALSE, FALSE)` - Sleep mode (requires linking `powrprof.lib`)
- `SendMessageTimeout` with `SC_MONITORPOWER` - Monitor power control with timeout

## Build Notes

The hidden version uses a technique from [Switchy](https://github.com/erryox/Switchy):
- Link with `-mwindows` (GUI subsystem)
- Set entry point to `mainCRTStartup` via `-Wl,-e,mainCRTStartup`
- This allows using regular `main()` while having no console window
6 changes: 3 additions & 3 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
cmake_minimum_required(VERSION 3.16)
project(pc-control C)
project(pc-control C RC)

set(CMAKE_C_STANDARD 11)

Expand All @@ -19,12 +19,12 @@ set(COMMON_LIBS
)

# Console version (for debugging/interactive use)
add_executable(pc-control src/main.c)
add_executable(pc-control src/main.c src/version.rc)
target_link_libraries(pc-control ${COMMON_LIBS})

# Hidden version (GUI subsystem, no console window at all)
# Uses mainCRTStartup entry point to keep regular main() function
add_executable(pc-control-hidden src/main.c)
add_executable(pc-control-hidden src/main.c src/version.rc)
target_compile_definitions(pc-control-hidden PRIVATE HIDDEN_BUILD)
target_link_libraries(pc-control-hidden ${COMMON_LIBS})
if(MINGW)
Expand Down
33 changes: 27 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ Minimalistic Windows 11 command-line tool for remote PC power management via MQT

## Dependencies

- [Paho MQTT C Client](https://github.com/eclipse/paho.mqtt.c)
- [Paho MQTT C Client](https://github.com/eclipse-paho/paho.mqtt.c)
- CMake 3.16+
- MinGW-w64

Expand All @@ -29,7 +29,7 @@ choco install cmake mingw -y

```bash
# Clone the library (next to pc-control directory)
git clone https://github.com/eclipse/paho.mqtt.c.git ../paho.mqtt.c
git clone https://github.com/eclipse-paho/paho.mqtt.c.git ../paho.mqtt.c

# Configure with MinGW (adjust paths if MinGW is installed elsewhere)
cmake -B ../paho.mqtt.c/build -S ../paho.mqtt.c -G "MinGW Makefiles" ^
Expand Down Expand Up @@ -69,7 +69,7 @@ The build produces two standalone executables in `build/` (no DLLs required):
## Usage

```bash
pc-control.exe <broker_ip> <username> <password> [port] [hostname]
pc-control.exe [--hide] <broker_ip> <username> <password> [port] [hostname]
```

| Argument | Required | Default | Description |
Expand All @@ -80,6 +80,8 @@ pc-control.exe <broker_ip> <username> <password> [port] [hostname]
| port | No | 1883 | MQTT broker port |
| hostname | No | System hostname | Device name used in topics |

`--hide` hides the console window after startup. For autostart, prefer `pc-control-hidden.exe`.

Examples:
```bash
# Minimal (uses default port 1883 and system hostname)
Expand Down Expand Up @@ -117,11 +119,13 @@ Topics include the hostname to support multiple PCs:

| Topic | Type | Description |
|-------|------|-------------|
| `pc-control/<hostname>/sleep` | Command | Put PC to sleep (send any message) |
| `pc-control/<hostname>/monitor-off` | Command | Turn off monitor (send any message) |
| `pc-control/<hostname>/sleep` | Command | Put PC to sleep (send `1`, `true`, `on`, or `yes`) |
| `pc-control/<hostname>/monitor-off` | Command | Turn off monitor (send `1`, `true`, `on`, or `yes`) |
| `pc-control/<hostname>/status` | Status | `online` / `offline` (retained) |
| `pc-control/<hostname>/version` | Info | Version string, e.g. `1.0.0` (retained) |

Retained messages on command topics are ignored to avoid accidental actions after reconnect.

### Status tracking (LWT)

The client publishes `online` to the status topic on connect, and the broker automatically publishes `offline` via Last Will and Testament when the client disconnects unexpectedly (crash, network failure, PC sleep).
Expand Down Expand Up @@ -165,6 +169,23 @@ Commands are logged to `pc-control.log` in the working directory with timestamps
[2026-02-01 16:05:01] MONITOR_OFF command received - turning off monitor
[2026-02-01 16:10:15] SLEEP command received - entering sleep mode
```
## CI/CD & Releasing

The project uses GitHub Actions to build and release automatically.

**On every PR to `main`:** both executables are built and uploaded as artifacts for testing.

**To release a new version:**

1. Update `VERSION` in `src/version.h`
2. Work on a feature branch, open a PR, and merge to `main`
3. Tag the merge commit and push the tag:
```bash
git tag v1.2.0
git push origin v1.2.0
```
4. GitHub Actions builds and creates a [Release](../../releases) with both `.exe` files attached

## Authors

* Original creator: [Artur Brynka](https://github.com/FireHawken)
Expand All @@ -175,4 +196,4 @@ Commands are logged to `pc-control.log` in the working directory with timestamps
|---------|------------|---------------------------------------------------------------------------------------------------------------|
| 1.1.0 | 02.03.2026 | Add new, separate "invisible" executable and statically link paho-mqtt, so only 1 file is required for distribution |
| 1.0.1 | 02.02.2026 | Add logging to file, improved Home Assistant support, status topic (`online`/`offline`) via LWT, monitor off command. |
| 1.0.0 | 01.02.2026 | Initial release |
| 1.0.0 | 01.02.2026 | Initial release |
34 changes: 34 additions & 0 deletions REFACTORING_PLAN.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Refactoring Plan

## Goal

Improve stability and reliability of `pc-control` while keeping the tool small and predictable.

## Work Items

1. Move command execution out of the MQTT callback and into the main loop.
- The callback should only record pending work and return quickly.
- The main loop should execute Windows power actions.
2. Replace shared `volatile` state with thread-safe synchronization.
- Use Windows `Interlocked*` APIs or C11 atomics for flags touched by callbacks.
3. Validate command payloads and ignore retained command messages.
- Avoid accidental sleep/monitor-off after subscribing to retained command topics.
4. Add timeouts and error logging around Windows power APIs.
- Use `SendMessageTimeout` for monitor-off.
- Log `SetSuspendState` failures with `GetLastError`.
5. Make status publishing more deterministic.
- Wait for retained status/version delivery where it matters, especially shutdown.
6. Improve process and logging robustness.
- Add a single-instance guard per hostname.
- Move logs to a predictable user-writable location and consider rotation.
7. Tighten input validation and build checks.
- Validate broker address/port and check `snprintf` truncation.
- Enable compiler warnings in CMake/CI.

## Progress

- Done: item 1, command execution now happens in the main loop.
- Done: item 2, shared runtime flags now use Windows `Interlocked*` helpers.
- Done: item 3, command messages now require explicit payloads and retained command messages are ignored.
- Done: item 4, Windows power API calls now have timeout/failure logging.
- Current: item 5, make status publishing more deterministic.
Loading
Loading