Xi Control is a lightweight tray utility for Xiaomi / Redmi (Redmibook) laptops,
built primarily for the Xiaomi Book Pro 14 (2026) but not limited to it.
Battery care (charge limit) with automatic re-arm after sleep, five performance
modes including the hidden Eco mode, OSD overlays, Mi button gestures, special
key fixes (Fn-Lock OSD, microphone mute, keyboard backlight levels) and an
owl mode that keeps the system awake while the lid is closed on AC power.
Everything is driven through the firmware's stock WMI interface - no WinRing0,
no third-party drivers. Requires administrator rights (a firmware WMI
requirement); the app elevates itself via UAC on launch.
A lightweight tray utility for Xiaomi / Redmi (Redmibook) laptops β first and foremost
for the Xiaomi Book Pro 14 (2026), on which it was developed and tested, but not limited
to it. Battery charge protection, performance modes, OSD and "revival" of the vendor keys.
All hardware control goes through the firmware's stock WMI interface (MiCommonInterface,
ODM Bitland "MIFS") β the same channel the official Xiaomi PC Manager uses.
No WinRing0, no third-party drivers and no direct EC access.
Quick settings panel (hold the Mi button): five performance modes, charge limit,
"travel charge" (a one-off charge to 100%), touchpad and touchscreen on/off,
auto refresh rate and "owl mode" (stay awake).
Monitor window: live graphs of power draw (W), CPU, GPU and RAM usage.
In full view β live graphs: power draw (W), CPU, GPU (clock and watts under the percentage),
RAM, hotspot temperature (hot zone in cherry red) and the connected adapter's wattage. Collapses
into a compact line (Power / CPU / GPU / RAM) or a single watts readout β via the "view" button or a
double-click; current direction is shown by color (charging green / discharging orange).
The tray icon changes with the active mode:
Features
π Charge protection β "battery care" with a selectable threshold (40/50/60/70/80%,
picked in Settings β Battery) / full charge to 100%.
ChargeGuard: the firmware drops the limit after sleep and power-source changes β
the utility re-applies it automatically.
π§³ "Travel" mode β a one-off charge to 100% on top of battery care: the suitcase button
in the panel / a menu item. On reaching 100% β an OSD and a sound; unplugging the charger
resets the mode by itself (the next plug-in is back to the threshold).
π Charger wattage β when the charger is plugged in, show the connected PD adapter's
wattage (watts) in the OSD and in the Monitor. Over the charge icon β a PSU quality badge:
π΄ "!" if the adapter is weaker than the configured threshold (slow charging), βͺ "?" if the
PSU is non-PD (e.g. plain 5 V β wattage can't be negotiated). The icon still shows the current
charge limit. The threshold is configurable (Settings β Battery). Driver-free (read-only).
short press β cycle through modes with an OSD (configurable);
double click β toggle the charge limit (configurable);
hold β quick settings panel (modes + charge limit, closes on Esc/X/click-outside;
configurable).
β¨οΈ Reviving "dead" keys with remapping: the Mi clicks and the "settings" / AI /
"projection" keys can be bound to any function β from cycling modes to launching your
own program (see "Key remapping"); the microphone key mutes the system mic, the backlight
key shows an OSD with the level.
π±οΈ Touchpad on/off β an action for any key + a cell in the panel. Disabling is done the
stock way (like in Device Manager, no drivers) and does not survive a reboot β the touchpad
can't get stuck disabled.
π Touchscreen on/off β same for the laptop's touchscreen: an action for a key,
a cell in the panel, stock driver-free disabling and auto re-enable after a reboot.
The cell appears only if a touchscreen is present in the system.
π Update check (on by default) β once a day the app asks GitHub whether a new version is out
and shows a toast linking to the release; the notice also appears on the "About" tab. Updates are
never installed automatically β this is a notification only (grab it from the release page or
run winget upgrade Oksion.XiControl). With the toggle off the app makes no network requests at
all, and a one-off check is available on a button.
βοΈ Settings window in Windows 11 style β all options across tabs (General / Features /
Battery / Display / Touchpad / Performance / Keys / HTTP API / About), dark and light themes.
The Features tab controls what to show: "Owl mode", touchpad, touchscreen and refresh-rate
control; a disabled feature disappears from the menu and panel entirely.
π¨ The tray icon changes with the mode, monochrome to match a light/dark taskbar;
a dark menu matching the system theme (switches on the fly).
π¦ "Owl mode" β don't sleep, and don't turn off the display (optional); a closed lid on
AC power only turns off the display (on battery β regular sleep). Owl in the panel / a checkbox
in the menu; power timings aren't changed, the lid action is restored afterwards.
If you only need the machine to stay awake (for a remote session, say), set
"OwlIgnoreDisplay": true in config β the screen then blanks as usual instead of burning idle.
The feature can be hidden entirely ("OwlMode": false in config).
π₯οΈ Auto refresh rate β plug in the charger β 120 Hz, unplug β 60 Hz
(rates configurable in config: AcRefreshRate/BatteryRefreshRate; if the panel lacks
such a rate, the nearest one is used). A menu toggle and a panel cell;
holds after sleep and power-source changes. Refresh-rate control can be hidden entirely
(Settings β Features or "RefreshRateFeature": false) β the menu item, panel cell and the
refresh-rate section of the Display tab go away (the tab itself stays β brightness lives there).
π Power profiles β your own performance mode on AC and on battery
("Don't change" β leave alone). Applied at startup and on power-source changes; driver-free (firmware WMI).
π‘ Brightness memory β a separate option (no profiles): screen brightness is remembered
and restored separately for AC and battery (WMI ACPI, the same channel Windows uses).
π Brightness limit β a maximum brightness, separately for AC and battery (protects an
OLED panel from burn-in): anything above the limit smoothly slides back, and not as an
ultimatum but as a "bargain" β once a minute, half the gap; raise it again and the utility
backs off for 2 hours. See "Screen brightness limit" below.
π UI language: Russian / English / Chinese (δΈζ).
π Autostart via Task Scheduler (no UAC prompt at logon, works on battery).
π°οΈ HTTP API for the local network (optional, off by default) β control it from a
phone or from Home Assistant automations: read status and charge (GET /status),
switch mode, charge protection, "travel" and owl. A command allowlist, token authorization
(only the SHA-256 is stored), binds to 127.0.0.1 by default β network access is enabled by
a separate toggle. See "HTTP API" below.
Compatibility
Tested on the Xiaomi Book Pro 14 (TM2424). Should work on Xiaomi/Redmi laptops made by
ODM Bitland with the MiCommonInterface WMI class (most recent-generation Redmibook / Xiaomi
Book models).
If the class is found β the interface is there. The set of supported functions depends on the
model (the utility detects them at runtime and won't crash on unsupported ones).
Run as administrator (a firmware WMI-interface requirement β even reading doesn't work
without elevation).
Antivirus false positives
Xi Control is unsigned, runs as administrator and touches system things (the firmware charge
limit, refresh rate, a firewall rule for the optional HTTP API, an autostart task) β enough for
some over-eager heuristics to twitch. Easy to check: upload the exe to
VirusTotal β typically, out of ~70 engines only Bkav Pro
flags it (a generic signature like W32.Malware.*), while Microsoft Defender and everyone
else stay silent. Bkav's engine is AI/ML-based β that's the vendor's own positioning, and their
generic detection is literally named W32.AIDetectMalware. That kind of analysis looks for
"suspicious" code patterns common to both legitimate and malicious software β hence its notorious
reputation for false positives. It's a false positive, and it's not a bug on our side.
Why you can trust the build:
the source code is open β read it and compile it yourself;
releases are built in GitHub Actions from this repository (visible in the Actions logs),
not "from someone's laptop";
the exe is reproducible with dotnet publish (below) β verify it yourself.
So if your antivirus complains, it's a reason for it to apologize, not for you to panic. If you
like, report the file to the vendor as a false positive: such generic signatures usually fall off
with the next database update.
Build from source:
dotnet build XiControl.sln -c Release
# β src/bin/x64/Release/net8.0-windows/XiControl.exe
dotnet test XiControl.sln -c Release --no-build # unit tests (no hardware required)
All options live in the Settings⦠window (tray menu item): tabs General (language,
autostart, panel theme, update check, logging), Features (owl, touchpad,
touchscreen, refresh-rate control), Battery (charge threshold with a hint, "travel" sound and file,
lock-screen sound and toast, charger-wattage OSD, "weak PSU" threshold, battery health), Display
(brightness limit and memory; auto refresh rate, "Keep refresh rate" and the rates β the section is
hidden if refresh-rate control is off), Touchpad (bottom dead zone), Performance (mode visibility,
startup mode, power profiles), Keys (remapping), HTTP API (port, token, permissions) and About
(version, model with the board code, BIOS, serial number β masked by default, click to reveal).
The quick toggles (charge, "travel", owl, refresh rate, Monitor,
mode) stay in the tray menu and the panel.
Fine timings are edited only in %APPDATA%\XiControl\config.json (applied on the next
launch): MiHoldMs β the Mi-button hold threshold (400), MiDoubleClickMs β the double-click
window (300), OsdDurationMs β how long the OSD stays up (2800).
Config keys behind the UI settings (no need to edit them by hand, but worth knowing):
CareLimitPercent β the selected charge threshold; TravelLockSound / TravelLockToast β "travel"
sound and toast on a locked screen; CheckUpdates β whether to check for updates, SkippedVersion β
the version already announced, LastUpdateCheckUtc β timestamp of the last check (daily window).
For autostart, enable "Start with Windows" (Settings β General) β the scheduler task is created
elevated, so there's no UAC prompt at logon. The task is per user account (named
XiControl_S-1-5-21-β¦), so on a shared machine people no longer overwrite each other's autostart;
a task from older versions (plain XiControl) is recognised and migrated to the new name on the
next toggle. Self-repair on launch covers not only a missing exe but also a task pointing at an
outdated build β the typical case when the portable version is unpacked into a new folder.
Besides logon, the task also fires when you return to the session via fast user switching.
Travel mode (a one-off charge to 100%)
You usually keep battery care on (say, 80%), but before a trip you want a full charge. Press the
suitcase button in the panel (left of the threshold/100 pills) or the "Charge for the road"
menu item β the utility lifts the limit once and tops up to 100%.
On reaching 100% β a "ready for the road" OSD and a sound (toggle
Settings β Battery β "Ready sound", on by default).
Unplug the charger β the mode turns off by itself; the next plug-in is back to the care threshold.
Manually picking the threshold/100 pill also cancels the mode. With a permanent "100%" the button is
inactive (nothing to top up).
The "threshold/100" pills show the base setting β "travel" is a temporary override on top of it
(driver-free, the same charge WMI channel). In config.json: "TravelMode", "TravelSound".
A custom ready sound β the "Custom sound file" field in the same settings (or config.json;
empty or a missing file β the built-in jingle; %VARIABLES% are supported; WAV/PCM only):
The toggles Settings β Performance β "Show the Eco mode" / "Show Full speed"
(applied immediately; the panel doesn't shrink β the cells of the remaining modes stretch).
Same in %APPDATA%\XiControl\config.json (you then won't be able to enable a hidden mode from
the app):
"EcoMode": false,
"FullSpeedMode": false
Eco β a hidden firmware mode absent from the official software (on the tested model it turns
off the keyboard backlight and lowers screen brightness β the most economical profile);
Full speed β if you don't use it or want to prevent accidental activation
(the mode is loud and only works on AC).
Both are shown by default. After editing the config by hand, restart the app.
Startup performance mode
The firmware resets the mode on reboot. What to enable at startup is a radio choice
Settings β Performance β "Mode at startup" (mutually exclusive; the fourth,
"Power profiles", is below):
Don't touch β leave whatever the firmware set.
Restore last β the app remembers the selected mode and brings it back after a reboot
(follows your switches). On enabling it records the current one right away.
Pin current β fixes one mode: it will be enabled every startup, whatever you shut down
from. You pick it while in the desired mode β that one gets pinned.
If the desired mode is unavailable at startup (e.g. "Full speed" on battery), "Auto" is enabled.
The pinned mode can also be set by editing config.json: "ForceStartMode": "Eco" (allowed:
"Quiet" / "Turbo" / "FullSpeed" / "Auto" / "Eco"; null or removing the line clears it).
Power profiles (mode by power source)
The fourth "Mode at startup" option: your own performance mode on AC and on battery.
Selected in the same place β Settings β Performance β "Power profiles";
below the radio choice appear "Mode on AC" and "Mode on battery" (or "Don't change").
"PowerProfiles": true,
"AcPerfMode": "Turbo", // mode on AC; null or "Don't change" β leave alone
"BatteryPerfMode": "Quiet" // mode on battery
The profile is applied at startup and on every power-source change ACβbattery (and after
sleep), through a debounced guard β like charge protection and auto refresh rate. Driver-free:
firmware WMI 0x08.
If the firmware rejected the mode (e.g. "Full speed" on battery) β a soft fallback to "Auto".
Screen brightness memory
A separate option Settings β Display β "Remember screen brightness" (off by default),
independent of "Power profiles". The utility tracks your brightness separately for AC and battery
and restores it on each transition: you set 80% on AC β the next plug-in returns 80%. Driver-free:
WMI WmiMonitorBrightness* (ACPI backlight, the same channel Windows uses).
Xi Control overrides Windows' brightness with its value β which is why the option is enabled
explicitly; a transition may briefly do a double adjustment (Windows β ours ~1.5 s later).
AcBrightness/BatteryBrightness in config fill themselves in.
Mode and brightness changes run in the background (the UI isn't blocked); on a panel without
WMI brightness the feature degrades silently (logs to log.txt, doesn't crash). Config writes are
debounced (sparing the SSD).
Screen brightness limit
Settings β Display β "Limit brightness" (off by default) + two limits β one for AC, one
for battery. Made with OLED panels in mind: constant high brightness accelerates burn-in, and the
limit gently keeps it from living there. Driver-free β the same ACPI backlight WMI channel.
Up front and honestly: locking the Windows brightness slider itself is impossible β no such
API exists. The utility can only bring brightness back after the fact, so the rollback is made as
unobtrusive as possible:
Excess is lowered smoothly (~10 s for the whole path, 1% steps), never as a jump.
A "polite bargain" instead of an ultimatum: set 80 with a limit of 60 β the utility doesn't
rewind immediately, but halves the gap once a minute: 80 β 70 β 65 β 63 β β¦ β 60. A remainder
of β€2% is closed in one go.
Raised it again after its step β that's a "I really need it brighter" signal: the utility
yields and leaves brightness alone for 2 hours. The pause resets on session lock, sleep,
a power-source change and an app restart.
Lowering below the limit is never touched β the utility never raises brightness.
With Windows adaptive brightness on, the limit doesn't work (two systems would fight over
brightness): the utility detects this and states the reason right on the tab. Turn adaptive off
in Settings β System β Display.
Plays nice with "Remember brightness": values above the limit are not remembered at all
(rather than clamped) β your comfortable brightness in the slot isn't eroded; on restore the
slot is pressed down to the current limit but stays untouched in the config.
In config.json: BrightnessCapEnabled, BrightnessCapAc/BrightnessCapBattery (limits, %),
and fine timings β BrightnessRampMs (smooth-slide duration, 10000), BrightnessConvergeMs
(interval between bargain steps, 60000), BrightnessBackoffMin (pause after a repeated raise, 120),
BrightnessGapDivisor (gap divisor, 2), BrightnessSnapPercent (close-out threshold, 2).
Auto refresh rate (screen rate by power source)
The screen switches to a different rate depending on the power source: on AC β higher (smoothness),
on battery β lower (savings). Enabled from the tray menu item, a panel cell, or in
Settings β Display; the rates are picked there too (applied immediately). In config.json:
How it actually behaves (plain Win32: the built-in panel is located via QueryDisplayConfig, the
rate is set with ChangeDisplaySettingsEx β no driver needed):
The laptop's built-in panel only, resolution and color depth are untouched β only the rate
changes. External monitors are never touched, even when one of them is set as primary; if the
panel isn't currently active (lid closed, "second screen only") nothing changes at all.
The nearest supported rate at the current resolution is taken: you asked for 120, the panel
can only do 90/60 β it picks 90 (ties go to the higher). So entering "144" on a 60 Hz matrix is
safe β it just stays 60. A value β€ 0 in config is ignored.
Triggers at app startup, on ACβbattery changes and on wake from sleep (the latter two are
debounced ~1.5 s, events arrive in bursts), and immediately when you enable the option.
If the desired rate is already set β the screen doesn't blink (no redundant call is made).
The rate is written to the display registry (CDS_UPDATEREGISTRY), i.e. it survives a reboot;
but with the option off the app doesn't touch the rate at all (including at the moment you
uncheck it β whatever was set stays, restore it manually).
The actual mode switch runs on a background thread (doesn't block the UI), and a failure is
merely logged to log.txt, the app doesn't crash. The power-change OSD appends the actual rate ("β¦ β’ 120 Hz").
When editing AcRefreshRate/BatteryRefreshRate directly in the config, restart the app
(a choice in the settings window applies immediately; a non-standard value from the config is shown too).
"Keep refresh rate" (HoldRefreshRate, off by default) β a separate toggle in the same
Settings β Display. With it, auto refresh rate watches the screen itself, not just the power
source: if the mode is changed by someone else β Windows settings, another utility, the driver after
a reset β the configured rate is restored through the same ~1.5 s debounce. Without it the setting
quietly stops holding until the next power event. It works on top of auto refresh rate (with it off
there is nothing to restore), so the toggle is greyed out while auto rate is disabled. No polling:
the system's display-mode-changed event only.
> An expected side effect, and the point of the feature: while the option is on, you can't change
> the rate via Windows settings β we restore it faster than Windows asks "Keep these settings?".
> To change it by hand, turn the toggle off.
Bottom dead zone on the touchpad
If the bottom edge of the pad keeps catching your palm or thumb, turn on Settings β Touchpad β
"Bottom dead zone" and pick the strip height (8/10/12/15/20 mm, 12 by default). In config.json
these are TouchpadDeadZone and TouchpadDeadZoneMm.
It's important to know what the zone actually does: it suppresses the start of a touch. A
finger first placed inside the strip doesn't move the cursor and doesn't tap β but a gesture
started higher up keeps working all the way down, so scrolling and dragging aren't cut off.
Pressing inside the zone still clicks β the strip doesn't become truly dead.
Under the hood this is a stock Windows Precision Touchpad setting (SuperCurtainBottom), not
input interception: a single machine-wide registry value, no drivers and no hooks. It applies
right away β the app restarts the touchpad node itself, no need to sign out and back in (the pad
disappears for a second). Turning the option off removes the value rather than writing a zero.
> The zone lives inside Windows palm rejection. If Settings β Bluetooth & devices β Touchpad
> is set to maximum sensitivity, palm rejection is off entirely β and so is our zone. XiControl
> notices this and shows a warning right on the tab.
Key remapping
Each key gets its own action: Settings β Keys. The slots are the single click, double click and
hold of the Mi button, plus the "Settings" (gear), AI and "Projection" keys. Any slot can be bound
to: cycle modes, charge limit on/off, quick panel, owl mode, Monitor, "travel", touchpad and
touchscreen on/off, the system "Projection (Win+P)" / "Windows Settings" / "Copilot (Win+C)", media
controls (play/pause, next and previous track, stop), the calculator, launching your own program,
or "Nothing".
Media actions work with any player β the key goes to whoever owns the Windows media session. Volume
is deliberately absent: the keyboard already has dedicated keys for it.
The touchpad and touchscreen are disabled with Windows' stock mechanism (like "Disable device" in
Device Manager, no drivers) and always re-enable on their own after a reboot β they can't get
stuck disabled. Their cells are also in the quick panel, next to auto refresh rate (the touchscreen
cell only if a touchscreen is present).
Holding the Mi button opens the quick panel by default, but it can be remapped β e.g. put the
panel on a single click and "travel" on the hold.
Double-click Mi = "Nothing" β the gesture is off, a single click fires instantly
(no ~300 ms wait window).
Hold Mi = "Nothing" β the gesture is off, and a long press falls back to a plain click
(rather than doing nothing at all).
With the panel open, the "Settings" key always toggles charge (the pill in the panel).
For "Launch a programβ¦" an exe, a document or a URL will do; environment variables
(%USERPROFILE% etc.) are expanded, a path with spaces goes in quotes, arguments can follow the
path: "C:\\Program Files\\App\\app.exe" --flag. Note: Xi Control runs with administrator rights β
the launched program inherits them.
The old options (MiShortPress, MiDoubleClick, SettingsKey, AiKeyProgram/AiKeyArgs)
are migrated automatically on the first launch of the new version.
HTTP API (control from the local network)
An optional web API to poke Xi Control from a phone or Home Assistant automations.
Off by default β enabled in Settings β HTTP API. There you set the port, generate a token
(shown once β copy it right away; only the SHA-256 is stored) and allow commands one by one. By
default only reading state is available.
Routes (all with an Authorization: Bearer header, body β JSON):
Security (the utility runs as administrator, so β deliberately and with caveats):
127.0.0.1 only by default β unreachable from the network even with a token. LAN access is a
separate "Access from local network" toggle with a warning; it then creates a firewall rule scoped
to LocalSubnet (your subnet only), removed when turned off.
The command allowlist is baked into the code β settings, autostart and launching programs via
the API are impossible; a disabled command answers 403, an unknown path β 404, no token β 401.
The API settings live in %ProgramData%\XiControl\api.json under a "write only for
administrators" ACL: a non-elevated third-party process can neither enable the server nor swap
the token.
Plaintext HTTP (no TLS) is a deliberate trade-off: the allowlist's blast radius is small.
None of this runs or spends resources while the API is off (the server simply isn't started).
Limitations
The "battery care" threshold is picked from a discrete set the firmware supports β an arbitrary
percentage via WMI is impossible. On the tested model (TM2424) that's 40/50/60/70/80/100%;
on other models the set may differ (the firmware validates it itself and rejects unsupported levels).
The Fn+Mi combo is indistinguishable from a single Mi (the firmware sends identical events),
which is why short/long presses are used.
The feature set depends on the model: firmware telemetry (fan RPM) is unsupported on the tested
machine. Temperature is shown anyway β not from the firmware, but via Intel DPTF
(WMI EsifDeviceInformation), as a line graph in the Monitor.
GPU usage comes from Intel IGCL β the graphics driver's user-mode API (ControlLib.dll in
System32, installed with the Intel driver; no administrator rights needed). On machines without
Intel graphics the GPU row simply does not appear. This channel exposes no temperature or fan RPM
for integrated GPUs β only usage, power and clock.
How it works
The MIFS protocol is reverse-engineered and documented in docs/:
01-wmi-protocol.md β transport, buffer format, command codes, key events (the main document);
In short: the MiInterface method takes a 32-byte buffer
([1] β GET 0xFA / SET 0xFB, [3] β command, [4]/[6] β arguments) and returns a
status in OUT[1] (0x80 β ok). Charge is command 0x10, modes are 0x08,
key events arrive as the WMI event HID_EVENT20.
The protocol was reconstructed from open sources (including the Linux kernel driver) without
copying anyone's code β only facts about the interface were carried over. Details and source
licenses: docs/04-references.md.
Development
src/ the app (C# / .NET 8 / WinForms): Wmi/ β the MIFS protocol, Input/ β keys
and gestures, Ui/ β tray, panel, OSD, monitor, settings (Ui/Settings/ β tabs),
SystemIntegration/ β guards, power, touchpad/screen, Config/, Localization/
tests/ unit tests (xUnit) of pure logic on fakes β run without Xiaomi hardware
assets/svg/ icons: osd/ β color 128Γ128, tray/ β monochrome 24Γ24 (currentColor),
ui/ β non-square interface art (the Buy Me a Coffee button)
assets/sound/ embedded WAV jingles for "travel" mode
tools/ IconPreview/ β renders icons to PNG + generates app.ico; helper scripts
docs/ protocol and architecture documentation
reference/ PowerShell probes, firmware research logs
How the code is structured (the command layer, seams for tests, the guard pattern) β CLAUDE.md,
how to contribute β CONTRIBUTING.md.
Diagnostics: errors are written to %APPDATA%\XiControl\log.txt.