fix(settings): show Mac product name and physical screen resolution

This commit is contained in:
tianma-if committed 2026-09-20 22:46:06 +08:00
1 parent ac142b2af5
commit 10e34dbc6f
6 files changed
+111 -6

No files matched your search

+33 -1
View File
@@ -6,6 +6,36 @@ const trimModel = (value) => {
return text || null;
};
const parseIoregQuotedProperty = (output, key) => {
const match = String(output ?? "").match(new RegExp(`"${key}"\\s*=\\s*(?:<"([^"]+)">|"([^"]+)")`));
return trimModel(match?.[1] || match?.[2]);
};
const readDarwinMarketingName = (execFile) => {
try {
const productTree = execFile("ioreg", ["-p", "IODeviceTree", "-n", "product", "-rd1"], {
encoding: "utf8",
timeout: 2_000,
});
const productName = parseIoregQuotedProperty(productTree, "product-name");
if (productName) return productName;
} catch {
// Older Macs may not expose the device-tree product node.
}
try {
const profile = JSON.parse(execFile("system_profiler", ["SPHardwareDataType", "-json"], {
encoding: "utf8",
timeout: 5_000,
}));
const hardware = Array.isArray(profile?.SPHardwareDataType) ? profile.SPHardwareDataType[0] : null;
const machineName = trimModel(hardware?.machine_name);
if (machineName) return machineName;
} catch {
// system_profiler is a compatibility fallback for machines without IOKit product-name.
}
return null;
};
export const readDesktopDeviceModel = ({
execFileSync: execFile = execFileSync,
platform = process.platform,
@@ -13,7 +43,9 @@ export const readDesktopDeviceModel = ({
} = {}) => {
try {
if (platform === "darwin") {
return trimModel(execFile("sysctl", ["-n", "hw.model"], { encoding: "utf8", timeout: 2_000 }));
// hw.model is a board identifier (Mac16,11); product-name is the user-facing model.
return readDarwinMarketingName(execFile)
|| trimModel(execFile("sysctl", ["-n", "hw.model"], { encoding: "utf8", timeout: 2_000 }));
}
if (platform === "win32") {
return trimModel(execFile("powershell.exe", [
@@ -2,9 +2,43 @@ import { describe, expect, test } from "bun:test";
import { readDesktopDeviceModel } from "./desktop-device-model.mjs";
describe("desktop device model", () => {
test("reads the macOS hardware model identifier", () => {
test("prefers the macOS IOKit product name over the board identifier", () => {
expect(readDesktopDeviceModel({
execFileSync: (command, args) => {
expect(command).toBe("ioreg");
expect(args).toEqual(["-p", "IODeviceTree", "-n", "product", "-rd1"]);
return `
+-o product <class IOPlatformDevice>
{
"product-description" = <"Mac mini (2024)">
"product-name" = <"Mac mini (2024)">
"sub-product-type" = <"Mac16,11">
}
`;
},
platform: "darwin",
})).toBe("Mac mini (2024)");
});
test("falls back to system_profiler machine name when IOKit product-name is missing", () => {
expect(readDesktopDeviceModel({
execFileSync: (command, args) => {
if (command === "ioreg") return `"model" = <"Mac16,7">\n`;
expect(command).toBe("system_profiler");
expect(args).toEqual(["SPHardwareDataType", "-json"]);
return JSON.stringify({
SPHardwareDataType: [{ machine_model: "Mac16,7", machine_name: "MacBook Pro" }],
});
},
platform: "darwin",
})).toBe("MacBook Pro");
});
test("falls back to the macOS hardware model identifier", () => {
expect(readDesktopDeviceModel({
execFileSync: (command, args) => {
if (command === "ioreg") throw new Error("ioreg unavailable");
if (command === "system_profiler") throw new Error("system_profiler unavailable");
expect(command).toBe("sysctl");
expect(args).toEqual(["-n", "hw.model"]);
return "Mac16,7\n";
@@ -44,6 +44,7 @@ describe("system information diagnostic fields", () => {
expect(source).toContain("readBrowserClientDisplaySize");
expect(source).toContain("getClientDisplaySizeParts");
expect(source).toContain('window.addEventListener("resize", onResize)');
expect(readFileSync(new URL("../../lib/system-diagnostics.ts", import.meta.url), "utf8")).toContain("toDevicePixelScreenSize");
});
test("distinguishes empty sync from never synced", () => {
+3 -3
View File
@@ -1,4 +1,4 @@
import { summarizeSyncQueue, type ClientDisplaySizeInput } from "@edgeever/shared";
import { summarizeSyncQueue, toDevicePixelScreenSize, type ClientDisplaySizeInput } from "@edgeever/shared";
import { api, getConfiguredDesktopApiBaseUrl } from "./api";
import { localDb } from "./local-db";
import { createLocalDataScope } from "./local-mirror";
@@ -66,13 +66,13 @@ const browserOperatingSystem = (userAgent: string, platform: string, platformVer
export const readBrowserClientDisplaySize = (): ClientDisplaySizeInput | null => {
if (typeof window === "undefined" || typeof screen === "undefined") return null;
return {
return toDevicePixelScreenSize({
devicePixelRatio: Number(window.devicePixelRatio) || 1,
screenHeight: Number(screen.height),
screenWidth: Number(screen.width),
windowHeight: Number(window.innerHeight),
windowWidth: Number(window.innerWidth),
};
});
};
const desktopOperatingSystem = (platform: string, version: string) => {
@@ -1,5 +1,5 @@
import { describe, expect, test } from "bun:test";
import { formatClientDisplaySize, formatDevicePixelRatio, getClientDisplaySizeParts } from "./client-display-size";
import { formatClientDisplaySize, formatDevicePixelRatio, getClientDisplaySizeParts, toDevicePixelScreenSize } from "./client-display-size";
const chineseTemplate = "{{screen}} @{{dpr}}x(窗口 {{windowSize}})";
const englishTemplate = "{{screen}} @{{dpr}}x (window {{windowSize}})";
@@ -49,6 +49,29 @@ describe("client display size formatting", () => {
}, chineseTemplate)).toBe("1512×981 @2x(窗口 1279×800)");
});
test("scales CSS screen size to device pixels and keeps window size in CSS pixels", () => {
expect(toDevicePixelScreenSize({
devicePixelRatio: 2,
screenHeight: 1080,
screenWidth: 1920,
windowHeight: 925,
windowWidth: 1920,
})).toEqual({
devicePixelRatio: 2,
screenHeight: 2160,
screenWidth: 3840,
windowHeight: 925,
windowWidth: 1920,
});
expect(formatClientDisplaySize(toDevicePixelScreenSize({
devicePixelRatio: 2,
screenHeight: 1080,
screenWidth: 1920,
windowHeight: 925,
windowWidth: 1920,
}), chineseTemplate)).toBe("3840×2160 @2x(窗口 1920×925)");
});
test("returns null when a display metric is missing", () => {
expect(formatClientDisplaySize({
devicePixelRatio: 2,
@@ -23,6 +23,21 @@ export const formatDevicePixelRatio = (value: number) => {
return Number.isInteger(rounded) ? String(rounded) : String(rounded);
};
const scaleCssPixelsToDevicePixels = (cssPixels: number, devicePixelRatio: number) => {
if (!Number.isFinite(cssPixels) || cssPixels <= 0) return cssPixels;
if (!Number.isFinite(devicePixelRatio) || devicePixelRatio <= 0) return cssPixels;
return cssPixels * devicePixelRatio;
};
// Browser `screen` APIs report CSS pixels; system info shows the device-pixel panel size.
export const toDevicePixelScreenSize = (input: ClientDisplaySizeInput): ClientDisplaySizeInput => ({
devicePixelRatio: input.devicePixelRatio,
screenHeight: scaleCssPixelsToDevicePixels(input.screenHeight, input.devicePixelRatio),
screenWidth: scaleCssPixelsToDevicePixels(input.screenWidth, input.devicePixelRatio),
windowHeight: input.windowHeight,
windowWidth: input.windowWidth,
});
export const getClientDisplaySizeParts = (
input: ClientDisplaySizeInput,
): ClientDisplaySizeParts | null => {