Repository navigation
FBD 1.7.0
BetterDisplay-parity work agreed at the Phase 3 gate. Scope, evidence and the
deliberate exclusions are in docs/plans/betterdisplay-parity-scope.md,
docs/plans/fbd-gap-matrix.md and docs/plans/fbd-parity-progress.md.
Added
- A live DPCD read over AUX:
fbdcli dpcd <display>. Row 24's read path —IODPDeviceReadDPCD,
the AUX route to a DisplayPort sink's registers where DSC, link training and tiling are described —
reached through a runtime-resolved IOKit shim that declares only the read. Its write-side
neighbours (IODPDeviceWriteDPCD, the retrain and set calls) resolve on this macOS and are
deliberately not declared: a wrong write there can wedge the only attached display. The command
reads a block and hands it to the decoderreport --dpcdalready uses, and a failure reports which
step declined rather than one message for three causes. Verified as far as this machine allows: the
entry points resolve, the argument validation and the decode path are exercised, and against the
built-in panel it declines at service discovery, because FBD's registry walk is tuned for external
displays. Proving a read end-to-end needs an external DisplayPort display, which this Mac has
none of. A guessed fallback for the built-in panel was written, measured, and removed rather than
shipped unverified. - The HUD can use the system's glass. FBD's HUD is a vibrancy view in
.withinWindowmode,
which samples the window's own content — the SwiftUI content and nothing else — so it draws a
flat panel rather than sampling the desktop behind it. The OS's glass (NSGlassEffectView,
macOS 26+) is now a third HUD background; the default is untouched, so an untouched install keeps
the look it has always had. Captured on macOS 27: it renders correctly, and the difference is
subtle — which matches the Crisp review's own note that no glass material can hold a HUD's
backdrop on this OS family. Two measured facts shaped it: onlycontentViewis guaranteed to sit
inside the glass (so the SwiftUI content goes there, not in as a subview), and.behindWindowis
not a substitute for.withinWindowhere. - FBD speaks 简体中文 and 繁體中文 — starting with the panel. Crisp ships en · 简体中文 · 繁體中文
and FBD shipped English only. The mechanism is deliberately the small one: three
.lproj/Localizable.stringsbeside the icon inSources/FBD/Resources, copied into the bundle by
make app, which is where macOS looks for them — no SwiftPM resource bundle, and no code
change, because SwiftUI already resolvesText("Display Mode")against the app bundle. The
225 strings now cover the panel, the Settings pages and the filters/automation views. That last
round turned up the real defect rather than a missing translation:SettingsRow,
SettingsSliderRowanddisclosureSectiontooktitle: String, andText(String)is verbatim
— SwiftUI only looks a key up through aLocalizedStringKey— so every settings title and every
section header, the panel's included, was invisible to these files no matter how many translations
were added. All call sites pass literals, so the parameters becameLocalizedStringKeyand the
titles started resolving: verified live in the app's accessibility tree, where "Connect all" now
reads 全部连接. A test stops the three files drifting: the languages must agree on every key, and
every key must still appear in the source, since a renamed string would otherwise silently stop
resolving and look like "not translated yet" rather than broken. The Settings sidebar went the same
way: it renderedText(item.rawValue), a String, so its labels were verbatim too, andSettingsPage
now spells its cases out in aswitchreturningLocalizedStringKey— which keeps every key a
literal the test can verify (236 keys each). What the drift test cannot reach is an interpolated
string: SwiftUI turnsText("\(nits) nits")into the format key%lld nits, which never appears
verbatim in the source, so the "every key still exists" check would reject an entry for it. That is
now fixed rather than accepted: the check understands format keys and verifies their literal
segments, so the interpolated strings are localised too —%lld nits,%lld Hz,%lld display%@
and%lldpx, 240 keys per language. The Chinese forms drop the English plural, which is why a
translation may omit a placeholder. What remains English is numbers with no words in them, and the
automation model's ownlabel/token— those stay English on purpose, becauseFBD_EVENT_LABEL
hands them to shell rules as a script contract, so the UI maps those cases itself. Two things worth
recording —Sources/FBD/Resourcesisexcluded from the SwiftPM target (which is why Info.plist
lives there and is copied by hand), so the.lprojdirectories have to be copied by the Makefile;
andCFBundleLocalizationsmust not sit alongside real.lprojdirectories, because it made
macOS report every language twice, a signal that sends the next person hunting a nested copy that
is not there. - The DDC input menu marks the input in effect. It marked nothing until now: reading VCP 0x60 was
a blocking DDC read, so the panel deliberately refused to guess and its title said "Choose…". The
read now rides in the same batched read as contrast/volume/mute — one more feature on the
display's own queue, off the main actor — so the menu shows a checkmark on the current input, its
title names it, and where a monitor refuses to answer it falls back to the last input set rather
than claiming knowledge it does not have. TheDouble → UInt16conversion is a tested helper
(DDC.inputSource(from:)) rather than an inline cast in a view, where an out-of-range answer would
have trapped. - A Dark Mode toggle, completing Night Shift + True Tone. Crisp has all three system toggles and
FBD had two. The mechanism is Crisp's: SkyLight's appearance theme
(SLSGetAppearanceThemeLegacy/SLSSetAppearanceThemeLegacy/SLSSetAppearanceThemeNotifying),
resolved at runtime through a C shim like every other private surface, so a Mac without those
symbols degrades to "unavailable" rather than crashing — and the notifying variant is preferred so
the switch crossfades the way Control Center's does. Available asfbdcli dark-mode [on|off]
(bare, it reports) and as a toggle beside True Tone in Settings. Deliberately not stored in
Settings: it is the system's switch and another app can flip it, so the UI reads it live rather
than remembering a value that could go stale. - The resolution list shows the sizes you actually switch between. The panel's Resolutions
section offered every mode inline — 132 of them on this machine — while reaching one through
the ⋯ submenu cost three clicks, so neither route was good. It now shows one row per logical
size at the current mode's aspect ratio, HiDPI preferred, with the mode in effect always on
the list, and Show all N modes one click away.CuratedModesis pure, so what counts as
worth showing is unit-tested: one row per size, the current mode owns its own row, variants
win elsewhere, other aspect ratios wait behind "show all", and truncation never drops the
mode that is on screen. - Configuration protection covers rotation and the colour profile.
ProtectedPropertiesoffered six of BetterDisplay's nine properties; it offers eight now.
Neither new one can be read — there is no readable rotation and no readable current
colour profile — so both are restored from the value FBD itself last applied, recorded as
it applies it; one set by another tool is invisible, which the type says plainly. The
legacy default is deliberately unchanged, so upgrading never begins re-applying more state
than before: widening remains an explicit choice. - A saved network device can be attached to a display. The device then follows that
display's volume when the brightness/volume keys drive it, so a TV or AVR stops being
something reachable only throughfbdcli tv. Attachments are keyed by the display's
identity, not its id, so a reconnect keeps them;fbdcli config device attach|detach|attachmentsmanages them, and a device deleted afterwards is reported as
dangling rather than silently resolving to nothing. Philips sets are the one exception,
and say so: pairing needs a PIN read off the TV and that pairing does not outlive the
controller instance, so a fire-and-forget volume send cannot complete it. - Manual controls and a notification action for automation. A rule can be scoped to
manual— a control you invoke yourself, via the new Run button in Settings →
Automation orfbdcli automation run— and anotifyaction posts a user
notification, which runs in-process rather than through a shell. ON and OFF are two
manual rules, and a shell control that prints its state already records that output in
the run log, which is how a state-query control is built. Notification permission is
asked for only once a notification rule exists, never at first launch, and
fbdcli automation runrefuses a notification rule outright — the CLI is a bare binary
with no bundle identity or authorisation, so a request from there would be accepted and
then dropped, and reporting success would be a lie. fbdcli report [file]— an exportable display report. A detailed, paste-ready block
per display (identity, kind, geometry, current mode with its colour encoding, mode count
and refresh rates, control paths, brightness, XDR/HDR state) under a header carrying the
FBD and macOS versions — the macOS version because display behaviour differs per release.
Prints, or writes to a file for attaching to a bug report. The formatting lives in
DisplayReportin FBDCore and is locked by format tests, so it cannot drift silently.- The HTTP API covers four more of the parity settings.
POST /api/config/connectionsruns the connection planner through the same guarded
disconnect chokepoint the Settings buttons use;POST /api/config/protectionsets a
display's protected-property selection (the display may be a number or the string
/api/displaysreturned);POST /api/config/devicelists, adds or removes a saved
TV/AVR; andPOST /api/config/favouritelists resolution favourites, records the
current mode into a slot, or recalls one. Each validates its input and names the valid
values when it refuses, and a favourite whose mode is gone is reported rather than
approximated. - Three Shortcuts actions for the parity surfaces.
Set Lock Dim,Set Keyboard Control TargetandSync Display Groupjoin the existing display actions, so the
new features are scriptable from Shortcuts as well as fromfbdcliand the app.
Sync Display Groupruns the same tested planner as the Settings page's "Sync now",
and each action validates its input and names the valid values when it refuses. - The HTTP API covers the parity settings.
GET /api/configreports the new
settings — lock dim, the keyboard control target (with the valid values), the
brightness model and configuration protection;POST /api/config/lockdimand
POST /api/config/keytargetset them. Both writes take effect without a restart,
because each is read back at use time: lock dim re-reads its policy on every lock
event, and the media-key path reads the control target on every press. Both go
throughSettings, the same single write path the app andfbdclishare. The
existing token gate and method gate apply unchanged, and an omitted
targetPercentkeeps the stored level rather than resetting it. - A saved network-device registry. FBD already had working LG webOS, Samsung,
Philips and Yamaha controllers, but reaching one meant repeating the brand and
host on every single command and nothing remembered a device between runs.
NetworkDeviceRegistrystores them (name, brand, host, optional Yamaha zone)
through the single settings write path;fbdcli config device list|add|remove
manages them; andfbdcli tv <name> …addresses one by name. A host that could
redirect the request — a scheme, path, credentials, query, or a:portsuffix
— is refused when the device is added rather than stored for later. fbdcli configcompleted:group-sync <group> <0-100>,
connections connect-all|disconnect-all-but-main|swap-identical, and
model <key> [value]— the last of which also makes each brightness-model
value settable on its own instead of only as one opaque blob.- Per-property configuration protection. Protection was one opaque blob
(mode + brightness + preset). It now protects six properties independently —
resolution, refresh rate, preset, HDR mode, main status and brightness — each
selectable per display (ProtectedProperties, the Displays page, or
fbdcli config protect <id> <properties>). The default stays the old set, so
an existing install behaves exactly as before until the user widens it, and a
property the display cannot report is never forced. - Display-group synchronisation and activation policy. Groups gained an
enable flag, one of five activation policies, sync source/target member
filters, an optional nits-matching mode, and a pureGroupSyncPlannerthat
reusesBrightnessSyncfor the nits maths. Managed on the new Groups page and
viafbdcli config group-policy|group-enabled. - Resolution favourites. Five recall slots per display, recorded and applied
from the Displays page orfbdcli config favourite <id> save|apply|list. A
slot is recalled exactly, then by an equivalent mode, otherwise it reports
itself unavailable rather than applying a wrong mode. - Mode-list breadth and a refresh-rate list.
ModeFilterOptionsplus a
deduplicated descendingrefreshRates(from:). The unexposed (NTSC) timings
BetterDisplay lists are not faked:docs/PRIVATE_APIS.mdrecords that the
private queries behind them need a live WindowServer connection and crash a
process without one, so the gap is documented. - A tunable brightness model.
BrightnessPreferencesexposes the combined
curve: hardware min/max nits, HDR peak SDR/HDR nits, a re-defined 0 % software
level, a minimum hardware level, the software-dimming switchover point, the
upscaling maximum, whether hardware control is preferred, and what to assume
about stored control values at startup.BrightnessCurveis the pure
mapping; every field is clamped, inverted bounds are repaired, and a
non-finite value falls back to its default rather than to the ceiling. - Startup safe mode. Holding ⇧ Shift at launch — or
FBD_SAFE_MODE=1—
skips stored EDID overrides and configuration-protection restore for the
session. Fail-closed: it only ever suppresses an apply, and says so. - Lock dim. Dims to a chosen level while the screen is locked or the
screensaver is up, and restores the recorded brightness on unlock. The state
machine never dims blind, is idempotent on a repeated lock, and always has a
restore path. - Keyboard control target. The brightness and volume keys can act on the
screen under the pointer, the focused screen, every suitable screen, or the
display whose name matches the current audio device (exact or partial match),
with a deterministic fallback so a key press is never silently swallowed. - Connection management.
ConnectionPlannerproduces ordered
connect/disconnect/swap steps, executed through the existing guarded
DisconnectControllerchokepoint. It never emits a built-in-display disable
unless the explicit override is set, and refuses outright when there is no
main display. fbdcli config— one command for the settings above (show,protect,
favourite,lockdim,lockdim-target,keytarget,group-policy,
group-enabled), running locally against the shared defaults with no second
write path. Unknown property names, targets and on/off words are refused
rather than silently ignored.- A sidebar Settings layout. Settings was a two-tab form; it is now a
sidebar ofDisplays·Groups·Application·Menu·Keyboard·
Aboutwith long pages, and the panel grows to 760×860 for it. - Keyboard shortcuts on the panel's own controls. The settings gear takes ⌘,
and the tools footer ⌘⇧V (Virtual Screens), ⌘⇧G (Groups), ⌘⇧P (PiP), ⌘U
(Check for Updates) and ⌘Q (Quit FBD). The panel's controls previously carried
no key equivalents at all — ⌘, only duplicated the one the status menu already
had. This works because the panel can take key focus: its style mask carries
both.titledand.nonactivatingPanel. fbdcli ambient-light <display> [on|off]. Reads or sets the Apple panel's
ambient-light auto-brightness. The seam existed and was reachable from the
panel's More menu but had no CLI surface (gap-matrix row #12). A bare read
doubles as the capability probe — a display with no ambient-light sensor is
reported unavailable and exits 2 — and a set is confirmed by reading the state
back rather than trusting a status code.- The status item's icon is now a choice, and it can hide itself when there is
nothing to control. Settings → Menu → Status item offers a curated set of SF
Symbols (each measured to resolve on macOS 27 — the list is closed rather than
free text, because a name that fails to resolve would leave a menu-bar app with
no button), and an option to hide the icon while no display other than the
built-in one is attached. That option is refused unless a Dock icon is enabled:
hiding the item with no other way in would strand the user, so the policy fails
closed instead. Choosing an icon that cannot be resolved falls back to the
default rather than leaving the button blank. fbdcli config identityandconfig transfer. Which key a display's
stored settings are filed under is now a choice — vendor+model+serial (the
default), vendor+model, serial alone, or the display's name — and one display's
settings can be copied onto another. The default reproduces the historical key
byte for byte, so an existing install keeps every per-display setting attached;
switching is an explicit step and the transfer is separate rather than a silent
re-key. The copy handles both storage shapes: the per-display key families and
the network-device attachment, which lives inside one shared blob and would
otherwise be left pointing at nothing. It refuses to overwrite settings the
destination already has unless--overwriteis passed.- A DPCD/DSC decoder, driven by
fbdcli report --dpcd <hex>. The decoders for
the DisplayPort Configuration Data block — revision, link rate and lane
capability, per-lane training status, the DSC capability block, and HDCP's
BSTATUS — are pure, andreport --dpcddecodes a dump you supply, optionally
with an address prefix (68028:00), because HDCP sits 400 kB away from the
capability block. Reading DPCD from the hardware is not built: the private
getters are located and symbol-verified, but their signatures are unknown, and
calling one speculatively against a machine's only attached display is not a
risk worth taking. A dump is treated as a set of ranges, and a register that was
never read reports "not read" rather than zero — those are different claims, and
a report that conflates them is worse than no report. - Two sibling projects reviewed, and the first of their ideas taken on. Two
free alternatives, FreeDisplay and Crisp, were read for what FBD lacks; both are
MIT (Crisp ships aLICENSE, FreeDisplay declares MIT in its README but ships no
licence file). The capability comparison, the licences, and Crisp's Apple-grounded
UI doctrine are recorded in
docs/plans/reference-projects-freedisplay-crisp.md— FBD is ahead on automation,
network devices, streaming and EDID override, and behind on scaling finesse,
named presets, localisation and a few cheap system controls. - The panel's chrome no longer stacks a material on glass. The header, the
settings header and the tools footer sat on.ultraThinMaterialinside a panel
whose backdrop is already a frosted layer. They now use a flatlabelColorshade
(FBDTheme.chromeBandOpacity), so the panel holds one material rather than four —
and three live blurs no longer re-sample the same backdrop on every redraw. - Keep the Mac awake. A toggle in Settings → Menu → System holds a
NoDisplaySleepAssertionwhile FBD runs, so a long transfer, render or
presentation does not end with the display asleep. It is app-side by necessity:
the assertion belongs to the process that holds it, so afbdcliinvocation
would drop it on exit — which is why there is no command-line switch for it. - Several PiP windows at once. Capture sessions are now keyed by their source,
so a second display's PiP opens a second window instead of closing the first, and
re-asking for a source replaces only that source's window. The app keeps one
window per display, which is one window per source.fbdcli piptakes several
sources in one invocation (pip 1 --window 1234), which is the only way to open
two at once on a single-display Mac. Verified live: two windows coexisted and both
tore down cleanly when the CLI's stdin closed. - A virtual screen can be associated with real displays, so an arrangement is
recalled when it attaches.fbdcli config vscreen associate <screen-id> <display-id>…records the arrangement the displays have at that moment. They are
named by identity, so the association outlives the reconnect that reassigns display
ids — the reason it is worth storing at all. Recall goes through the same
CGConfigureDisplayOriginpath layout protection uses, so there is one place that
knows how to talk to the display layer, and it fails closed: if any associated
display is absent, nothing moves, because a partial arrangement would shuffle the
displays that are present into positions chosen for a layout that is not.
Mirroring is deliberately not implemented and has no stored flag — ADR-0001 records
that a virtual screen cannot be addressed as a display, so a flag nothing could
apply would be the dead configuration this codebase has had to remove twice. - GUI scale matching — and the physical-size data it needed.
CGDisplayScreenSize
reports a real panel size (344×222 mm for this machine's built-in display), so the row
carried as blocked on "reliable physical sizes" was blocked on nobody having called
it.Displaynow exposes that size and the GUI scale it implies — logical points per
millimetre, which is what determines how large the interface actually looks — and
UIScaleMatchingpicks, for each display, the mode whose scale is closest to a
reference. Two things are deliberate: distance is relative, so 5→6 and 6→5 are
equally far apart, and the choice fails closed — if no mode gets within tolerance
nothing changes, because moving every window on a display without achieving the match
is worse than leaving it alone.fbdcli config uiscalereports each display's scale
and what it would do, and toggles the setting; it reports rather than applies, since a
resolution change moves every open window.
Changed
- The panel is one surface now: rows are bands, not cards. The three rounded cards that floated
inside the panel — the display row, Virtual Screens and Display Groups — each carried their own
fill and a hairline border, which is the glass-on-glass stacking the chrome fix was about, one
level down. They are full-width bands ofColor.primary.opacity(0.035)(fbdPanelBand())
separated by hairlines, so the expanded detail region sits in the panel's surface rather than on
a card inside it.fbdCard()remains for Settings, where a group genuinely is its own surface.
Checked against the rendered panel rather than the source: no rounded corners remain on rows, and
the expanded detail reads as part of the band. - The brightness/volume HUD is configurable. Position (top centre/left/right, bottom
centre), a continuous or a segmented bar, a dark HUD or one that follows the system
appearance, a shadow toggle, and a corner-radius slider. The defaults reproduce the
previous appearance exactly, so an untouched install is unchanged, and every value is
clamped — a stored radius cannot clip the content. The HUD's content now carries its own
foreground, which also fixes unreadable text on the dark HUD in light mode. - Dock visibility rules. A menu-bar app is an accessory by default; it can now be given
a permanent Dock icon, or a brief one at launch so it is findable in the Dock just after
it opens. - The menu-bar panel is flattened. Each display is now one compact row: icon, name,
capability badges, current mode, the options menu and the power button on the title
line, with the brightness slider and its nits value inline beneath it. Filters, DDC/CI
and the resolution/XDR sections moved behind a single More controls disclosure.
Before this, one display filled most of the 650pt panel, so a multi-display Mac could
not see its own displays at once — the one thing the panel exists for. Nothing became
unreachable: the ⋯ menu already covered mode, refresh, colour, presets, favourites,
mirror, PiP, rotation, disable and Open in Settings, and the folded content is unfolded
in Settings → Displays. Settings.debugSummarynow includes the lock-dim, keyboard-target and
brightness-model settings.fbdcli helpdocumentsconfig.
Fixed
- "Prefer hardware brightness" and the curve's dimming zone now work, and a brightness read now
agrees with the screen. All three needed the same thing: FBD applied software brightness as an
overlay but neither tracked nor reported it, so a read returned the backlight value while the
screen showed something dimmer — the read-back-disagrees-with-the-screen bug
CombinedBrightnessdocuments itself as having fixed once already. The dim is now tracked per
display and reported byget; the overlay gives the brightness route its own dim source
beside the dim-to-black/lock one, combined as the darkest of the two so releasing either leaves
the other in force. On that footingpreferHardwareControl == falsedelivers brightness as a
dim over the backlight — falling back to the hardware when the request is brighter than the
backlight, because a dim can only ever lower what the screen shows — and the curve's
minHardwarePercent/softwareZeroPercentramp the hardware and dim the rest, releasing the
dim once the route climbs above the ceiling. The dim is taken only where it outlives the call, so
a one-shotfbdcliinvocation keeps the hardware route rather than accepting a write that would
vanish. The shipped profile takes neither route, so a default install behaves exactly as before. - Four brightness-model sliders shaped nothing; two of them do now. The combined-curve
model (BrightnessCurve) had no caller at all —XDRBoostPlannercomputes its own linear
map — so the "Upscaling maximum" and "Software dimming switchover" sliders in Settings were
inert.upscaleMaxPercentnow caps how far an upscale may reach, on the native nits target
as well as the software-boost factor, anddimmingSwitchoverPercentmoves where that boost
ramps. Both take effect only when the profile carries them, so a shipped profile behaves
exactly as before. The other two knobs — "Minimum hardware level" and "Redefine 0% software
brightness" — belong to the curve's dimming zone, which needs software-brightness state FBD
does not track;getdoes not report an overlay dim, so honouring them would make read-back
disagree with the screen, which is precisely whatCombinedBrightnessdocuments itself as
fixing. The row records that rather than half-wiring it. - The startup control-value policy works now. Settings offered "assume last used / write
to device / read from device" and nothing read it: FBD had no store of per-display control
values for it to act on, so a visible picker could not do anything.DisplayController
now records the brightness it sets, per display identity, and launch reads it back,
re-writes it, or leaves the device alone as asked. Absence is stored as absent rather than
0, because 0 is a legitimate brightness — and the default policy touches nothing, so a
default install behaves exactly as before. Verified live: withwrite, a stored 0.4
reached the panel (38.6% after backlight granularity); withread, a deliberately wrong
0.99 was corrected to the device's 0.3898 without changing the device; withassume,
nothing happened at all. - "Allow unsafe modes" did nothing; now it does. The setting was written by the
Settings page and read by nobody — every mode list came straight from the raw list — so
the switch changed nothing while appearing to work. The offered list now runs through
ModeFilterOptions(Display.offeredModes) in the panel's Display Mode menu, the
per-display sections andfbdcli modes, which also reports how many modes it hid. Every
other filter rule is deliberately left permissive:standard's view hides the scaled
("mixed") resolutions macOS itself provides — six on this machine — so wiring that view
in wholesale would have silently removed resolutions people could pick. That regression
is what the live before/after count caught, and there is a test for it now. - Settings → Back discarded a resized panel. Going back restored a hard-coded
460×650, so a panel the user had widened snapped back to 460pt wide (the saved
frame on this author's machine is 535pt). The size the panel had when Settings
opened is now remembered and restored on the way back, clamped to the panel's
minimum, so a deliberate resize survives the round trip.
Verification
swift build --disable-sandbox— clean, zero warnings.swift test --disable-sandbox— 868 tests, 0 failures (FBDCoreTests 732,
including the 266 test seams added by the parity work; FBDCLIParserTests 136).make ui-smoke— 5/5 against the rebuilt app.- CLI round-trips verified against the live app for every
configsubcommand,
including the refusal paths.
Not in this change
- The menu-bar quick menu was not restructured to mirror BetterDisplay's
grouped layout; only Settings was. Recorded as open. - The HTTP API stops short of group sync apply: it is reachable from
fbdcliand the
app UI, but exposing it needs more than a route. Group sync matches brightness in nits,
which means the executor'sDisplayControllingseam would needhardwareCeilingNits
added, otherwise the endpoint would silently skip the luminance matching the CLI does. - No new private API was introduced, so
docs/PRIVATE_APIS.mddoes not change.