2

I'm trying to control the backlight brightness of my HP laptop integrated LCD, on Windows 10, with or without intel graphics driver.

I wanna lower the brightness much lower than the minimum allowed by windows/intel, just like I can do on Linux Mint (dual boot setup) through ACPI /sys/class/backlight/intel_backlight/brightness.

Here is what I have tried already:

  • WmiSetBrightness
  • IOCTL_VIDEO_SET_DISPLAY_BRIGHTNESS
  • laptop screen doesn't support DDC/CI
  • every single tool I could find online (f.lux, nircmd, screenbright, ...)
  • trying every single method in the undocumented igfx dll libraries in Windows/System32 and comparing the returned data with different brightness values of the screen, as well as igfxDHLib.IDataHandler::SetBackLightBrightness(_CUI_CALIBRATION_COLOR_INFO) but it didn't work, returning error codes that are nowhere to be found online.
  • trying to find the ACPI driver code from linux code, but didn't find much
  • tried static analysis (IDA) on monitor.sys, wmiprov.dll and igdkmd64.sys, found some promising functions like BrightnessTargetToPossible, IoctlSetBrightness and IoctlQueryBrightnessCaps, but couldn't find any hardcoded caps, instead just CallNextLowerDriver, so my guess is that it is passed to the next driver in the device stack, and I don't have pdb for igdkmd64 obviously :(

(Also, without the intel graphics driver, windows can't change brightness at all.)

Things I still didn't try:

  • Kernel mode debugging, mainly because I doubt that it would work on a VM since it should instead be able to access the real display interface? (and because I never tried Kernel debugging before)
  • writing a monitor filter driver? a Filter DO as described here

I know this question has been asked before, but those questions are more than 5 years old and have no correct answer. I wasted so much time on this and it's driving me insane how such a simple thing of controlling one single number (the PWM duty cycle) can be this much difficult to do. It's really unbelievable that intel and windows never thought their minimum brightness could be too blinding at night.

I'm really desperate for any way of doing this. Thank you very much for any help.

7
  • use a mechanical solution ... apply a window tint sheet on the display ... or wear sunglasses
    – jsotola
    Commented Jun 18, 2021 at 0:22
  • 1
    I have two Windows 10 laptops here (one 21H1 and one 21390.2025) both with Intel Graphics and standard Windows Brightness (Start, Settings, System) set to 0 is just barely lit - just off screen OFF. How dim do you need it?
    – anon
    Commented Jun 18, 2021 at 0:23
  • @John I want it to keep dimming until it turns off completely. The lowest brightness is fine if the room is lit, but if there is only one small lamp at night and I'm watching something on my laptop, it burns my eyes even though I use dark mode on everything. Commented Jun 18, 2021 at 0:29
  • 1
    My Brightness at 0 is very close to OFF. I cannot see it being dimmer and still be seen.
    – anon
    Commented Jun 18, 2021 at 0:30
  • @jsotola thought about that, but obstructing light affects colors and white balance too much. Simply dimming the backlight is so much different (I was shocked how different it is when I first used it on linux). Commented Jun 18, 2021 at 0:32

1 Answer 1

1

After years of not finding a solution, I finally did! And now I can control the backlight directly even setting it to zero and with fine granularity. So I'll share the solution for anyone interested :)

So, first, you'll need RWEverything (or any similar program/code that can read PCIe and system memory). The idea is simple, there is an address that is used by the Intel graphics driver where Windows writes the value to use when you use the default brightness keys.

I found the code in GitHub:

#!/usr/bin/env python3
# Intel Backlight Hack
# CC0 2018 Taeyeon Mori aka /u/Haruha

# Must be absolute!
rw_path = "D:\\Development\\Win32-Intel-Backlight\\Rw-Everything\\Rw.exe"

# Actuall Rw batch code follows
rw_code = """\
Read Intel GFX BAR1
> Local0 = rPCIe32(0, 2, 0, 0x10)
Null out flags to get address
> Local1 = and(Local0, 0xFFFFFFFFFFFFFFF0)
LVX offset = 0xC8254
See https://github.com/RehabMan/OS-X-Intel-Backlight/blob/master/IntelBacklight/IntelBacklightHandler.cpp#L39
> Local2 = or(Local1, 0xC8254)
Get LVX
> Local3 = r32(Local2)
Get max brightness
> Local4 = shr(Local3, 16)
Get relative brightness
> Local5 = mul(Local4, {level})
> Local5 = div(Local5, 100)
Construct new LVX
> Local6 = shl(Local4, 16)
> Local6 = or(Local6, Local5)
Write to hardware
> w32 Local2 Local6
"""

rw_exit_code = "> RwExit"


import sys
import ctypes

def is_admin():
    try:
        return ctypes.windll.shell32.IsUserAnAdmin()
    except:
        return False


if not is_admin():
    # Re-run the program with admin rights
    ctypes.windll.shell32.ShellExecuteW(None, "runas", sys.executable, " ".join([__file__] + sys.argv[1:]), None, 1)
    
    sys.exit()


import os
import argparse
import subprocess
import tempfile

parser = argparse.ArgumentParser()
parser.add_argument("level", help="New Backlight level", choices=list(range(0,101)), type=int, metavar="0-100", nargs="?")
parser.add_argument("--noexit", action="store_true", help="Don't exit Rw after running the script")
parser.add_argument("--pause", action="store_true", help="Pause after execution")
args = parser.parse_args()

if args.level is None:
    print("Enter new brightness level in %, or \"x\" to abort")
    while True:
        print("Brightness: ", end="")
        v = input()

        if v.lower() == "x":
            print("Aborted")
            sys.exit(12)
        
        try:
            args.level = int(v)
        except ValueError:
            continue
        else:
            if args.level <= 100 and args.level >= 0:
                break

with tempfile.NamedTemporaryFile("w", delete=False) as f:
    f.write(rw_code.format(level=args.level))
    
    if not args.noexit:
        f.write(rw_exit_code)
    
    path = os.path.dirname(f.name)
    name = os.path.basename(f.name)

subprocess.run([rw_path, "/Command=%s" % name], cwd=path)

os.unlink(os.path.join(path, name))

if args.pause:
    print("Press enter to continue")
    input()

(Someone edited the code above, I assume to preserve it in case the gist disappears, but anyways. The important part is only the "rw_code = ..." those are the instructions you need to execute in RWEverything, you can ignore the rest if you're writing your own code.)

You can use the python script in the link above or write something in your favorite language (I used AHK). But basically, you read a value from the PCIe of Intel graphics, and then you get some address that you read from system memory and the current and max values, and you modify the current brightness value.

All of this is using the command line API of RWEverything so you can call it from some keyboard shortcut and make sure it runs as admin whenever Windows is started and voila, brightness control that goes to zero.

In my code I made two shortcuts, one for controlling windows brightness and then one for the direct brightness value. When you write a value using memory Windows still thinks it's the old value, so I only use writing to write below the minimum of Windows and use windows for above the Windows minimum.

It's not a big deal because rarely, like after waking up, Windows might set the brightness again to the old value, so I keep them close by using windows steps for the big jumps and my code for fine tuning or going really low.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .