Monitor Functions and Object

Functions for retrieving screen resolution and multi-monitor info. Click on a function name for details. The Keysharp-specific Monitor object below adds display identity, metadata, brightness control and change notifications.

Function Description
MonitorGet Checks if the specified monitor exists and optionally retrieves its bounding coordinates.
MonitorGetCount Returns the total number of monitors.
MonitorGetName Returns the operating system's name of the specified monitor.
MonitorGetPrimary Returns the number of the primary monitor.
MonitorGetWorkArea Checks if the specified monitor exists and optionally retrieves the bounding coordinates of its working area.

Monitor Object

class Monitor extends Object

Represents one display. This class is exported by the KS module:

#Import "Ks" { Monitor }

Note: This class is experimental: it and its nested types may change or be removed without deprecation. See #Warn Experimental.

Snapshot semantics

Identity and geometry are read when a Monitor is created; call Refresh to re-read them. Other metadata, such as Model or RefreshRate, is read when first used and then kept. Brightness and the VCP methods query the device on every call. A property the display does not report is the empty string.

Obtaining a Monitor

M := Monitor(N)
M := Monitor.Primary
M := Monitor.FromPoint(X, Y)
M := Monitor.FromMouse()
M := Monitor.FromWindow(WinTitle, WinText, ExcludeTitle, ExcludeText)
M := Monitor.FromId(Id)
Monitors := Monitor.All
Count := Monitor.Count
MemberDescription
Monitor(N)The monitor with 1-based number N, the same numbering MonitorGet uses. If N is omitted, the primary monitor. A number outside the current monitor count throws a ValueError.
Monitor.PrimaryThe primary monitor.
Monitor.AllAn Array of every monitor, in number order, built from one enumeration. Prefer this to a loop over Monitor(A_Index), whose numbering can change between calls.
Monitor.CountThe number of monitors; the same value as MonitorGetCount.
Monitor.VirtualScreenThe whole desktop — the union of every monitor — as an object with X, Y, Width and Height, in native screen coordinates. X and Y are negative when a display sits left of or above the primary.
Monitor.FromPoint(X, Y)The monitor containing a point in native screen coordinates, or the nearest monitor when the point lies in a gap between displays.
Monitor.FromMouse()The monitor the mouse cursor is on. On Linux, cursor queries require a supported keysharp-desktop backend and do not request an interactive grant.
Monitor.FromWindow(WinTitle, ...)The monitor a window overlaps most, matching how the platform decides which monitor owns a window. Criteria use ordinary WinTitle matching.
Monitor.FromId(Id)The monitor whose Id matches, or the empty string when that monitor is not currently attached. Matching is case-insensitive. It reads the metadata of every attached display.

Identity

PropertyDescription
IndexThis monitor's 1-based number, matching MonitorGet.
NameThe operating system's name for the monitor; the same value as MonitorGetName.
ModelThe panel's model name, for example "U2720Q".
ManufacturerThe three-letter PNP manufacturer code, for example "DEL".
SerialThe panel's serial number.
IdAn identifier for this physical monitor that survives reboots and replugging, suitable for saving in a settings file — for example to restore a window layout per monitor. Pass it back to Monitor.FromId to find the display again.
AdapterThe graphics adapter driving the monitor.
ConnectionHow the monitor is attached: "HDMI", "DisplayPort", "eDP", "DVI", "VGA", "Internal", or the empty string.
IsPrimaryWhether this is the primary monitor.
IsInternalWhether this is a built-in panel, such as a laptop or all-in-one screen, rather than an external monitor.

Note: For a monitor which reports no serial number, Id identifies the port it is plugged into, not the panel; on macOS, it identifies the model, so identical displays share one Id. Id is the empty string when the display exposes no usable identity.

Geometry

PropertyDescription
X, Y, Width, HeightPosition and size in native screen coordinates, the same space MonitorGet reports.
BoundsThe full monitor rectangle as an object with X, Y, Width and Height.
WorkAreaThe work area — the desktop minus taskbars, docks and panels — in the same shape.
ScaleThe monitor's authored-size scale; 1.0 is 100% and 1.5 is 150%. It maps deliberately authored UI sizes into native screen units.
DpiDots per inch, computed from the panel's physical size. Expressed in the same units as Width and Height.
PhysicalWidth, PhysicalHeightThe panel's physical size in millimetres.
RefreshRateVertical refresh in Hz as a floating-point value — 59.94, not 59.
OrientationClockwise rotation of the desktop content in degrees: 0, 90, 180 or 270.
Refresh()Re-reads this monitor's layout and metadata in place and returns the same object, so it can be chained. The monitor is matched by name, then by number. Returns an empty string when the monitor is no longer attached, leaving the object's values unchanged.

Note: Scale is not a screen-coordinate conversion factor and must never be applied to absolute positions.

Brightness and DDC/CI

Percent := M.Brightness
M.Brightness := Percent
Supported := M.IsBrightnessSupported
Feature := M.GetVCP(Code)
M.SetVCP(Code, Value)
MemberDescription
BrightnessGets or sets the monitor's brightness as a percentage from 0 to 100. Assigning another value throws a ValueError before the device is contacted.
IsBrightnessSupportedWhether Brightness works for this monitor.
GetVCP(Code)Reads one raw DDC/CI VCP feature, returning an object with Current and Maximum. Throws a ValueError for a Code outside 0 to 255, and an OSError when the monitor does not answer.
SetVCP(Code, Value)Writes one raw DDC/CI VCP feature. Throws a ValueError for a Code outside 0 to 255 or a Value outside 0 to 65535, and an OSError when the monitor does not implement the code.

Over DDC/CI, each read or assignment of Brightness takes tens of milliseconds. On a monitor or platform which does not support it, Brightness throws an OSError naming the reason; reading IsBrightnessSupported performs one brightness read to test this without an exception.

VCP codes are defined by the MCCS standard, for example 0x10 brightness, 0x12 contrast, 0x60 input source, 0x62 speaker volume and 0xD6 power mode.

SetVCP reads the feature before writing it, so each call performs two device transactions.

Warning: SetVCP drives the monitor's firmware directly. Setting an input-source or power code will switch the monitor away from this computer, and some displays react badly to codes they document but mishandle. Verify a code against the monitor's own MCCS documentation first.

Display change notifications

Hook := Monitor.OnChange(Callback)
Hooks := Monitor.Hooks

Calls Callback whenever the display configuration changes, and returns a running MonitorHook controlling the subscription.

Callback is a function object which receives (Hook, Change):

ChangeMeaning
"Topology"The set of attached monitors changed: one was plugged in or unplugged, or the machine docked or undocked.
"Settings"The same monitors are attached, but something about them changed: resolution, position, scale, or which one is primary.

To inspect the new layout, read Monitor.Count or Monitor.All in the callback, or call Refresh on a Monitor obtained earlier.

The callback is not called for a notification which changed nothing observable.

The returned hook is an EventHook. Monitor.Hooks is an Array of the running hooks. A MonitorHook does not keep the script running, so a script which only waits for display changes calls Persistent.

Remarks

The built-in variables A_ScreenWidth and A_ScreenHeight contain the dimensions of the primary monitor, in pixels.

SysGet can be used to retrieve the bounding rectangle of all display monitors. For example, this retrieves the width and height of the virtual screen:

MsgBox SysGet(78) " x " SysGet(79)

Geometry, Name, Index and the factories work on every platform. What varies is metadata and device control.

Windows

Brightness works on the built-in panel and on external monitors which support DDC/CI.

Linux

New queries refresh display geometry and scale after display changes; existing Monitor objects retain their snapshot semantics. Work-area queries account for panels and docks independently of display-layout changes.

On X11, geometry, refresh rate and orientation are read through keysharp-desktop. On Wayland, RefreshRate and Orientation are the empty string and 0 if the native compositor connection is unavailable. Adapter is the DRM driver name, such as amdgpu or i915.

For external monitors, Brightness and the VCP methods need the i2c-dev kernel module and access to the i2c bus. A root install adds a udev rule granting the local-seat user access to display-controller i2c buses, and ddcutil's own rule also works; without either, they throw an OSError naming the requirement. OnChange works on X11 and every Wayland compositor.

macOS

Model and Adapter are always the empty string, and Connection distinguishes only built-in from external. RefreshRate is the empty string on some built-in panels.

Brightness works on the built-in panel, Apple displays and other external monitors which support DDC/CI. The VCP methods work only on external DDC/CI monitors.

Note: DDC/CI on Intel Macs is unverified. Some USB-C hubs and docks do not carry the DDC channel through to the monitor, in which case Brightness and the VCP methods throw an OSError for a display that would otherwise support them.

DllCall, Win functions, SysGet, EventHook

Examples

Displays info about each monitor.

MonitorCount := MonitorGetCount()
MonitorPrimary := MonitorGetPrimary()
MsgBox "Monitor Count:`t" MonitorCount "`nPrimary Monitor:`t" MonitorPrimary
Loop MonitorCount
{
    MonitorGet A_Index, &L, &T, &R, &B
    MonitorGetWorkArea A_Index, &WL, &WT, &WR, &WB
    MsgBox
    (
        "Monitor:`t#" A_Index "
        Name:`t" MonitorGetName(A_Index) "
        Left:`t" L " (" WL " work)
        Top:`t" T " (" WT " work)
        Right:`t" R " (" WR " work)
        Bottom:`t" B " (" WB " work)"
    )
}

Lists every monitor using the Monitor object, showing which facts the displays actually report.

#Import "Ks" { Monitor }

for M in Monitor.All
{
    Info := "Monitor:`t#" M.Index (M.IsPrimary ? " (primary)" : "") "`n"
    Info .= "Name:`t" M.Name "`n"
    Info .= "Model:`t" M.Manufacturer " " M.Model "`n"
    Info .= "Size:`t" M.Width "x" M.Height " at " (M.Scale * 100) "%`n"
    Info .= "Refresh:`t" M.RefreshRate "`n"
    Info .= "Id:`t" M.Id
    MsgBox Info
}

Dims every monitor that supports it. IsBrightnessSupported is tested first so an unsupported display is skipped instead of throwing.

#Import "Ks" { Monitor }

for M in Monitor.All
    if (M.IsBrightnessSupported)
        M.Brightness := Max(0, M.Brightness - 20)

Reacts to the display configuration changing — for example to re-place windows after docking. The hook does not keep the script running, so Persistent does.

#Import "Ks" { Monitor }

Hook := Monitor.OnChange(DisplaysChanged)
Persistent

DisplaysChanged(Hook, Change)
{
    if (Change = "Topology")
        ToolTip "Monitors attached or removed: now " Monitor.Count
    else
        ToolTip "Display settings changed"

    SetTimer () => ToolTip(), -3000
}

Saves the monitor a window is on, and finds that same physical display again on a later run.

#Import "Ks" { Monitor }

Id := Monitor.FromWindow("A").Id
IniWrite Id, "layout.ini", "Editor", "Monitor"

; ... on a later run:
Saved := IniRead("layout.ini", "Editor", "Monitor", "")
M := Monitor.FromId(Saved)

if (M)
    MsgBox "That display is attached, as monitor #" M.Index
else
    MsgBox "That display is not currently attached."