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(7, 11), 2)),
44 (char) (64 + Integer.parseInt(temp.substring(12, 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 public static String toString(byte[] edid) {
212 StringBuilder sb = new StringBuilder();
213 sb.append(" Manuf. ID=").append(EdidUtil.getManufacturerID(edid));
214 sb.append(", Product ID=").append(EdidUtil.getProductID(edid));
215 sb.append(", ").append(EdidUtil.isDigital(edid) ? "Digital" : "Analog");
216 sb.append(", Serial=").append(EdidUtil.getSerialNo(edid));
217 sb.append(", ManufDate=").append(EdidUtil.getWeek(edid) * 12 / 52 + 1).append('/')
218 .append(EdidUtil.getYear(edid));
219 sb.append(", EDID v").append(EdidUtil.getVersion(edid));
220 int hSize = EdidUtil.getHcm(edid);
221 int vSize = EdidUtil.getVcm(edid);
222 sb.append(String.format(Locale.ROOT, "%n %d x %d cm (%.1f x %.1f in)", hSize, vSize, hSize / 2.54,
223 vSize / 2.54));
224 byte[][] desc = EdidUtil.getDescriptors(edid);
225 for (byte[] b : desc) {
226 switch (EdidUtil.getDescriptorType(b)) {
227 case 0xff:
228 sb.append("\n Serial Number: ").append(EdidUtil.getDescriptorText(b));
229 break;
230 case 0xfe:
231 sb.append("\n Unspecified Text: ").append(EdidUtil.getDescriptorText(b));
232 break;
233 case 0xfd:
234 sb.append("\n Range Limits: ").append(EdidUtil.getDescriptorRangeLimits(b));
235 break;
236 case 0xfc:
237 sb.append("\n Monitor Name: ").append(EdidUtil.getDescriptorText(b));
238 break;
239 case 0xfb:
240 sb.append("\n White Point Data: ").append(ParseUtil.byteArrayToHexString(b));
241 break;
242 case 0xfa:
243 sb.append("\n Standard Timing ID: ").append(ParseUtil.byteArrayToHexString(b));
244 break;
245 default:
246 if (EdidUtil.getDescriptorType(b) <= 0x0f && EdidUtil.getDescriptorType(b) >= 0x00) {
247 sb.append("\n Manufacturer Data: ").append(ParseUtil.byteArrayToHexString(b));
248 } else {
249 sb.append("\n Preferred Timing: ").append(EdidUtil.getTimingDescriptor(b));
250 }
251 break;
252 }
253 }
254 return sb.toString();
255 }
256 }