/* * Copyright © 2009 CNRS * Copyright © 2009-2018 Inria. All rights reserved. * Copyright © 2009-2010, 2014, 2017 Université Bordeaux * Copyright © 2009-2011 Cisco Systems, Inc. All rights reserved. * See COPYING in top-level directory. */ #include #include #ifdef CAIRO_HAS_PDF_SURFACE #include #endif /* CAIRO_HAS_PDF_SURFACE */ #ifdef CAIRO_HAS_PS_SURFACE #include #endif /* CAIRO_HAS_PS_SURFACE */ #ifdef CAIRO_HAS_SVG_SURFACE #include #endif /* CAIRO_HAS_SVG_SURFACE */ #ifndef HWLOC_HAVE_X11_KEYSYM /* In case X11 headers aren't availble, forcefully disable Cairo/Xlib. */ # undef CAIRO_HAS_XLIB_SURFACE #endif #ifdef CAIRO_HAS_XLIB_SURFACE #include #include #include #include #include /* Avoid Xwindow's definition conflict with Windows' use for fields names. */ #undef Status #endif /* CAIRO_HAS_XLIB_SURFACE */ #include #include #include #include #include #include "lstopo.h" #if (defined CAIRO_HAS_XLIB_SURFACE) + (defined CAIRO_HAS_PNG_FUNCTIONS) + (defined CAIRO_HAS_PDF_SURFACE) + (defined CAIRO_HAS_PS_SURFACE) + (defined CAIRO_HAS_SVG_SURFACE) struct lstopo_cairo_output { struct lstopo_output loutput; /* must be at the beginning */ cairo_surface_t *surface; cairo_t *context; unsigned max_x; unsigned max_y; int drawing; }; /* Cairo methods */ static void topo_cairo_box(void *_output, int r, int g, int b, unsigned depth __hwloc_attribute_unused, unsigned x, unsigned width, unsigned y, unsigned height) { struct lstopo_cairo_output *coutput = _output; cairo_t *c = coutput->context; if (x > coutput->max_x) coutput->max_x = x; if (x + width > coutput->max_x) coutput->max_x = x + width; if (y > coutput->max_y) coutput->max_y = y; if (y + height > coutput->max_y) coutput->max_y = y + height; if (!coutput->drawing) return; cairo_rectangle(c, x, y, width, height); cairo_set_source_rgb(c, (float)r / 255, (float) g / 255, (float) b / 255); cairo_fill(c); cairo_rectangle(c, x, y, width, height); cairo_set_source_rgb(c, 0, 0, 0); cairo_set_line_width(c, 1); cairo_stroke(c); } static void topo_cairo_line(void *_output, int r, int g, int b, unsigned depth __hwloc_attribute_unused, unsigned x1, unsigned y1, unsigned x2, unsigned y2) { struct lstopo_cairo_output *coutput = _output; cairo_t *c = coutput->context; if (x1 > coutput->max_x) coutput->max_x = x1; if (x2 > coutput->max_x) coutput->max_x = x2; if (y1 > coutput->max_y) coutput->max_y = y1; if (y2 > coutput->max_y) coutput->max_y = y2; if (!coutput->drawing) return; cairo_move_to(c, x1, y1); cairo_set_source_rgb(c, (float) r / 255, (float) g / 255, (float) b / 255); cairo_set_line_width(c, 1); cairo_line_to(c, x2, y2); cairo_stroke(c); } static void topo_cairo_text(void *_output, int r, int g, int b, unsigned depth __hwloc_attribute_unused, unsigned x, unsigned y, const char *text) { struct lstopo_cairo_output *coutput = _output; cairo_t *c = coutput->context; if (!coutput->drawing) return; cairo_move_to(c, x, y + fontsize); cairo_set_source_rgb(c, (float)r / 255, (float) g / 255, (float) b / 255); cairo_show_text(c, text); } static void topo_cairo_textsize(void *_output, const char *text, unsigned textlength __hwloc_attribute_unused, unsigned *width) { struct lstopo_cairo_output *coutput = _output; cairo_t *c = coutput->context; cairo_text_extents_t extents; cairo_text_extents(c, text, &extents); *width = extents.width; } #if (defined CAIRO_HAS_PNG_FUNCTIONS) + (defined CAIRO_HAS_PDF_SURFACE) + (defined CAIRO_HAS_PS_SURFACE) + (defined CAIRO_HAS_SVG_SURFACE) static cairo_status_t topo_cairo_write(void *closure, const unsigned char *data, unsigned int length) { if (fwrite(data, length, 1, closure) < 1) return CAIRO_STATUS_WRITE_ERROR; return CAIRO_STATUS_SUCCESS; } #endif /* (CAIRO_HAS_PNG_FUNCTIONS + CAIRO_HAS_PDF_SURFACE + CAIRO_HAS_PS_SURFACE + CAIRO_HAS_SVG_SURFACE) */ static void topo_cairo_paint(struct lstopo_cairo_output *coutput) { cairo_surface_t *cs = coutput->surface; cairo_t *c = cairo_create(cs); coutput->context = c; cairo_set_font_size(c, fontsize); output_draw(&coutput->loutput); cairo_show_page(c); cairo_destroy(c); coutput->context = NULL; } static void null_declare_color (void *output __hwloc_attribute_unused, int r __hwloc_attribute_unused, int g __hwloc_attribute_unused, int b __hwloc_attribute_unused) {} #endif /* (CAIRO_HAS_XLIB_SURFACE + CAIRO_HAS_PNG_FUNCTIONS + CAIRO_HAS_PDF_SURFACE + CAIRO_HAS_PS_SURFACE + CAIRO_HAS_SVG_SURFACE) */ #ifdef CAIRO_HAS_XLIB_SURFACE /* X11 back-end */ static struct draw_methods x11_draw_methods; struct lstopo_x11_output { struct lstopo_cairo_output coutput; /* must be at the beginning */ Display *dpy; int scr; Window top, win; Cursor hand; unsigned int orig_fontsize, orig_gridsize; int screen_width, screen_height; /** visible part size */ int last_screen_width, last_screen_height; /** last visible part size */ int width, height; /** total normal display size */ int x, y; /** top left corner of the visible part */ float scale, last_scale; }; static void x11_create(struct lstopo_x11_output *disp, int width, int height) { disp->win = XCreateSimpleWindow(disp->dpy, disp->top, 0, 0, width, height, 0, WhitePixel(disp->dpy, disp->scr), WhitePixel(disp->dpy, disp->scr)); disp->hand = XCreateFontCursor(disp->dpy, XC_fleur); XDefineCursor(disp->dpy, disp->win, disp->hand); XSelectInput(disp->dpy, disp->win, KeyPressMask | ButtonPressMask | ButtonReleaseMask | PointerMotionMask | ExposureMask); XMapWindow(disp->dpy, disp->win); disp->coutput.surface = cairo_xlib_surface_create(disp->dpy, disp->win, DefaultVisual(disp->dpy, disp->scr), width, height); } static void x11_destroy(struct lstopo_x11_output *disp) { cairo_surface_destroy(disp->coutput.surface); XDestroyWindow(disp->dpy, disp->win); } static void x11_init(void *_disp) { struct lstopo_x11_output *disp = _disp; struct lstopo_cairo_output *coutput = &disp->coutput; Display *dpy; Window root, top; int scr; Screen *screen; int screen_width, screen_height; unsigned int dpi_x, dpi_y, dpi; /* create the toplevel window */ if (!(dpy = XOpenDisplay(NULL))) { fprintf(stderr, "couldn't connect to X\n"); exit(EXIT_FAILURE); } disp->dpy = dpy; disp->scr = scr = DefaultScreen(dpy); screen = ScreenOfDisplay(dpy, scr); /* 25.4mm per inch */ dpi_x = ((double) DisplayWidth(dpy, scr) * 25.4) / DisplayWidthMM(dpy, scr); dpi_y = ((double) DisplayHeight(dpy, scr) * 25.4) / DisplayHeightMM(dpy, scr); dpi = (dpi_x + dpi_y) / 2; /* Original values for fontsize/gridsize were tuned for 96dpi */ fontsize = (fontsize * dpi) / 96; gridsize = (gridsize * dpi) / 96; /* compute the maximal needed size using the root window */ root = RootWindow(dpy, scr); disp->top = root; coutput->drawing = 0; x11_create(disp, 1, 1); topo_cairo_paint(coutput); x11_destroy(disp); coutput->drawing = 1; /* now create the actual window with the computed max size */ screen_width = coutput->max_x; screen_height = coutput->max_y; disp->top = top = XCreateSimpleWindow(dpy, root, 0, 0, screen_width, screen_height, 0, WhitePixel(dpy, scr), WhitePixel(dpy, scr)); XStoreName(dpy, top, "lstopo"); XSetIconName(dpy, top, "lstopo"); XSelectInput(dpy,top, StructureNotifyMask | KeyPressMask); if (screen_width >= screen->width) screen_width = screen->width; if (screen_height >= screen->height) screen_height = screen->height; disp->last_screen_width = 0; disp->last_screen_height = 0; disp->screen_width = screen_width; disp->screen_height = screen_height; disp->width = coutput->max_x; disp->height = coutput->max_y; disp->orig_fontsize = fontsize; disp->orig_gridsize = gridsize; disp->x = 0; disp->y = 0; disp->scale = disp->last_scale = 1.0f; /* TODO: if window got truncated, scale down? */ x11_create(disp, coutput->max_x, coutput->max_y); XMapWindow(dpy, top); printf("\n"); printf("Keyboard shortcuts:\n"); printf(" Zoom-in or out .................... + -\n"); printf(" Try to fit scale to window ........ f F\n"); printf(" Reset scale to default ............ 1\n"); printf(" Scroll vertically ................. Up Down PageUp PageDown\n"); printf(" Scroll horizontally ............... Left Right Ctrl+PageUp/Down\n"); printf(" Scroll to the top-left corner ..... Home\n"); printf(" Scroll to the bottom-right corner . End\n"); printf(" Exit .............................. q Q Esc\n"); printf("\n\n"); } static struct draw_methods x11_draw_methods = { x11_init, null_declare_color, topo_cairo_box, topo_cairo_line, topo_cairo_text, topo_cairo_textsize, }; /** Clip coordinates of the visible part. */ static void move_x11(struct lstopo_x11_output *disp) { if (disp->scale != disp->last_scale) { disp->x = disp->scale / disp->last_scale * (float)disp->x; disp->y = disp->scale / disp->last_scale * (float)disp->y; } if (disp->screen_width != disp->last_screen_width || disp->screen_height != disp->last_screen_height || disp->scale != disp->last_scale) { disp->last_screen_width = disp->screen_width; disp->last_screen_height = disp->screen_height; disp->last_scale = disp->scale; fontsize = disp->orig_fontsize * disp->scale; gridsize = disp->orig_gridsize * disp->scale; x11_destroy(disp); x11_create(disp, disp->width, disp->height); disp->coutput.max_x = 0; disp->coutput.max_y = 0; topo_cairo_paint(&disp->coutput); if (disp->coutput.max_x > (unsigned) disp->width || disp->coutput.max_y > (unsigned) disp->height) { /* need to extend the window and redraw */ x11_destroy(disp); x11_create(disp, disp->coutput.max_x, disp->coutput.max_y); topo_cairo_paint(&disp->coutput); } disp->width = disp->coutput.max_x; disp->height = disp->coutput.max_y; } if (disp->width <= disp->screen_width) { disp->x = 0; } else { if (disp->x < 0) disp->x = 0; if (disp->x >= disp->width - disp->screen_width) disp->x = disp->width - disp->screen_width; } if (disp->height <= disp->screen_height) { disp->y = 0; } else { if (disp->y < 0) disp->y = 0; if (disp->y >= disp->height - disp->screen_height) disp->y = disp->height - disp->screen_height; } } void output_x11(struct lstopo_output *loutput, const char *filename __hwloc_attribute_unused) { struct lstopo_x11_output _disp, *disp = &_disp; struct lstopo_cairo_output *coutput; int finish = 0; int state = 0; int x = 0, y = 0; /* shut warning down */ int lastx, lasty; coutput = &disp->coutput; memset(coutput, 0, sizeof(*coutput)); memcpy(&coutput->loutput, loutput, sizeof(*loutput)); coutput->loutput.methods = &x11_draw_methods; output_draw_start(&coutput->loutput); lastx = disp->x; lasty = disp->y; topo_cairo_paint(coutput); while (!finish) { XEvent e; if (!XEventsQueued(disp->dpy, QueuedAfterFlush)) { /* No pending event, flush moving windows before waiting for next event */ if (disp->x != lastx || disp->y != lasty) { XMoveWindow(disp->dpy, disp->win, -disp->x, -disp->y); lastx = disp->x; lasty = disp->y; } } XNextEvent(disp->dpy, &e); switch (e.type) { case Expose: if (e.xexpose.count < 1) topo_cairo_paint(coutput); break; case MotionNotify: if (state) { disp->x -= e.xmotion.x_root - x; disp->y -= e.xmotion.y_root - y; x = e.xmotion.x_root; y = e.xmotion.y_root; move_x11(disp); } break; case ConfigureNotify: { float wscale, hscale; disp->screen_width = e.xconfigure.width; disp->screen_height = e.xconfigure.height; if (disp->screen_width != disp->last_screen_width || disp->screen_height != disp->last_screen_height) { wscale = disp->screen_width / (float)disp->width; hscale = disp->screen_height / (float)disp->height; disp->scale *= wscale > hscale ? hscale : wscale; if (disp->scale < 1.0f) disp->scale = 1.0f; move_x11(disp); } if (disp->x != lastx || disp->y != lasty) XMoveWindow(disp->dpy, disp->win, -disp->x, -disp->y); break; } case ButtonPress: if (e.xbutton.button == Button1) { state = 1; x = e.xbutton.x_root; y = e.xbutton.y_root; } break; case ButtonRelease: if (e.xbutton.button == Button1) state = 0; break; case MappingNotify: XRefreshKeyboardMapping(&e.xmapping); break; case KeyPress: { KeySym keysym; XLookupString(&e.xkey, NULL, 0, &keysym, NULL); switch (keysym) { case XK_q: case XK_Q: case XK_Escape: finish = 1; break; case XK_Left: disp->x -= disp->screen_width/10; move_x11(disp); break; case XK_Right: disp->x += disp->screen_width/10; move_x11(disp); break; case XK_Up: disp->y -= disp->screen_height/10; move_x11(disp); break; case XK_Down: disp->y += disp->screen_height/10; move_x11(disp); break; case XK_Page_Up: if (e.xkey.state & ControlMask) { disp->x -= disp->screen_width; move_x11(disp); } else { disp->y -= disp->screen_height; move_x11(disp); } break; case XK_Page_Down: if (e.xkey.state & ControlMask) { disp->x += disp->screen_width; move_x11(disp); } else { disp->y += disp->screen_height; move_x11(disp); } break; case XK_Home: disp->x = 0; disp->y = 0; move_x11(disp); break; case XK_End: disp->x = INT_MAX; disp->y = INT_MAX; move_x11(disp); break; case XK_f: case XK_F: { float wscale = disp->screen_width / (float)disp->width; float hscale = disp->screen_height / (float)disp->height; disp->scale *= wscale > hscale ? hscale : wscale; move_x11(disp); break; } case XK_plus: case XK_KP_Add: disp->scale *= 1.2f; move_x11(disp); break; case XK_minus: case XK_KP_Subtract: disp->scale /= 1.2f; move_x11(disp); break; case XK_1: case XK_KP_1: disp->scale = 1.0f; move_x11(disp); break; } } } } x11_destroy(disp); XDestroyWindow(disp->dpy, disp->top); XFreeCursor(disp->dpy, disp->hand); XCloseDisplay(disp->dpy); } #endif /* CAIRO_HAS_XLIB_SURFACE */ #ifdef CAIRO_HAS_PNG_FUNCTIONS /* PNG back-end */ static struct draw_methods png_draw_methods; static void png_init(void *_coutput) { struct lstopo_cairo_output *coutput = _coutput; cairo_surface_t *fakecs, *cs; /* create a fake surface */ fakecs = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 1, 1); coutput->surface = fakecs; /* compute the maximal size using the fake surface */ coutput->drawing = 0; topo_cairo_paint(coutput); coutput->drawing = 1; cairo_surface_destroy(fakecs); /* create the actual surface with the right size */ cs = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, coutput->max_x, coutput->max_y); coutput->surface = cs; } static struct draw_methods png_draw_methods = { png_init, null_declare_color, topo_cairo_box, topo_cairo_line, topo_cairo_text, topo_cairo_textsize, }; void output_png(struct lstopo_output *loutput, const char *filename) { struct lstopo_cairo_output coutput; FILE *output; output = open_output(filename, loutput->overwrite); if (!output) { fprintf(stderr, "Failed to open %s for writing (%s)\n", filename, strerror(errno)); return; } memset(&coutput, 0, sizeof(coutput)); memcpy(&coutput.loutput, loutput, sizeof(*loutput)); coutput.loutput.file = output; coutput.loutput.methods = &png_draw_methods; output_draw_start(&coutput.loutput); topo_cairo_paint(&coutput); cairo_surface_write_to_png_stream(coutput.surface, topo_cairo_write, output); cairo_surface_destroy(coutput.surface); if (output != stdout) fclose(output); } #endif /* CAIRO_HAS_PNG_FUNCTIONS */ #ifdef CAIRO_HAS_PDF_SURFACE /* PDF back-end */ static struct draw_methods pdf_draw_methods; static void pdf_init(void *_coutput) { struct lstopo_cairo_output *coutput = _coutput; cairo_surface_t *fakecs, *cs; /* create a fake surface */ fakecs = cairo_pdf_surface_create_for_stream(NULL, NULL, 1, 1); coutput->surface = fakecs; /* compute the maximal size using the fake surface */ coutput->drawing = 0; topo_cairo_paint(coutput); coutput->drawing = 1; cairo_surface_destroy(fakecs); /* create the actual surface with the right size */ cs = cairo_pdf_surface_create_for_stream(topo_cairo_write, coutput->loutput.file, coutput->max_x, coutput->max_y); coutput->surface = cs; } static struct draw_methods pdf_draw_methods = { pdf_init, null_declare_color, topo_cairo_box, topo_cairo_line, topo_cairo_text, topo_cairo_textsize, }; void output_pdf(struct lstopo_output *loutput, const char *filename) { struct lstopo_cairo_output coutput; FILE *output; output = open_output(filename, loutput->overwrite); if (!output) { fprintf(stderr, "Failed to open %s for writing (%s)\n", filename, strerror(errno)); return; } memset(&coutput, 0, sizeof(coutput)); memcpy(&coutput.loutput, loutput, sizeof(*loutput)); coutput.loutput.file = output; coutput.loutput.methods = &pdf_draw_methods; output_draw_start(&coutput.loutput); topo_cairo_paint(&coutput); cairo_surface_flush(coutput.surface); cairo_surface_destroy(coutput.surface); if (output != stdout) fclose(output); } #endif /* CAIRO_HAS_PDF_SURFACE */ #ifdef CAIRO_HAS_PS_SURFACE /* PS back-end */ static struct draw_methods ps_draw_methods; static void ps_init(void *_coutput) { struct lstopo_cairo_output *coutput = _coutput; cairo_surface_t *fakecs, *cs; /* create a fake surface */ fakecs = cairo_ps_surface_create_for_stream(NULL, NULL, 1, 1); coutput->surface = fakecs; /* compute the maximal size using the fake surface */ coutput->drawing = 0; topo_cairo_paint(coutput); coutput->drawing = 1; cairo_surface_destroy(fakecs); /* create the actual surface with the right size */ cs = cairo_ps_surface_create_for_stream(topo_cairo_write, coutput->loutput.file, coutput->max_x, coutput->max_y); coutput->surface = cs; } static struct draw_methods ps_draw_methods = { ps_init, null_declare_color, topo_cairo_box, topo_cairo_line, topo_cairo_text, topo_cairo_textsize, }; void output_ps(struct lstopo_output *loutput, const char *filename) { struct lstopo_cairo_output coutput; FILE *output; output = open_output(filename, loutput->overwrite); if (!output) { fprintf(stderr, "Failed to open %s for writing (%s)\n", filename, strerror(errno)); return; } memset(&coutput, 0, sizeof(coutput)); memcpy(&coutput.loutput, loutput, sizeof(*loutput)); coutput.loutput.file = output; coutput.loutput.methods = &ps_draw_methods; output_draw_start(&coutput.loutput); topo_cairo_paint(&coutput); cairo_surface_flush(coutput.surface); cairo_surface_destroy(coutput.surface); if (output != stdout) fclose(output); } #endif /* CAIRO_HAS_PS_SURFACE */ #ifdef CAIRO_HAS_SVG_SURFACE /* SVG back-end */ static struct draw_methods svg_draw_methods; static void svg_init(void *_coutput) { struct lstopo_cairo_output *coutput = _coutput; cairo_surface_t *fakecs, *cs; /* create a fake surface */ fakecs = cairo_svg_surface_create_for_stream(NULL, NULL, 1, 1); coutput->surface = fakecs; /* compute the maximal size using the fake surface */ coutput->drawing = 0; topo_cairo_paint(coutput); coutput->drawing = 1; cairo_surface_destroy(fakecs); /* create the actual surface with the right size */ cs = cairo_svg_surface_create_for_stream(topo_cairo_write, coutput->loutput.file, coutput->max_x, coutput->max_y); coutput->surface = cs; } static struct draw_methods svg_draw_methods = { svg_init, null_declare_color, topo_cairo_box, topo_cairo_line, topo_cairo_text, topo_cairo_textsize, }; void output_svg(struct lstopo_output *loutput, const char *filename) { struct lstopo_cairo_output coutput; FILE *output; output = open_output(filename, loutput->overwrite); if (!output) { fprintf(stderr, "Failed to open %s for writing (%s)\n", filename, strerror(errno)); return; } memset(&coutput, 0, sizeof(coutput)); memcpy(&coutput.loutput, loutput, sizeof(*loutput)); coutput.loutput.file = output; coutput.loutput.methods = &svg_draw_methods; output_draw_start(&coutput.loutput); topo_cairo_paint(&coutput); cairo_surface_flush(coutput.surface); cairo_surface_destroy(coutput.surface); if (output != stdout) fclose(output); } #endif /* CAIRO_HAS_SVG_SURFACE */