I'm trying to use the Cairo graphics library on Linux in C to make a pretty lightweight x11 GUI.
After trying very hard to follow the woefully incomplete guide that cairo gives for x11, this is the best I've got:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <cairo.h>
#include <cairo-xlib.h>
#include <X11/Xlib.h>
#include <X11/extensions/Xrender.h>
#include <X11/extensions/renderproto.h>
//This function should give us a new x11 surface to draw on.
cairo_surface_t* create_x11_surface(int x, int y)
{
Display* d;
Drawable da;
int screen;
cairo_surface_t* sfc;
if((d = XOpenDisplay(NULL)) == NULL)
{
printf("failed to open display\n");
exit(1);
}
screen = DefaultScreen(d);
da = XCreateSimpleWindow(d, DefaultRootWindow(d), 0, 0, x, y, 0, 0, 0);
XSelectInput(d, da, ButtonPressMask | KeyPressMask);
XMapWindow(d, da);
sfc = cairo_xlib_surface_create(d, da, DefaultVisual(d, screen), x, y);
cairo_xlib_surface_set_size(sfc, x, y);
return sfc;
}
int main(int argc, char** argv)
{
//create a new cairo surface in an x11 window as well as a cairo_t* to draw
//on the x11 window with.
cairo_surface_t* surface = create_x11_surface(300, 200);
cairo_t* cr = cairo_create(surface);
while(1)
{
//save the empty drawing for the next time through the loop.
cairo_push_group(cr);
//draw some text
cairo_select_font_face(cr, "serif", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_BOLD);
cairo_set_font_size(cr, 32.0);
cairo_set_source_rgb(cr, 0, 0, 1.0);
cairo_move_to(cr, 10.0, 25.0);
if((argc == 2) && (strnlen(argv[1], 100) < 50))
cairo_show_text(cr, argv[1]);
else
cairo_show_text(cr, "usage: ./p1 <string>");
//put the drawn text onto the screen(?)
cairo_pop_group_to_source(cr);
cairo_paint(cr);
cairo_surface_flush(surface);
//pause for a little bit.
int c = getchar();
//change the text around so we can see the screen update.
for(int i = 0; i < strnlen(argv[1], 100); i++)
{
argv[1][i] = argv[1][i + 1];
}
if(c == 'q')
{
break;
}
}
cairo_surface_destroy(surface);
return 0;
}
On Linux systems that have Cairo installed, it can be compiled with
gcc -o myprog $(pkg-config --cflags --libs cairo x11) -std=gnu99 main.c
And it should be run with a single argument.
For reasons I don't understand at all, inserting the line
cairo_pop_group_to_source(cr);
cairo_paint(cr);
cairo_surface_write_to_png (surface, "hello.png"); //<--------- inserted
cairo_surface_flush(surface);
Puts something on the screen, but there are 2 problems:
Text that I draw with this method is persistent, creating a smearing effect.
I don't want some .png file mediating between my program and an x11 window. Data should be sent directly!
Several issues:
In X11, the X11 server doesn't save what you drew to a window, but instead sends an ExposeEvent to your window that tells it to redraw. This means you get a black window, because you do not handle this event.
getchar only gives you something after a line break, so just typing something won't help.
libX11 buffers stuff and only sends it to the X11 server when you wait for an event (or the buffer fills up). Since you never wait for an event, it never flushes. Calling XFlush explicitly helps.
The group that you push is useless. Just get rid of it.
Your code to move the string one direction to the left easily goes beyond the end of the string. You apparently know this already, because you 'fixed' this with a strnlen.
Here is a little better solution, but it still gives you an initially black window, because you draw to it before it is mapped:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <cairo-xlib.h>
#include <X11/Xlib.h>
//This function should give us a new x11 surface to draw on.
cairo_surface_t* create_x11_surface(Display *d, int x, int y)
{
Drawable da;
int screen;
cairo_surface_t* sfc;
screen = DefaultScreen(d);
da = XCreateSimpleWindow(d, DefaultRootWindow(d), 0, 0, x, y, 0, 0, 0);
XSelectInput(d, da, ButtonPressMask | KeyPressMask);
XMapWindow(d, da);
sfc = cairo_xlib_surface_create(d, da, DefaultVisual(d, screen), x, y);
return sfc;
}
int main(int argc, char** argv)
{
Display *d = XOpenDisplay(NULL);
if (d == NULL) {
fprintf(stderr, "Failed to open display\n");
return 1;
}
//create a new cairo surface in an x11 window as well as a cairo_t* to draw
//on the x11 window with.
cairo_surface_t* surface = create_x11_surface(d, 300, 200);
cairo_t* cr = cairo_create(surface);
char *text = argv[1];
size_t text_len = 0;
if (argc != 2)
text = NULL;
else
text_len = strlen(text);
while(1)
{
// Clear the background
cairo_set_source_rgb(cr, 0, 0, 0);
cairo_paint(cr);
//draw some text
cairo_select_font_face(cr, "serif", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_BOLD);
cairo_set_font_size(cr, 32.0);
cairo_set_source_rgb(cr, 0, 0, 1.0);
cairo_move_to(cr, 10.0, 25.0);
if (text)
cairo_show_text(cr, text);
else
cairo_show_text(cr, "usage: ./p1 <string>");
cairo_surface_flush(surface);
XFlush(d);
//pause for a little bit.
int c = getchar();
//change the text around so we can see the screen update.
memmove(text, &text[1], text_len);
if (text_len > 0)
text_len--;
printf("got char %c\n", c);
if(c == 'q')
{
break;
}
}
// XXX: Lots of other stuff isn't properly destroyed here
cairo_surface_destroy(surface);
return 0;
}
Edit: Also, why exactly do you feel like cairo only gives you a woefully incomplete guide? It tells you how to get the cairo parts working and it also explains you some parts about X11, even though you should already know those if you want to use cairo-x11. That's none of its business. The guide you linked to even provides a complete, working and self-contained example: https://www.cypherpunk.at/files/2014/11/cairo_xlib_simple.c
I've you would have read the complete text of this "imcomplete guide" you would have seen that there is a link to the full sample: https://www.cypherpunk.at/files/2014/11/cairo_xlib_simple.c .
Related
I would like to open a "dock type" window without its title bar with x11 in C and I want to capture a key press (Ctrl-q) to close it. The actual code does not capture the key presses to the active window as they are all sent and printed in the terminal. Almost all the Ctrl key presses are somehow not detected. The only key presses that actually work on the opened window are Ctrl-c. I am aware of the _MOTIF_WM_HINTS property but it has the disadvantage to display a window icon in the panel to show a new opened window.
How can I have a "dock type" window while not losing the ability to detect key presses to the active window?
Here is the code basically copied from two other posts here and here
#include <stdio.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/Xatom.h>
#include <X11/keysym.h>
int main(int argc, char **argv)
{
Display *dpy = XOpenDisplay(0);
int blackColor = BlackPixel(dpy, DefaultScreen(dpy));
int whiteColor = WhitePixel(dpy, DefaultScreen(dpy));
Window w = XCreateSimpleWindow(dpy, DefaultRootWindow(dpy), 0, 0,
200, 100, 0, blackColor, blackColor);
//Tell X Server to send MapNotify events
XSelectInput(dpy, w, StructureNotifyMask | KeyPressMask);
Atom window_type = XInternAtom(dpy, "_NET_WM_WINDOW_TYPE", False);
long value = XInternAtom(dpy, "_NET_WM_WINDOW_TYPE_DOCK", False);
XChangeProperty(dpy, w, window_type, 4, 32,
PropModeReplace,
(unsigned char *)&value, 1);
//Make window appear
XMapWindow(dpy, w);
//Graphics Context
GC gc = XCreateGC(dpy, w, 0, 0);
//Set white color for drawing
XSetForeground(dpy, gc, whiteColor);
//Wait for the MapNotify event
for(;;) {
XEvent e;
XNextEvent(dpy, &e);
if (e.type == MapNotify) {
break;
}
}
//Draw the line
XDrawLine(dpy, w, gc, 10, 60, 180, 20);
//Send the "DrawLine" request to the server
XFlush(dpy);
char text[255];
XEvent e;
KeySym key;
int numKeys = 0;
for(;;) {
XNextEvent(dpy, &e);
if(e.type == KeyPress) {
if((numKeys = XLookupString(&e.xkey, text, 255, &key, 0))) {
if(e.xkey.state == ControlMask && key == XK_q) {
printf("CTRL-Q\n");
break;
}
}
}
}
if (dpy && gc) XFreeGC(dpy, gc);
if (dpy && w) XDestroyWindow(dpy, w);
if (dpy) XCloseDisplay(dpy);
return 0;
}
Compiled with:
gcc main.c -L/usr/X11R6/lib -lX11 -lm && ./a.out
I've also looked at the _NET_WM_STRUT_PARTIAL property but I haven't been succesful at implementing it.
I worked with cairo and X11 before, and had a piece of code working perfectly, and now I am developping a new project (supposed to be a karaoke), and I took a piece of the code that was used to display something on the screen, which worked on the old project, but which doesn't work anymore.
I have been looking for a mistake all day and I must be missing something, because nothing works.
The code is the following.
A first function used to display some text :
void display_line(cairo_surface_t *surface, lyrics_line l)
{
cairo_t *cr;
cr=cairo_create(surface);
cairo_set_source_rgb(cr, 0, 0, 0); // Should paint the window black
cairo_paint(cr);
cairo_set_source_rgb(cr, 1., 1., 1.);
cairo_select_font_face(cr, "Hacker", CAIRO_FONT_SLANT_NORMAL,CAIRO_FONT_WEIGHT_BOLD);
cairo_set_font_size(cr, 13);
cairo_move_to(cr, 620, 30);
char text[255];
strcpy(text, l.text);
cairo_show_text(cr, text); // Should print the text in white
cairo_destroy(cr);
printf("%s\n", text);
}
And the main function, that calls the previous one,
void display(song s)
{
// X11 display
Display *dpy;
Window rootwin;
Window win;
int scr;
// init the display
if(!(dpy=XOpenDisplay(NULL))) {
fprintf(stderr, "ERROR: Could not open display\n");
exit(1);
}
scr=DefaultScreen(dpy);
rootwin=RootWindow(dpy, scr);
win=XCreateSimpleWindow(dpy, rootwin, 1, 1, WINSIZEX, WINSIZEY, 0, BlackPixel(dpy, scr), BlackPixel(dpy, scr));
XStoreName(dpy, win, "Karaoke");
KeyPressMask|ButtonPressMask|ExposureMask);
XMapWindow(dpy, win);
// create cairo surface
cairo_surface_t *cs;
cs=cairo_xlib_surface_create(dpy, win, DefaultVisual(dpy, 0), WINSIZEX, WINSIZEY);
cairo_t *cr;
cr=cairo_create(cs);
cairo_set_source_rgb (cr, 0.0, 0.0, 1.0);
cairo_paint(cr); // Should fill the window in blue
cairo_destroy(cr);
int i;
printf("\n");
lyrics_line l = {"That's a test", 200};
display_line(cs, l);
usleep(s.text[0].length*10000);
for(i=0; i<s.length; i++)
{
display_line(cs, s.text[i]);
usleep((s.text[i+1].length - s.text[i].length)*10000);
}
cairo_surface_destroy(cs); // destroy cairo surface
XCloseDisplay(dpy); // close the display
}
I don't know if display_line causes a problem, because when I simply try to color the window in blue with displayit doesn't even work.
However, the lyrics do display in the console, so the algorithm is not the problem.
What could I be missing ?
Thanks in advance.
I would like to create a semi transparent white window in XLib, but the window is not semi translucent, it remains fully opaque. I use the compton compositor and there are transparent windows in the system, so the problem is in the code:
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <stdio.h>
int main(int argc, char* argv[])
{
Display* display = XOpenDisplay(NULL);
XVisualInfo vinfo;
XMatchVisualInfo(display, DefaultScreen(display), 32, TrueColor, &vinfo);
XSetWindowAttributes attr;
attr.colormap = XCreateColormap(display, DefaultRootWindow(display), vinfo.visual, AllocNone);
attr.border_pixel = 0;
attr.background_pixel = 0x80ffffff;
Window win = XCreateWindow(display, DefaultRootWindow(display), 0, 0, 300, 200, 0, vinfo.depth, InputOutput, vinfo.visual, CWColormap | CWBorderPixel | CWBackPixel, &attr);
XSelectInput(display, win, StructureNotifyMask);
GC gc = XCreateGC(display, win, 0, 0);
Atom wm_delete_window = XInternAtom(display, "WM_DELETE_WINDOW", 0);
XSetWMProtocols(display, win, &wm_delete_window, 1);
XMapWindow(display, win);
int keep_running = 1;
XEvent event;
while (keep_running) {
XNextEvent(display, &event);
switch(event.type) {
case ClientMessage:
if (event.xclient.message_type == XInternAtom(display, "WM_PROTOCOLS", 1) && (Atom)event.xclient.data.l[0] == XInternAtom(display, "WM_DELETE_WINDOW", 1))
keep_running = 0;
break;
default:
break;
}
}
XDestroyWindow(display, win);
XCloseDisplay(display);
return 0;
}
X11 expects pre-multiplied colours, i.e. real opaque colours need to be multiplied by the alpha value (and scaled accordingly, i.e. divided by 256 when channel widths is 8 bits). This format is easier to work with when you need to combine many levels. See formulas here. There's less computation when everything is pre-multiplied.
So you need to multiply each of your R, G and B channels by the alpha value (0x80) and divide by 256.
Setting the background to 0x80808080 gives the desired result:
Note the result is different from what #patthoyts suggests: here only the window proper is semi-transparent, the WM decoration stays opaque; there both the window proper and the decoration are made transparent by the WM (and the WM does the necessary colour blending).
You need to set _NET_WM_WINDOW_OPACITY. Here is a snippet to add in before you map the window:
double alpha = 0.8;
unsigned long opacity = (unsigned long)(0xFFFFFFFFul * alpha);
Atom XA_NET_WM_WINDOW_OPACITY = XInternAtom(display, "_NET_WM_WINDOW_OPACITY", False);
XChangeProperty(display, win, XA_NET_WM_WINDOW_OPACITY, XA_CARDINAL, 32,
PropModeReplace, (unsigned char *)&opacity, 1L);
Note you should add #include <X11/Xatom.h> to get the declaration of XA_CARDINAL.
I'm not entirely sure how stable this interface is. This appears to be a proposed extension to the Extended Window Manager Hints specification but has not made it into any final revision from what I can see. I know that this is how Tk implements transparency support on unix though.
The result looks like:
I am designing an app that having the ID of an X11 window to draw a rectangle.
The problem I have is that I can not draw nothing in the window.
Code
One obvious error is that you did not select ExposureMask in call to XSelectInput, so you will not receive Expose event you are waiting for.
Other possible problem is not setting foreground drawing color of the GC by XSetForegroundColor, default is black. And using default gc of screen may fail if the window has different color depth or different visual. Other important GC attributes issubwindow_mode (whether to draw over child windows).
Still, after I did those changes, the program only works for me (draws a rectangle) on root window and xev, but not for xterm, no expose events.
This is my fixed version:
#include <X11/Xlib.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
int main (int argc, char *argv[]){
Window win;
Display *display;
XEvent e;
display = XOpenDisplay(NULL);
if(display==NULL){
fprintf(stderr,"Cannot open Display\n");
exit(1);
}
int s = DefaultScreen(display);
//unsigned long *id = (unsigned long*)(argv[1]);
sscanf(argv[1], "0x%x", &win);
if(!XSelectInput(display,win,StructureNotifyMask | ExposureMask)){
fprintf(stderr,"Cannot select Display\n");
return -1;
}
if(!XMapWindow(display,win)){
fprintf(stderr,"Cannot map Display\n");
return -1;
}
XGCValues gcv;
gcv.subwindow_mode = IncludeInferiors;
GC gc = XCreateGC(display, win, GCSubwindowMode,
&gcv);
XSetForeground(display, gc, 0xff00ff00);
XSetPlaneMask(display, gc, 0xffffffff);
while (1) {
puts("waiting for event\n");
XNextEvent(display, &e);
puts("got event\n");
if (e.type == Expose) {
printf("drawing\n");
XFillRectangle(display, win, gc, 20, 20, 100, 100);
}
if (e.type == KeyPress)
break;
}
return 0;
}
I am developing an application using GLFW, whereby the user can input some information into the console, the program does some processing on the information, then opens an opengl window (using GLFW) to display the results. The user can then close the window, and be returned to the main menu and enter more information if they want.
Currently the problem I'm having is that once closing the GLFW/OpenGL window the console no longer accepts any input from scanf(). I am fairly certain that I am closing GLFW properly, so I am unsure as to what the problem is.
The code I am using is below:
Main.c:
#include <stdio.h>
#include <stdlib.h>
#include "glfw.h"
#include "pantograph.h"
int main(int argc, char** argv)
{
printf("program start");
int a = 0;
scanf("%i",&a); //this works
printf("%c",a);
p_open_window(1000, 500, 0, "hi there");
int i = 0;
for(i=0;i<1000;i++)
{
p_begin_render();
glBegin(GL_POINTS);
glVertex2i(i,i/2);
glEnd();
scanf("%i",&a);
p_end_render();
}
p_close_window();
scanf("%i",&a); //this does not work
printf("%i",a);
return 0;
}
pantograph.h:
int p_open_window(int width, int height, int fullscreen, const char* title)
{
glfwInit();
glfwDisable(GLFW_AUTO_POLL_EVENTS);
if(fullscreen)
{
glfwOpenWindow(width,height,8,8,8,8,0,0,GLFW_FULLSCREEN);
}else{
glfwOpenWindowHint(GLFW_WINDOW_NO_RESIZE, GL_TRUE);
glfwOpenWindow(width,height,8,8,8,8,0,0,GLFW_WINDOW);
}
glViewport(0, 0, width, height);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrtho(0, width, height, 0, 0, 1);
glMatrixMode(GL_MODELVIEW);
glDisable(GL_DEPTH_TEST);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
return 1;
}
void p_begin_render()
{
glClearColor(0.0, 0.0, 0.0, 1.0);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glColor4f(1.0, 1.0, 1.0, 1.0);
glLoadIdentity();
}
void p_end_render()
{
glfwSwapBuffers();
}
void p_close_window()
{
glfwCloseWindow();
glfwTerminate();
}
I'm a little confused by your program flow. You say 'the user can close the window and be returned to the main menu'. But how does the program exit from the (i<1000) loop? If the user just closes the window it seems like it would still be executing the for loop (especially because of the scanf inside the loop).
Have you used a debugger to see which point your program gets caught on?
Thanks to the people at #glfw I've managed to find a somewhat hackish solution...
If you "flush" the input buffer directly after closing the glfw window scanf will start to work again. I'm not entirely sure why, but it seems to work for now, so I'm satsfied.
The code I'm using to do it is as follows (after closing the window):
int ch;
while ((ch = getchar()) != '\n' && ch != EOF);
After this, scanf began working again.