1
2
3
4
5 package oshi.util;
6
7 import java.nio.ByteBuffer;
8 import java.nio.ByteOrder;
9 import java.nio.charset.StandardCharsets;
10 import java.util.Arrays;
11 import java.util.Locale;
12
13 import org.slf4j.Logger;
14 import org.slf4j.LoggerFactory;
15
16 import oshi.annotation.SuppressForbidden;
17 import oshi.annotation.concurrent.ThreadSafe;
18
19
20
21
22 @ThreadSafe
23 public final class EdidUtil {
24
25 private static final Logger LOG = LoggerFactory.getLogger(EdidUtil.class);
26
27 private EdidUtil() {
28 }
29
30
31
32
33
34
35
36 @SuppressForbidden(reason = "customized base 2 parsing not in Util class")
37 public static String getManufacturerID(byte[] edid) {
38
39 String temp = String.format(Locale.ROOT, "%8s%8s", Integer.toBinaryString(edid[8] & 0xFF),
40 Integer.toBinaryString(edid[9] & 0xFF)).replace(' ', '0');
41 LOG.debug("Manufacurer ID: {}", temp);
42 return String.format(Locale.ROOT, "%s%s%s", (char) (64 + Integer.parseInt(temp.substring(1, 6), 2)),
43 (char) (64 + Integer.parseInt(temp.substring(6, 11), 2)),
44 (char) (64 + Integer.parseInt(temp.substring(11, 16), 2))).replace("@", "");
45 }
46
47
48
49
50
51
52
53 public static String getProductID(byte[] edid) {
54
55 return Integer.toHexString(
56 ByteBuffer.wrap(Arrays.copyOfRange(edid, 10, 12)).order(ByteOrder.LITTLE_ENDIAN).getShort() & 0xffff);
57 }
58
59
60
61
62
63
64
65 public static String getSerialNo(byte[] edid) {
66
67 if (LOG.isDebugEnabled()) {
68 LOG.debug("Serial number: {}", Arrays.toString(Arrays.copyOfRange(edid, 12, 16)));
69 }
70 return String.format(Locale.ROOT, "%s%s%s%s", getAlphaNumericOrHex(edid[15]), getAlphaNumericOrHex(edid[14]),
71 getAlphaNumericOrHex(edid[13]), getAlphaNumericOrHex(edid[12]));
72 }
73
74 private static String getAlphaNumericOrHex(byte b) {
75 return Character.isLetterOrDigit((char) b) ? String.format(Locale.ROOT, "%s", (char) b)
76 : String.format(Locale.ROOT, "%02X", b);
77 }
78
79
80
81
82
83
84
85 public static byte getWeek(byte[] edid) {
86
87 return edid[16];
88 }
89
90
91
92
93
94
95
96 public static int getYear(byte[] edid) {
97
98 byte temp = edid[17];
99 LOG.debug("Year-1990: {}", temp);
100 return temp + 1990;
101 }
102
103
104
105
106
107
108
109 public static String getVersion(byte[] edid) {
110
111 return edid[18] + "." + edid[19];
112 }
113
114
115
116
117
118
119
120 public static boolean isDigital(byte[] edid) {
121
122 return 1 == (edid[20] & 0xff) >> 7;
123 }
124
125
126
127
128
129
130
131 public static int getHcm(byte[] edid) {
132
133 return edid[21];
134 }
135
136
137
138
139
140
141
142 public static int getVcm(byte[] edid) {
143
144 return edid[22];
145 }
146
147
148
149
150
151
152
153 public static byte[][] getDescriptors(byte[] edid) {
154 byte[][] desc = new byte[4][18];
155 for (int i = 0; i < desc.length; i++) {
156 System.arraycopy(edid, 54 + 18 * i, desc[i], 0, 18);
157 }
158 return desc;
159 }
160
161
162
163
164
165
166
167 public static int getDescriptorType(byte[] desc) {
168 return ByteBuffer.wrap(Arrays.copyOfRange(desc, 0, 4)).getInt();
169 }
170
171
172
173
174
175
176
177 public static String getTimingDescriptor(byte[] desc) {
178 int clock = ByteBuffer.wrap(Arrays.copyOfRange(desc, 0, 2)).order(ByteOrder.LITTLE_ENDIAN).getShort() / 100;
179 int hActive = (desc[2] & 0xff) + ((desc[4] & 0xf0) << 4);
180 int vActive = (desc[5] & 0xff) + ((desc[7] & 0xf0) << 4);
181 return String.format(Locale.ROOT, "Clock %dMHz, Active Pixels %dx%d ", clock, hActive, vActive);
182 }
183
184
185
186
187
188
189
190 public static String getDescriptorRangeLimits(byte[] desc) {
191 return String.format(Locale.ROOT, "Field Rate %d-%d Hz vertical, %d-%d Hz horizontal, Max clock: %d MHz",
192 desc[5], desc[6], desc[7], desc[8], desc[9] * 10);
193 }
194
195
196
197
198
199
200
201 public static String getDescriptorText(byte[] desc) {
202 return new String(Arrays.copyOfRange(desc, 4, 18), StandardCharsets.US_ASCII).trim();
203 }
204
205
206
207
208
209
210
211
212 public static String getPreferredResolution(byte[] edid) {
213 int dtd = 54;
214 int horizontalRes = (edid[dtd + 4] & 0xF0) << 4 | edid[dtd + 2] & 0xFF;
215 int verticalRes = (edid[dtd + 7] & 0xF0) << 4 | edid[dtd + 5] & 0xFF;
216 return horizontalRes + "x" + verticalRes;
217 }
218
219
220
221
222
223
224
225
226 public static String getModel(byte[] edid) {
227
228 byte[][] desc = EdidUtil.getDescriptors(edid);
229 String model = null;
230
231 for (byte[] b : desc) {
232
233 if (EdidUtil.getDescriptorType(b) == 0xfc) {
234 model = EdidUtil.getDescriptorText(b);
235 break;
236 }
237 }
238
239 assert model != null;
240 String[] tokens = model.split("\\s+");
241 if (tokens.length >= 1) {
242 model = tokens[tokens.length - 1];
243 }
244 return model.trim();
245 }
246
247
248
249
250
251
252
253 public static String toString(byte[] edid) {
254 StringBuilder sb = new StringBuilder();
255 sb.append(" Manuf. ID=").append(EdidUtil.getManufacturerID(edid));
256 sb.append(", Product ID=").append(EdidUtil.getProductID(edid));
257 sb.append(", ").append(EdidUtil.isDigital(edid) ? "Digital" : "Analog");
258 sb.append(", Serial=").append(EdidUtil.getSerialNo(edid));
259 sb.append(", ManufDate=").append(EdidUtil.getWeek(edid) * 12 / 52 + 1).append('/')
260 .append(EdidUtil.getYear(edid));
261 sb.append(", EDID v").append(EdidUtil.getVersion(edid));
262 int hSize = EdidUtil.getHcm(edid);
263 int vSize = EdidUtil.getVcm(edid);
264 sb.append(String.format(Locale.ROOT, "%n %d x %d cm (%.1f x %.1f in)", hSize, vSize, hSize / 2.54,
265 vSize / 2.54));
266 byte[][] desc = EdidUtil.getDescriptors(edid);
267 for (byte[] b : desc) {
268 switch (EdidUtil.getDescriptorType(b)) {
269 case 0xff:
270 sb.append("\n Serial Number: ").append(EdidUtil.getDescriptorText(b));
271 break;
272 case 0xfe:
273 sb.append("\n Unspecified Text: ").append(EdidUtil.getDescriptorText(b));
274 break;
275 case 0xfd:
276 sb.append("\n Range Limits: ").append(EdidUtil.getDescriptorRangeLimits(b));
277 break;
278 case 0xfc:
279 sb.append("\n Monitor Name: ").append(EdidUtil.getDescriptorText(b));
280 break;
281 case 0xfb:
282 sb.append("\n White Point Data: ").append(ParseUtil.byteArrayToHexString(b));
283 break;
284 case 0xfa:
285 sb.append("\n Standard Timing ID: ").append(ParseUtil.byteArrayToHexString(b));
286 break;
287 default:
288 if (EdidUtil.getDescriptorType(b) <= 0x0f && EdidUtil.getDescriptorType(b) >= 0x00) {
289 sb.append("\n Manufacturer Data: ").append(ParseUtil.byteArrayToHexString(b));
290 } else {
291 sb.append("\n Preferred Timing: ").append(EdidUtil.getTimingDescriptor(b));
292 }
293 break;
294 }
295 }
296 return sb.toString();
297 }
298 }