ohmu
·
2026-10-15
1#include "common/printing.h"
2#include "common/jsonconfig.h"
3#include "common/size.h"
4#include "common/strutil.h"
5#include "detection/displayserver/displayserver.h"
6#include "modules/display/display.h"
7
8#include <math.h>
9
10static int sortByNameAsc(const void* a, const void* b) {
11 return ffStrbufComp(&((const FFDisplayResult*) a)->name, &((const FFDisplayResult*) b)->name);
12}
13
14static int sortByNameDesc(const void* a, const void* b) {
15 return -ffStrbufComp(&((const FFDisplayResult*) a)->name, &((const FFDisplayResult*) b)->name);
16}
17
18// `ffConnectDisplayServer()` hands out a process-wide result that other modules read too: `Monitor`
19// prints the same list, and `Brightness` pairs its own readings with these entries by position. It
20// must therefore not be reordered in place, which is what this module used to do through a cast
21// that dropped `const`. Sort a copy instead, so both the text and the JSON path get the requested
22// order without any other consumer seeing it.
23//
24// The copy is shallow: the entries keep owning their `FFstrbuf`s through the original result, and
25// it is never destroyed element-wise, so only the array itself is freed.
26static FFlist getOrderedDisplays(const FFDisplayOptions* options) {
27 const FFlist* displays = &ffConnectDisplayServer()->displays;
28 FFlist result = ffListCreateA(sizeof(FFDisplayResult), displays->length);
29 if (displays->length > 0) {
30 memcpy(result.data, displays->data, (size_t) displays->length * sizeof(FFDisplayResult));
31 result.length = displays->length;
32 }
33
34 if (options->order != FF_DISPLAY_ORDER_NONE) {
35 ffListSort(&result, sizeof(FFDisplayResult), options->order == FF_DISPLAY_ORDER_ASC ? sortByNameAsc : sortByNameDesc);
36 }
37
38 return result;
39}
40
41bool ffPrintDisplay(FFDisplayOptions* options) {
42 FF_LIST_AUTO_DESTROY displays = getOrderedDisplays(options);
43
44 if (displays.length == 0) {
45 ffPrintError(FF_MODULE_GET_DISPLAY_NAME(Display), 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "Couldn't detect display");
46 return false;
47 }
48
49 if (options->compactType != FF_DISPLAY_COMPACT_TYPE_NONE) {
50 ffPrintLogoAndKey(FF_MODULE_GET_DISPLAY_NAME(Display), 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT);
51
52 FF_STRBUF_AUTO_DESTROY buffer = ffStrbufCreate();
53 FF_LIST_FOR_EACH (FFDisplayResult, result, displays) {
54 if (options->compactType & FF_DISPLAY_COMPACT_TYPE_ORIGINAL_BIT) {
55 ffStrbufAppendF(&buffer, "%ix%i", result->width, result->height);
56 } else {
57 uint32_t scaledWidth = (result->width * 96 + result->dpi / 2) / result->dpi;
58 uint32_t scaledHeight = (result->height * 96 + result->dpi / 2) / result->dpi;
59 ffStrbufAppendF(&buffer, "%ix%i", scaledWidth, scaledHeight);
60 }
61
62 if (options->compactType & FF_DISPLAY_COMPACT_TYPE_REFRESH_RATE_BIT) {
63 if (result->refreshRate > 0) {
64 const char* space = instance.config.display.freqSpaceBeforeUnit != FF_SPACE_BEFORE_UNIT_NEVER ? " " : "";
65 if (options->preciseRefreshRate) {
66 ffStrbufAppendF(&buffer, " @ %g%sHz", result->refreshRate, space);
67 } else {
68 ffStrbufAppendF(&buffer, " @ %i%sHz", (uint32_t) (result->refreshRate + 0.5), space);
69 }
70 }
71 ffStrbufAppendS(&buffer, ", ");
72 } else {
73 ffStrbufAppendC(&buffer, ' ');
74 }
75 }
76 ffStrbufTrimRight(&buffer, ' ');
77 ffStrbufTrimRight(&buffer, ',');
78 ffStrbufPutTo(&buffer, stdout);
79 return true;
80 }
81
82 FF_STRBUF_AUTO_DESTROY key = ffStrbufCreate();
83
84 for (uint32_t i = 0; i < displays.length; i++) {
85 FFDisplayResult* result = FF_LIST_GET(FFDisplayResult, displays, i);
86 uint32_t moduleIndex = displays.length == 1 ? 0 : i + 1;
87 const char* displayType = result->type == FF_DISPLAY_TYPE_UNKNOWN ? nullptr : result->type == FF_DISPLAY_TYPE_BUILTIN ? "Built-in"
88 : "External";
89
90 ffStrbufClear(&key);
91 if (options->moduleArgs.key.length == 0) {
92 if (result->name.length) {
93 ffStrbufAppendF(&key, "%s (%s)", FF_MODULE_GET_DISPLAY_NAME(Display), result->name.chars);
94 } else if (moduleIndex > 0) {
95 ffStrbufAppendF(&key, "%s (%d)", FF_MODULE_GET_DISPLAY_NAME(Display), moduleIndex);
96 } else {
97 ffStrbufAppendS(&key, FF_MODULE_GET_DISPLAY_NAME(Display));
98 }
99 } else {
100 FF_PARSE_FORMAT_STRING_CHECKED(&key, &options->moduleArgs.key, ((FFformatarg[]) {
101 FF_ARG(moduleIndex, "index"),
102 FF_ARG(result->name, "name"),
103 FF_ARG(displayType, "type"),
104 FF_ARG(options->moduleArgs.keyIcon, "icon"),
105 FF_ARG(FF_MODULE_GET_DISPLAY_NAME(Display), "module-name"),
106 }));
107 }
108
109 FF_STRBUF_AUTO_DESTROY buffer = ffStrbufCreate();
110 double inch = sqrt(result->physicalWidth * result->physicalWidth + result->physicalHeight * result->physicalHeight) / 25.4;
111 uint32_t scaledWidth = (result->width * 96 + result->dpi / 2) / result->dpi;
112 uint32_t scaledHeight = (result->height * 96 + result->dpi / 2) / result->dpi;
113 double scaleFactor = (double) result->dpi / 96.;
114
115 if (options->moduleArgs.outputFormat.length == 0) {
116 ffPrintLogoAndKey(key.chars, 0, &options->moduleArgs, FF_PRINT_TYPE_NO_CUSTOM_KEY);
117
118 ffStrbufAppendF(&buffer, "%ix%i", result->width, result->height);
119
120 if (result->dpi != 96) {
121 ffStrbufAppendS(&buffer, " @ ");
122 ffStrbufAppendDouble(&buffer, scaleFactor, instance.config.display.fractionNdigits, instance.config.display.fractionTrailingZeros == FF_FRACTION_TRAILING_ZEROS_TYPE_ALWAYS);
123 ffStrbufAppendC(&buffer, 'x');
124 }
125
126 if (inch > 1) {
127 ffStrbufAppendF(&buffer, " in %i\"", (uint32_t) (inch + 0.5));
128 }
129
130 if (result->refreshRate > 0) {
131 ffStrbufAppendS(&buffer, ", ");
132 if (options->preciseRefreshRate) {
133 ffStrbufAppendDouble(&buffer, result->refreshRate, 3, false);
134 } else {
135 ffStrbufAppendSInt(&buffer, (int) (result->refreshRate + 0.5));
136 }
137 ffStrbufAppendS(&buffer, instance.config.display.freqSpaceBeforeUnit == FF_SPACE_BEFORE_UNIT_NEVER ? "Hz" : " Hz");
138 }
139
140 bool flag = false;
141 if (result->type != FF_DISPLAY_TYPE_UNKNOWN) {
142 ffStrbufAppendS(&buffer, result->type == FF_DISPLAY_TYPE_BUILTIN ? " [Built-in" : " [External");
143 flag = true;
144 }
145
146 if (result->hdrStatus == FF_DISPLAY_HDR_STATUS_ENABLED) {
147 ffStrbufAppendS(&buffer, flag ? ", HDR" : " [HDR");
148 flag = true;
149 }
150
151 if (flag) {
152 ffStrbufAppendS(&buffer, "]");
153 }
154
155 if (moduleIndex > 0 && result->primary) {
156 ffStrbufAppendS(&buffer, " *");
157 }
158
159 ffStrbufPutTo(&buffer, stdout);
160 ffStrbufClear(&buffer);
161 } else {
162 double ppi = inch == 0 ? 0 : sqrt(result->width * result->width + result->height * result->height) / inch;
163 bool hdrEnabled = result->hdrStatus == FF_DISPLAY_HDR_STATUS_ENABLED;
164 bool hdrCompatible = result->hdrStatus == FF_DISPLAY_HDR_STATUS_SUPPORTED || result->hdrStatus == FF_DISPLAY_HDR_STATUS_ENABLED;
165 uint32_t iInch = (uint32_t) (inch + 0.5), iPpi = (uint32_t) (ppi + 0.5);
166
167 char refreshRate[16];
168 if (result->refreshRate > 0) {
169 if (options->preciseRefreshRate) {
170 snprintf(refreshRate, ARRAY_SIZE(refreshRate), "%g", ((int) (result->refreshRate * 1000 + 0.5)) / 1000.0);
171 } else {
172 snprintf(refreshRate, ARRAY_SIZE(refreshRate), "%i", (uint32_t) (result->refreshRate + 0.5));
173 }
174 } else {
175 refreshRate[0] = 0;
176 }
177
178 char preferredRefreshRate[16];
179 if (result->preferredRefreshRate > 0) {
180 if (options->preciseRefreshRate) {
181 snprintf(preferredRefreshRate, ARRAY_SIZE(preferredRefreshRate), "%g", ((int) (result->preferredRefreshRate * 1000 + 0.5)) / 1000.0);
182 } else {
183 snprintf(preferredRefreshRate, ARRAY_SIZE(preferredRefreshRate), "%i", (uint32_t) (result->preferredRefreshRate + 0.5));
184 }
185 } else {
186 preferredRefreshRate[0] = 0;
187 }
188
189 FF_PRINT_FORMAT_CHECKED(key.chars, 0, &options->moduleArgs, FF_PRINT_TYPE_NO_CUSTOM_KEY, ((FFformatarg[]) {
190 FF_ARG(result->width, "width"),
191 FF_ARG(result->height, "height"),
192 FF_ARG(refreshRate, "refresh-rate"),
193 FF_ARG(scaledWidth, "scaled-width"),
194 FF_ARG(scaledHeight, "scaled-height"),
195 FF_ARG(result->name, "name"),
196 FF_ARG(displayType, "type"),
197 FF_ARG(result->rotation, "rotation"),
198 FF_ARG(result->primary, "is-primary"),
199 FF_ARG(result->physicalWidth, "physical-width"),
200 FF_ARG(result->physicalHeight, "physical-height"),
201 FF_ARG(iInch, "inch"),
202 FF_ARG(iPpi, "ppi"),
203 FF_ARG(result->bitDepth, "bit-depth"),
204 FF_ARG(hdrEnabled, "hdr-enabled"),
205 FF_ARG(result->manufactureYear, "manufacture-year"),
206 FF_ARG(result->manufactureWeek, "manufacture-week"),
207 FF_ARG(result->serial, "serial"),
208 FF_ARG(result->platformApi, "platform-api"),
209 FF_ARG(hdrCompatible, "hdr-compatible"),
210 FF_ARG(scaleFactor, "scale-factor"),
211 FF_ARG(result->preferredWidth, "preferred-width"),
212 FF_ARG(result->preferredHeight, "preferred-height"),
213 FF_ARG(preferredRefreshRate, "preferred-refresh-rate"),
214 FF_ARG(result->dpi, "dpi"),
215 }));
216 }
217 }
218
219 return true;
220}
221
222void ffParseDisplayJsonObject(FFDisplayOptions* options, yyjson_val* module) {
223 yyjson_val *key, *val;
224 size_t idx, max;
225 yyjson_obj_foreach (module, idx, max, key, val) {
226 if (ffJsonConfigParseModuleArgs(key, val, &options->moduleArgs)) {
227 continue;
228 }
229
230 if (unsafe_yyjson_equals_str(key, "compactType")) {
231 if (yyjson_is_null(val)) {
232 options->compactType = FF_DISPLAY_COMPACT_TYPE_NONE;
233 } else {
234 int value;
235 const char* error = ffJsonConfigParseEnum(val, &value, (FFKeyValuePair[]) {
236 { "none", FF_DISPLAY_COMPACT_TYPE_NONE },
237 { "original", FF_DISPLAY_COMPACT_TYPE_ORIGINAL_BIT },
238 { "scaled", FF_DISPLAY_COMPACT_TYPE_SCALED_BIT },
239 { "original-with-refresh-rate", FF_DISPLAY_COMPACT_TYPE_ORIGINAL_BIT | FF_DISPLAY_COMPACT_TYPE_REFRESH_RATE_BIT },
240 { "scaled-with-refresh-rate", FF_DISPLAY_COMPACT_TYPE_SCALED_BIT | FF_DISPLAY_COMPACT_TYPE_REFRESH_RATE_BIT },
241 {},
242 });
243 if (error) {
244 ffPrintError(FF_MODULE_GET_DISPLAY_NAME(Display), 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "Invalid %s value: %s", unsafe_yyjson_get_str(key), error);
245 } else {
246 options->compactType = (FFDisplayCompactType) value;
247 }
248 }
249 continue;
250 }
251
252 if (unsafe_yyjson_equals_str(key, "preciseRefreshRate")) {
253 options->preciseRefreshRate = yyjson_get_bool(val);
254 continue;
255 }
256
257 if (unsafe_yyjson_equals_str(key, "order")) {
258 if (yyjson_is_null(val)) {
259 options->order = FF_DISPLAY_ORDER_NONE;
260 } else {
261 int value;
262 const char* error = ffJsonConfigParseEnum(val, &value, (FFKeyValuePair[]) {
263 { "asc", FF_DISPLAY_ORDER_ASC },
264 { "desc", FF_DISPLAY_ORDER_DESC },
265 { "none", FF_DISPLAY_ORDER_NONE },
266 {},
267 });
268 if (error) {
269 ffPrintError(FF_MODULE_GET_DISPLAY_NAME(Display), 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "Invalid %s value: %s", unsafe_yyjson_get_str(key), error);
270 } else {
271 options->order = (FFDisplayOrder) value;
272 }
273 }
274 continue;
275 }
276
277 ffPrintError(FF_MODULE_GET_DISPLAY_NAME(Display), 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "Unknown JSON key %s", unsafe_yyjson_get_str(key));
278 }
279}
280
281[[gnu::cold]]
282void ffGenerateDisplayJsonConfig(FFDisplayOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) {
283 ffJsonConfigGenerateModuleArgsConfig(doc, module, &options->moduleArgs);
284
285 switch ((int) options->compactType) {
286 case FF_DISPLAY_COMPACT_TYPE_NONE:
287 yyjson_mut_obj_add_str(doc, module, "compactType", "none");
288 break;
289 case FF_DISPLAY_COMPACT_TYPE_ORIGINAL_BIT:
290 yyjson_mut_obj_add_str(doc, module, "compactType", "original");
291 break;
292 case FF_DISPLAY_COMPACT_TYPE_SCALED_BIT:
293 yyjson_mut_obj_add_str(doc, module, "compactType", "scaled");
294 break;
295 case FF_DISPLAY_COMPACT_TYPE_ORIGINAL_BIT | FF_DISPLAY_COMPACT_TYPE_REFRESH_RATE_BIT:
296 yyjson_mut_obj_add_str(doc, module, "compactType", "original-with-refresh-rate");
297 break;
298 case FF_DISPLAY_COMPACT_TYPE_SCALED_BIT | FF_DISPLAY_COMPACT_TYPE_REFRESH_RATE_BIT:
299 yyjson_mut_obj_add_str(doc, module, "compactType", "scaled-with-refresh-rate");
300 break;
301 }
302
303 yyjson_mut_obj_add_bool(doc, module, "preciseRefreshRate", options->preciseRefreshRate);
304
305 switch (options->order) {
306 case FF_DISPLAY_ORDER_NONE:
307 yyjson_mut_obj_add_null(doc, module, "order");
308 break;
309 case FF_DISPLAY_ORDER_ASC:
310 yyjson_mut_obj_add_str(doc, module, "order", "asc");
311 break;
312 case FF_DISPLAY_ORDER_DESC:
313 yyjson_mut_obj_add_str(doc, module, "order", "desc");
314 break;
315 }
316}
317
318bool ffGenerateDisplayJsonResult(FFDisplayOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) {
319 FF_LIST_AUTO_DESTROY displays = getOrderedDisplays(options);
320
321 if (displays.length == 0) {
322 yyjson_mut_obj_add_str(doc, module, "error", "Couldn't detect display");
323 return false;
324 }
325
326 yyjson_mut_val* arr = yyjson_mut_obj_add_arr(doc, module, "result");
327 FF_LIST_FOR_EACH (FFDisplayResult, item, displays) {
328 yyjson_mut_val* obj = yyjson_mut_arr_add_obj(doc, arr);
329 yyjson_mut_obj_add_uint(doc, obj, "id", item->id);
330 yyjson_mut_obj_add_strbuf(doc, obj, "name", &item->name);
331 yyjson_mut_obj_add_bool(doc, obj, "primary", item->primary);
332
333 yyjson_mut_val* output = yyjson_mut_obj_add_obj(doc, obj, "output");
334 yyjson_mut_obj_add_uint(doc, output, "width", item->width);
335 yyjson_mut_obj_add_uint(doc, output, "height", item->height);
336 yyjson_mut_obj_add_real(doc, output, "refreshRate", item->refreshRate);
337
338 if (item->drrStatus == FF_DISPLAY_DRR_STATUS_UNKNOWN) {
339 yyjson_mut_obj_add_null(doc, output, "drrStatus");
340 } else {
341 switch (item->drrStatus) {
342 case FF_DISPLAY_DRR_STATUS_DISABLED:
343 yyjson_mut_obj_add_str(doc, output, "drrStatus", "Disabled");
344 break;
345 case FF_DISPLAY_DRR_STATUS_ENABLED:
346 yyjson_mut_obj_add_str(doc, output, "drrStatus", "Enabled");
347 break;
348 default:
349 yyjson_mut_obj_add_str(doc, output, "drrStatus", "Unknown");
350 break;
351 }
352 }
353 yyjson_mut_obj_add_uint(doc, output, "dpi", item->dpi);
354
355 uint32_t scaledWidth = (item->width * 96 + item->dpi / 2) / item->dpi;
356 uint32_t scaledHeight = (item->height * 96 + item->dpi / 2) / item->dpi;
357 yyjson_mut_val* scaled = yyjson_mut_obj_add_obj(doc, obj, "scaled");
358 yyjson_mut_obj_add_uint(doc, scaled, "width", scaledWidth);
359 yyjson_mut_obj_add_uint(doc, scaled, "height", scaledHeight);
360
361 yyjson_mut_val* preferred = yyjson_mut_obj_add_obj(doc, obj, "preferred");
362 yyjson_mut_obj_add_uint(doc, preferred, "width", item->preferredWidth);
363 yyjson_mut_obj_add_uint(doc, preferred, "height", item->preferredHeight);
364 yyjson_mut_obj_add_real(doc, preferred, "refreshRate", item->preferredRefreshRate);
365
366 yyjson_mut_val* physical = yyjson_mut_obj_add_obj(doc, obj, "physical");
367 yyjson_mut_obj_add_uint(doc, physical, "width", item->physicalWidth);
368 yyjson_mut_obj_add_uint(doc, physical, "height", item->physicalHeight);
369
370 yyjson_mut_obj_add_uint(doc, obj, "rotation", item->rotation);
371 yyjson_mut_obj_add_uint(doc, obj, "bitDepth", item->bitDepth);
372 if (item->hdrStatus == FF_DISPLAY_HDR_STATUS_UNKNOWN) {
373 yyjson_mut_obj_add_null(doc, obj, "hdrStatus");
374 } else {
375 switch (item->hdrStatus) {
376 case FF_DISPLAY_HDR_STATUS_UNSUPPORTED:
377 yyjson_mut_obj_add_str(doc, obj, "hdrStatus", "Unsupported");
378 break;
379 case FF_DISPLAY_HDR_STATUS_SUPPORTED:
380 yyjson_mut_obj_add_str(doc, obj, "hdrStatus", "Supported");
381 break;
382 case FF_DISPLAY_HDR_STATUS_ENABLED:
383 yyjson_mut_obj_add_str(doc, obj, "hdrStatus", "Enabled");
384 break;
385 default:
386 yyjson_mut_obj_add_str(doc, obj, "hdrStatus", "Unknown");
387 break;
388 }
389 }
390
391 switch (item->type) {
392 case FF_DISPLAY_TYPE_BUILTIN:
393 yyjson_mut_obj_add_str(doc, obj, "type", "Builtin");
394 break;
395 case FF_DISPLAY_TYPE_EXTERNAL:
396 yyjson_mut_obj_add_str(doc, obj, "type", "External");
397 break;
398 default:
399 yyjson_mut_obj_add_str(doc, obj, "type", "Unknown");
400 break;
401 }
402
403 if (item->manufactureYear) {
404 yyjson_mut_val* manufactureDate = yyjson_mut_obj_add_obj(doc, obj, "manufactureDate");
405 yyjson_mut_obj_add_uint(doc, manufactureDate, "year", item->manufactureYear);
406 yyjson_mut_obj_add_uint(doc, manufactureDate, "week", item->manufactureWeek);
407 } else {
408 yyjson_mut_obj_add_null(doc, obj, "manufactureDate");
409 }
410
411 if (item->serial.length) {
412 yyjson_mut_obj_add_strbuf(doc, obj, "serial", &item->serial);
413 } else {
414 yyjson_mut_obj_add_null(doc, obj, "serial");
415 }
416
417 yyjson_mut_obj_add_str(doc, obj, "platformApi", item->platformApi);
418 }
419
420 return true;
421}
422
423void ffInitDisplayOptions(FFDisplayOptions* options) {
424 ffOptionInitModuleArg(&options->moduleArgs, "");
425 options->compactType = FF_DISPLAY_COMPACT_TYPE_NONE;
426 options->preciseRefreshRate = false;
427 options->order = FF_DISPLAY_ORDER_NONE;
428}
429
430void ffDestroyDisplayOptions(FFDisplayOptions* options) {
431 ffOptionDestroyModuleArg(&options->moduleArgs);
432}
433
434FFModuleBaseInfo ffDisplayModuleInfo = {
435 .name = "Display",
436 .description = "Print resolutions, refresh rates, etc",
437 .displayName = {
438 .en = "Display",
439 .ar = "الشاشة",
440 .cs = "Displej",
441 .de = "Anzeige",
442 .es = "Pantalla",
443 .fi = "Näyttö",
444 .fr = "Écran",
445 .gl = "Pantalla",
446 .he = "תצוגה",
447 .id = "Tampilan",
448 .it = "Display",
449 .ja = "ディスプレイ",
450 .ko = "디스플레이",
451 .pl = "Wyświetlacz",
452 .pt = "Tela",
453 .ru = "Дисплей",
454 .tr = "Ekran",
455 .uk = "Дисплей",
456 .vi = "Hiển thị",
457 .zh_CN = "显示器",
458 .zh_TW = "顯示器",
459 },
460 .initOptions = (void*) ffInitDisplayOptions,
461 .destroyOptions = (void*) ffDestroyDisplayOptions,
462 .parseJsonObject = (void*) ffParseDisplayJsonObject,
463 .printModule = (void*) ffPrintDisplay,
464 .generateJsonResult = (void*) ffGenerateDisplayJsonResult,
465 .generateJsonConfig = (void*) ffGenerateDisplayJsonConfig,
466 .formatArgs = FF_FORMAT_ARG_LIST(((FFModuleFormatArg[]) {
467 { "Screen configured width (in pixels)", "width" },
468 { "Screen configured height (in pixels)", "height" },
469 { "Screen configured refresh rate (in Hz)", "refresh-rate" },
470 { "Screen scaled width (in pixels)", "scaled-width" },
471 { "Screen scaled height (in pixels)", "scaled-height" },
472 { "Screen name *", "name" },
473 { "Screen type (Built-in or External) *", "type" },
474 { "Screen rotation (in degrees)", "rotation" },
475 { "True if being the primary screen", "is-primary" },
476 { "Screen physical width (in millimeters)", "physical-width" },
477 { "Screen physical height (in millimeters)", "physical-height" },
478 { "Physical diagonal length in inches", "inch" },
479 { "Pixels per inch (PPI)", "ppi" },
480 { "Bits per color channel", "bit-depth" },
481 { "True if high dynamic range (HDR) mode is enabled", "hdr-enabled" },
482 { "Year of manufacturing", "manufacture-year" },
483 { "Nth week of manufacturing in the year", "manufacture-week" },
484 { "Serial number", "serial" },
485 { "The platform API used when detecting the display", "platform-api" },
486 { "True if the display is HDR compatible", "hdr-compatible" },
487 { "HiDPI scale factor", "scale-factor" },
488 { "Screen preferred width (in pixels)", "preferred-width" },
489 { "Screen preferred height (in pixels)", "preferred-height" },
490 { "Screen preferred refresh rate (in Hz)", "preferred-refresh-rate" },
491 { "DPI", "dpi" },
492 })),
493 .defaultOrder = 17,
494};