1 #include <algorithm>
2 #include <cinttypes>
3 #include <cstdio>
4 #include <iostream>
5 #include <string>
6 #include <unistd.h>
7 #include <fmt/format.h>
8
9 #include <kms++/kms++.h>
10 #include <kms++util/kms++util.h>
11
12 using namespace std;
13 using namespace kms;
14
15 static struct {
16 bool print_props;
17 bool print_modes;
18 bool print_list;
19 bool x_modeline;
20 } s_opts;
21
format_mode(const Videomode & m,unsigned idx)22 static string format_mode(const Videomode& m, unsigned idx)
23 {
24 string str;
25
26 str = fmt::format(" {:2} ", idx);
27
28 if (s_opts.x_modeline) {
29 str += fmt::format("{:12} {:6} {:4} {:4} {:4} {:4} {:4} {:4} {:4} {:4} {:3} {:#x} {:#x}",
30 m.name,
31 m.clock,
32 m.hdisplay, m.hsync_start, m.hsync_end, m.htotal,
33 m.vdisplay, m.vsync_start, m.vsync_end, m.vtotal,
34 m.vrefresh,
35 m.flags,
36 m.type);
37 } else {
38 str += m.to_string_long_padded();
39 }
40
41 return str;
42 }
43
format_mode_short(const Videomode & m)44 static string format_mode_short(const Videomode& m)
45 {
46 return m.to_string_long();
47 }
48
format_connector(Connector & c)49 static string format_connector(Connector& c)
50 {
51 string str;
52
53 str = fmt::format("Connector {} ({}) {}",
54 c.idx(), c.id(), c.fullname());
55
56 switch (c.connector_status()) {
57 case ConnectorStatus::Connected:
58 str += " (connected)";
59 break;
60 case ConnectorStatus::Disconnected:
61 str += " (disconnected)";
62 break;
63 default:
64 str += " (unknown)";
65 break;
66 }
67
68 return str;
69 }
70
format_encoder(Encoder & e)71 static string format_encoder(Encoder& e)
72 {
73 return fmt::format("Encoder {} ({}) {}",
74 e.idx(), e.id(), e.get_encoder_type());
75 }
76
format_crtc(Crtc & c)77 static string format_crtc(Crtc& c)
78 {
79 string str;
80
81 str = fmt::format("Crtc {} ({})", c.idx(), c.id());
82
83 if (c.mode_valid())
84 str += " " + format_mode_short(c.mode());
85
86 return str;
87 }
88
format_plane(Plane & p)89 static string format_plane(Plane& p)
90 {
91 string str;
92
93 str = fmt::format("Plane {} ({})", p.idx(), p.id());
94
95 if (p.fb_id())
96 str += fmt::format(" fb-id: {}", p.fb_id());
97
98 string crtcs = join<Crtc*>(p.get_possible_crtcs(), " ", [](Crtc* crtc) { return to_string(crtc->idx()); });
99
100 str += fmt::format(" (crtcs: {})", crtcs);
101
102 if (p.card().has_atomic()) {
103 str += fmt::format(" {},{} {}x{} -> {},{} {}x{}",
104 (uint32_t)p.get_prop_value("SRC_X") >> 16,
105 (uint32_t)p.get_prop_value("SRC_Y") >> 16,
106 (uint32_t)p.get_prop_value("SRC_W") >> 16,
107 (uint32_t)p.get_prop_value("SRC_H") >> 16,
108 (int32_t)p.get_prop_value("CRTC_X"),
109 (int32_t)p.get_prop_value("CRTC_Y"),
110 (uint32_t)p.get_prop_value("CRTC_W"),
111 (uint32_t)p.get_prop_value("CRTC_H"));
112 }
113
114 string fmts = join<PixelFormat>(p.get_formats(), " ", [](PixelFormat fmt) { return PixelFormatToFourCC(fmt); });
115
116 str += fmt::format(" ({})", fmts);
117
118 return str;
119 }
120
format_fb(Framebuffer & fb)121 static string format_fb(Framebuffer& fb)
122 {
123 return fmt::format("FB {} {}x{} {}",
124 fb.id(), fb.width(), fb.height(),
125 PixelFormatToFourCC(fb.format()));
126 }
127
format_property(const Property * prop,uint64_t val)128 static string format_property(const Property* prop, uint64_t val)
129 {
130 string ret = fmt::format("{} ({}) = ", prop->name(), prop->id());
131
132 switch (prop->type()) {
133 case PropertyType::Bitmask: {
134 vector<string> v, vall;
135
136 for (auto kvp : prop->get_enums()) {
137 if (val & (1 << kvp.first))
138 v.push_back(kvp.second);
139 vall.push_back(fmt::format("{}={:#x}", kvp.second, 1 << kvp.first));
140 }
141
142 // XXX
143 ret += fmt::format("{:#x} ({}) [{}]", val, join(v, "|"), join(vall, "|"));
144
145 break;
146 }
147
148 case PropertyType::Blob: {
149 uint32_t blob_id = (uint32_t)val;
150
151 if (blob_id) {
152 Blob blob(prop->card(), blob_id);
153 auto data = blob.data();
154
155 ret += fmt::format("blob-id {} len {}", blob_id, data.size());
156 } else {
157 ret += fmt::format("blob-id {}", blob_id);
158 }
159
160 break;
161 }
162
163 case PropertyType::Enum: {
164 string cur;
165 vector<string> vall;
166
167 for (auto kvp : prop->get_enums()) {
168 if (val == kvp.first)
169 cur = kvp.second;
170 vall.push_back(fmt::format("{}={}", kvp.second, kvp.first));
171 }
172
173 ret += fmt::format("{} ({}) [{}]", val, cur, join(vall, "|"));
174
175 break;
176 }
177
178 case PropertyType::Object: {
179 ret += fmt::format("object id {}", val);
180 break;
181 }
182
183 case PropertyType::Range: {
184 auto values = prop->get_values();
185
186 ret += fmt::format("{} [{} - {}]",
187 val, values[0], values[1]);
188
189 break;
190 }
191
192 case PropertyType::SignedRange: {
193 auto values = prop->get_values();
194
195 ret += fmt::format("{} [{} - {}]",
196 (int64_t)val, (int64_t)values[0], (int64_t)values[1]);
197
198 break;
199 }
200 }
201
202 if (prop->is_pending())
203 ret += " (pending)";
204 if (prop->is_immutable())
205 ret += " (immutable)";
206
207 return ret;
208 }
209
format_props(DrmPropObject * o)210 static vector<string> format_props(DrmPropObject* o)
211 {
212 vector<string> lines;
213
214 auto pmap = o->get_prop_map();
215 for (auto pp : pmap) {
216 const Property* p = o->card().get_prop(pp.first);
217 lines.push_back(format_property(p, pp.second));
218 }
219
220 return lines;
221 }
222
format_ob(DrmObject * ob)223 static string format_ob(DrmObject* ob)
224 {
225 if (auto o = dynamic_cast<Connector*>(ob))
226 return format_connector(*o);
227 else if (auto o = dynamic_cast<Encoder*>(ob))
228 return format_encoder(*o);
229 else if (auto o = dynamic_cast<Crtc*>(ob))
230 return format_crtc(*o);
231 else if (auto o = dynamic_cast<Plane*>(ob))
232 return format_plane(*o);
233 else if (auto o = dynamic_cast<Framebuffer*>(ob))
234 return format_fb(*o);
235 else
236 EXIT("Unkown DRM Object type\n");
237 }
238
239 template<class T>
filter(const vector<T> & sequence,function<bool (T)> predicate)240 vector<T> filter(const vector<T>& sequence, function<bool(T)> predicate)
241 {
242 vector<T> result;
243
244 for (auto it = sequence.begin(); it != sequence.end(); ++it)
245 if (predicate(*it))
246 result.push_back(*it);
247
248 return result;
249 }
250
251 struct Entry {
252 string title;
253 vector<string> lines;
254 vector<Entry> children;
255 };
256
add_entry(vector<Entry> & entries)257 static Entry& add_entry(vector<Entry>& entries)
258 {
259 entries.emplace_back();
260 return entries.back();
261 }
262 /*
263 static bool on_tty()
264 {
265 return isatty(STDOUT_FILENO) > 0;
266 }
267 */
268 enum class TreeGlyphMode {
269 None,
270 ASCII,
271 UTF8,
272 };
273
274 static TreeGlyphMode s_glyph_mode = TreeGlyphMode::None;
275
276 enum class TreeGlyph {
277 Vertical,
278 Branch,
279 Right,
280 Space,
281 };
282
283 static const map<TreeGlyph, string> glyphs_utf8 = {
284 { TreeGlyph::Vertical, "│ " },
285 { TreeGlyph::Branch, "├─" },
286 { TreeGlyph::Right, "└─" },
287 { TreeGlyph::Space, " " },
288
289 };
290
291 static const map<TreeGlyph, string> glyphs_ascii = {
292 { TreeGlyph::Vertical, "| " },
293 { TreeGlyph::Branch, "|-" },
294 { TreeGlyph::Right, "`-" },
295 { TreeGlyph::Space, " " },
296
297 };
298
get_glyph(TreeGlyph glyph)299 const string& get_glyph(TreeGlyph glyph)
300 {
301 static const string s_empty = " ";
302
303 if (s_glyph_mode == TreeGlyphMode::None)
304 return s_empty;
305
306 const map<TreeGlyph, string>& glyphs = s_glyph_mode == TreeGlyphMode::UTF8 ? glyphs_utf8 : glyphs_ascii;
307
308 return glyphs.at(glyph);
309 }
310
print_entry(const Entry & e,const string & prefix,bool is_child,bool is_last)311 static void print_entry(const Entry& e, const string& prefix, bool is_child, bool is_last)
312 {
313 string prefix1;
314 string prefix2;
315
316 if (is_child) {
317 prefix1 = prefix + (is_last ? get_glyph(TreeGlyph::Right) : get_glyph(TreeGlyph::Branch));
318 prefix2 = prefix + (is_last ? get_glyph(TreeGlyph::Space) : get_glyph(TreeGlyph::Vertical));
319 }
320
321 fmt::print("{}{}\n", prefix1, e.title);
322
323 bool has_children = e.children.size() > 0;
324
325 string data_prefix = prefix2 + (has_children ? get_glyph(TreeGlyph::Vertical) : get_glyph(TreeGlyph::Space));
326
327 for (const string& str : e.lines) {
328 string p = data_prefix + get_glyph(TreeGlyph::Space);
329 fmt::print("{}{}\n", p, str);
330 }
331
332 for (const Entry& child : e.children) {
333 bool is_last = &child == &e.children.back();
334
335 print_entry(child, prefix2, true, is_last);
336 }
337 }
338
print_entries(const vector<Entry> & entries,const string & prefix)339 static void print_entries(const vector<Entry>& entries, const string& prefix)
340 {
341 for (const Entry& e : entries) {
342 print_entry(e, "", false, false);
343 }
344 }
345
346 template<class T>
append(vector<DrmObject * > & dst,const vector<T * > & src)347 static void append(vector<DrmObject*>& dst, const vector<T*>& src)
348 {
349 dst.insert(dst.end(), src.begin(), src.end());
350 }
351
print_as_list(Card & card)352 static void print_as_list(Card& card)
353 {
354 vector<DrmPropObject*> obs;
355 vector<Framebuffer*> fbs;
356
357 for (Connector* conn : card.get_connectors()) {
358 obs.push_back(conn);
359 }
360
361 for (Encoder* enc : card.get_encoders()) {
362 obs.push_back(enc);
363 }
364
365 for (Crtc* crtc : card.get_crtcs()) {
366 obs.push_back(crtc);
367 if (crtc->buffer_id() && !card.has_universal_planes()) {
368 Framebuffer* fb = new Framebuffer(card, crtc->buffer_id());
369 fbs.push_back(fb);
370 }
371 }
372
373 for (Plane* plane : card.get_planes()) {
374 obs.push_back(plane);
375 if (plane->fb_id()) {
376 Framebuffer* fb = new Framebuffer(card, plane->fb_id());
377 fbs.push_back(fb);
378 }
379 }
380
381 for (DrmPropObject* ob : obs) {
382 fmt::print("{}\n", format_ob(ob));
383
384 if (s_opts.print_props) {
385 for (string str : format_props(ob))
386 fmt::print(" {}\n", str);
387 }
388 }
389
390 for (Framebuffer* fb : fbs) {
391 fmt::print("{}\n", format_ob(fb));
392 }
393 }
394
print_as_tree(Card & card)395 static void print_as_tree(Card& card)
396 {
397 vector<Entry> entries;
398
399 for (Connector* conn : card.get_connectors()) {
400 Entry& e1 = add_entry(entries);
401 e1.title = format_ob(conn);
402 if (s_opts.print_props)
403 e1.lines = format_props(conn);
404
405 for (Encoder* enc : conn->get_encoders()) {
406 Entry& e2 = add_entry(e1.children);
407 e2.title = format_ob(enc);
408 if (s_opts.print_props)
409 e2.lines = format_props(enc);
410
411 if (Crtc* crtc = enc->get_crtc()) {
412 Entry& e3 = add_entry(e2.children);
413 e3.title = format_ob(crtc);
414 if (s_opts.print_props)
415 e3.lines = format_props(crtc);
416
417 if (crtc->buffer_id() && !card.has_universal_planes()) {
418 Framebuffer fb(card, crtc->buffer_id());
419 Entry& e5 = add_entry(e3.children);
420
421 e5.title = format_ob(&fb);
422 }
423
424 for (Plane* plane : card.get_planes()) {
425 if (plane->crtc_id() != crtc->id())
426 continue;
427
428 Entry& e4 = add_entry(e3.children);
429 e4.title = format_ob(plane);
430 if (s_opts.print_props)
431 e4.lines = format_props(plane);
432
433 uint32_t fb_id = plane->fb_id();
434 if (fb_id) {
435 Framebuffer fb(card, fb_id);
436
437 Entry& e5 = add_entry(e4.children);
438
439 e5.title = format_ob(&fb);
440 }
441 }
442 }
443 }
444 }
445
446 print_entries(entries, "");
447 }
448
print_modes(Card & card)449 static void print_modes(Card& card)
450 {
451 for (Connector* conn : card.get_connectors()) {
452 if (!conn->connected())
453 continue;
454
455 fmt::print("{}\n", format_ob(conn));
456
457 auto modes = conn->get_modes();
458 for (unsigned i = 0; i < modes.size(); ++i)
459 fmt::print("{}\n", format_mode(modes[i], i));
460 }
461 }
462
463 static const char* usage_str =
464 "Usage: kmsprint [OPTIONS]\n\n"
465 "Options:\n"
466 " --device=DEVICE DEVICE is the path to DRM card to open\n"
467 " -l, --list Print list instead of tree\n"
468 " -m, --modes Print modes\n"
469 " --xmode Print modes using X modeline\n"
470 " -p, --props Print properties\n";
471
usage()472 static void usage()
473 {
474 puts(usage_str);
475 }
476
main(int argc,char ** argv)477 int main(int argc, char** argv)
478 {
479 string dev_path;
480
481 OptionSet optionset = {
482 Option("|device=", [&dev_path](string s) {
483 dev_path = s;
484 }),
485 Option("l|list", []() {
486 s_opts.print_list = true;
487 }),
488 Option("m|modes", []() {
489 s_opts.print_modes = true;
490 }),
491 Option("p|props", []() {
492 s_opts.print_props = true;
493 }),
494 Option("|xmode", []() {
495 s_opts.x_modeline = true;
496 }),
497 Option("h|help", []() {
498 usage();
499 exit(-1);
500 }),
501 };
502
503 optionset.parse(argc, argv);
504
505 if (optionset.params().size() > 0) {
506 usage();
507 exit(-1);
508 }
509
510 Card card(dev_path);
511
512 if (s_opts.print_modes) {
513 print_modes(card);
514 return 0;
515 }
516
517 if (s_opts.print_list)
518 print_as_list(card);
519 else
520 print_as_tree(card);
521 }
522