A distributed system that lets you control your external monitor's hardware settings (brightness, volume, contrast, input source) wirelessly from your Android phone — using your Mac as a bridge that communicates with the monitor via DDC/CI protocol over HDMI/DisplayPort.
Android Phone (Flutter Client)
|
| HTTP Request over Wi-Fi
v
Mac (Dart Server using shelf package)
|
| Executes m1ddc command
v
External Monitor (DDC/CI over HDMI Pin 15 & 16)
The Mac runs a lightweight Dart HTTP server. The Android app sends commands like /luminance?value=70 over the local network. The server receives them and executes m1ddc which talks to the monitor through the I2C bus on HDMI pins 15 and 16.
Monitor Remote/
├── server/ # Dart backend (runs on Mac)
│ ├── bin/
│ │ └── server.dart # HTTP server with m1ddc integration
│ └── pubspec.yaml
├── monitor_remote/ # Flutter Android client
│ └── lib/
│ ├── main.dart
│ ├── home/
│ │ └── home.dart # UI with sliders and buttons
│ └── server/
│ └── api.dart # HTTP client to talk to Mac server
└── doc/ # Personal research notes (not included in repo)
- Mac with Apple Silicon (M1/M2/M3/M4)
- External monitor connected via HDMI/DisplayPort/USB-C
m1ddcinstalled:brew install m1ddc- Mac and Android phone on the same Wi-Fi network
ipconfig getifaddr en0
Open monitor_remote/lib/server/api.dart and replace the IP address with yours.
cd server
dart run bin/server.dart
cd monitor_remote
flutter run
- Brightness control (slider, 0-100%)
- Volume control (slider, 0-100%)
- Contrast control (slider, 0-100%)
- Input source switching (HDMI 1, HDMI 2)
The monitor's hardware settings are controlled through the DDC/CI (Display Data Channel / Command Interface) protocol. This protocol runs over the I2C bus using two dedicated pins in the HDMI cable:
- Pin 15 (SCL) — Clock signal
- Pin 16 (SDA) — Data signal
The Mac acts as the I2C Master and the monitor is the I2C Slave. When you move a slider on your phone, the flow is:
Phone slider → HTTP request → Mac server → m1ddc command → I2C bus → Monitor adjusts
I evaluated three approaches for this project:
-
Method Channel Approach — Flutter UI talks to native Swift/IOKit code directly. Requires heavy low-level platform code.
-
Backend API Approach (chosen) — Mac runs a Dart HTTP server, Android phone sends commands over Wi-Fi. Creates a distributed client-server system.
-
Process.run Approach — Local macOS Flutter app executes terminal commands. Requires disabling the App Sandbox.
I chose the Backend API approach because it allows wireless control from any device on the network and demonstrates client-server architecture, HTTP API design, and hardware communication — all in one project.
- Client: Flutter (Dart), http package
- Server: Dart, shelf package
- Hardware Communication: m1ddc (DDC/CI over I2C)
Key concepts I researched and applied in this project:
- What m1ddc is and how it works
- HDMI pin layout and what each pin carries
- I2C protocol (Master/Slave communication)
- DDC/CI protocol and how it layers on top of I2C
- The protocol stack: DDC/CI → I2C → Physical Pins
- Comparison of all three architectural approaches