├── README ├── xcount ├── Makefile ├── README └── xcount.c ├── xhello ├── Makefile ├── README └── xhello.c ├── xoutput1 ├── Makefile ├── README └── xoutput.c ├── xoutput2 ├── Makefile ├── README └── xoutput.c ├── xoutput3 ├── Makefile ├── README └── xoutput.c ├── xpaint1 ├── Makefile ├── README └── xpaint.c └── xpaint2 ├── Makefile ├── README └── xpaint.c /README: -------------------------------------------------------------------------------- 1 | XNOTES(1) X Notes NOTESO(1) 2 | 3 | NAME 4 | xnotes - some notes on Xlib programming 5 | 6 | DESCRIPTION 7 | This is a collection of programs and notes I wrote back when I was 8 | learning how to program X11 clients, and some notes I am adding to help 9 | people who are learning how to program using the Xlib library. You can 10 | use these notes to help your learning, but don't use them as the only 11 | sources. I recomment you to use Guy Keren's tutorial and Adrian Nye's 12 | book (and the man pages, of course). 13 | 14 | Each directory contains a program that teaches a lesson. Begin with 15 | xhello, as it is teaches the basics of Xlib programming. All lessons 16 | are introductions; you must refer to the manuals, when available. 17 | 18 | When you're in a directory, first compile and run the program, for you 19 | to understand how it works. Then read the source code and try to grasp 20 | its structure. Then read its README along with the code to understand 21 | the main concepts applied in the code and how stuff are implemented. 22 | 23 | FILES 24 | xhello/ 25 | Create and display a simple blank window. 26 | Lesson: Introduction to Xlib programming. 27 | 28 | xcount/ 29 | A counter window that counts button presses. 30 | Lesson: Introduction to event handling. 31 | 32 | xpaint1/ 33 | A paint-like program, with a serious bug. 34 | Lesson: Introduction to drawing and graphics context. 35 | 36 | xpaint2/ 37 | A paint-like program. 38 | Lesson: Introduction to pixmaps. 39 | 40 | xwindow/ 41 | Create and display a simple blank window with title. 42 | Lesson: Introduction to X Properties and window hints. 43 | 44 | xmenu/ 45 | A menu window. 46 | Lesson: Introduction to override-redirect, atoms and EWMH. 47 | 48 | xcircle/ 49 | Create and display a simple round window. 50 | Lesson: Introduction to X Shape. 51 | 52 | xinput/ 53 | A window that reads the characters the user types and print them. 54 | Lesson: Introduction to X Input Method. 55 | 56 | xoutput1/ 57 | Display text on window. 58 | Lesson: Introduction to core X11 font system. 59 | 60 | xoutput2/ 61 | Display text on window. 62 | Lesson: Introduction to Xft. 63 | 64 | xoutput3/ 65 | Display text on window. 66 | Lesson: Using Xft with manually created fontset. 67 | 68 | xoutput4/ 69 | Display text on window. 70 | Lesson: Using Xft with fontset provided by fontconfig. 71 | 72 | BUGS 73 | Some paragraphs were copied as is from other sources when I was 74 | learning how to program using Xlib. I need to fix that. 75 | 76 | SEE ALSO 77 | Guy Keren, Basic Graphics Programming With The Xlib Library. 78 | 79 | Adrian Nye, Xlib Programming Manual, O'Reilly Media, 1992, 80 | ISBN 1-56592-002-3. 81 | 82 | Xlib - C Language X Interface 83 | https://www.x.org/releases/current/doc/libX11/libX11/libX11.html 84 | 85 | https://venam.nixers.net/blog/unix/2018/05/10/reparenting-redirecting-composition-rendering.html 86 | -------------------------------------------------------------------------------- /xcount/Makefile: -------------------------------------------------------------------------------- 1 | PROG = xcount 2 | SRCS = ${PROG}.c 3 | OBJS = ${SRCS:.c=.o} 4 | 5 | INCS = -I/usr/X11R6/include 6 | LIBS = -L/usr/X11R6/lib -lX11 7 | 8 | CC = cc 9 | CFLAGS = -g -O0 -Wall -Wextra ${INCS} 10 | LDFLAGS = ${LIBS} 11 | 12 | all: ${PROG} 13 | 14 | ${PROG}: ${OBJS} 15 | ${CC} -o $@ ${OBJS} ${LDFLAGS} 16 | 17 | .c.o: 18 | ${CC} ${CFLAGS} -c $< 19 | 20 | clean: 21 | -rm ${OBJS} ${PROG} 22 | 23 | test: ${PROG} 24 | ./${PROG} 25 | 26 | .PHONY: all clean test 27 | -------------------------------------------------------------------------------- /xcount/README: -------------------------------------------------------------------------------- 1 | XCOUNT(1) X Notes XCOUNT(1) 2 | 3 | NAME 4 | xcount - count with the mouse 5 | 6 | SYNOPSIS 7 | xcount 8 | 9 | DESCRIPTION 10 | This program displays a window with a number written on it. Clicking 11 | on the window with the left mouse button increments this number by one. 12 | Clicking on the window with the middle mouse button resets this number 13 | to zero. Clicking on the window with the right mouse button decrements 14 | this number by one. Pressing the key Q on the keyboard closes the 15 | window. 16 | 17 | The objective of this program is to understand and apply the basic 18 | concepts about event handling. 19 | 20 | CONCEPTS 21 | Let's ignore the draw function, what it does is to clear the window and 22 | draw a string on it. Let's concentrate on the function running the 23 | event loop and how it handle events. 24 | 25 | X programs use an asynchronous programming model called “event-driven 26 | programming”. This means that the program mostly sits idle, waiting 27 | for events sent by the X server, and then acts upon these events. An 28 | event may say “the user pressed the 1st mouse button in spot x,y”, or 29 | “the window you control needs to be redrawn”. 30 | 31 | Xlib stores received events in a queue. Clients can inspect and 32 | retrieve events from the queue. While the X server sends events, 33 | clients must explicitly call functions for accessing the events 34 | in the queue. Some of these functions may block; in this case, 35 | they also flush the output buffer. 36 | 37 | There are various kinds of events. An event can be the result of an 38 | input by the user, such as clicking on the window with the mouse, or 39 | pressing a key on the keyboard while the window is focused (a window 40 | must have focus in order to receive keyboard events). An event can 41 | also be the result of a window manipulation by the user, for example, 42 | restacking windows (moving a window so it uncovers the one below it) 43 | generates an expose event for the window that becames uncovered. 44 | 45 | Registering for Events 46 | First, the client application must inform what kind of events it wants 47 | to receive for what windows. To do this, the client must register for 48 | events on those windows. Applications can register for events on a 49 | window at the time the window is created with XCreateWindow(3); or 50 | after it has been created using the XSelectInput(3) function. 51 | 52 | Multiple client applications can register for the same events on the 53 | same window with the following restrictions: 54 | 55 | • Multiple clients can select events on the same window because their 56 | event masks are disjoint. When the X server generates an event, it 57 | reports it to all interested clients. 58 | 59 | • Only one client at a time can select CirculateRequest, MapRequest, or 60 | ConfigureRequest events, which are associated with the event mask 61 | SubstructureRedirectMask. 62 | 63 | • Only one client at a time can select a ResizeRequest event, which is 64 | associated with the event mask ResizeRedirectMask. 65 | 66 | • Only one client at a time can select a ButtonPress event, which is 67 | associated with the event mask ButtonPressMask. 68 | 69 | The server reports the event to all interested clients. 70 | 71 | Handling Events 72 | After we have registered for the event types we are interested in, we 73 | need to enter a loop of receiving events and handling them. This loop 74 | is blocked by a call to XNextEvent(3), which fetches the first event 75 | from the event queue into the specified XEvent structure and then 76 | removes it from the queue. If the event queue is empty, XNextEvent(3) 77 | flushes the output buffer and blocks until an event is received. This 78 | function always returns zero. 79 | 80 | This is the general form of a event loop. 81 | 82 | XEvent ev; 83 | 84 | while (XNextEvent(dpy, &ev) == 0) { 85 | switch(ev.type) { 86 | case Expose: 87 | ...; 88 | break; 89 | case ButtonPress: 90 | ...; 91 | break; 92 | case KeyPress: 93 | ...; 94 | break; 95 | ... 96 | } 97 | } 98 | 99 | The XEvent object filled by XNextEvent is a union of structures for all 100 | possible event types. The type member specifies the type of the event 101 | received. For each event type there is a structure member of this 102 | union containing the data associated with the event. All structure 103 | members have a .window member whose value is the identifier for the 104 | window that received the event. 105 | 106 | For example, if the value of the .type member of a XEvent is the 107 | constant ButtonPress, then .xbutton is filled with data related to the 108 | button press: .xbutton.window is the window that received the button 109 | press, .xbutton.button is the button that was pressed (Button1, 110 | Button2, etc); .xbutton.x and .xbutton.y are the positions on the 111 | window in wich the pointer was at the time of the button press; 112 | .xbutton.window is the identifier of the window that suffered the 113 | button press; etc. 114 | 115 | Check the manual for each event to know the members of the structure 116 | member associated to it on the XEvent union. 117 | 118 | Table of events 119 | The table below lists the events. The first column is the constant 120 | value that the type member can have and that indicates the event type. 121 | The second column is the name of the structure member on the XEvent 122 | union that contains the data associated with the event. The third 123 | column is the event mask that should be used while register for the 124 | event type. The fourth column is the manual for the event type. 125 | 126 | ┌───────────────┬───────────────┬───────────────────┬──────────────────────┐ 127 | │ Event type │ XEvent member │ Event Mask │ Manual page │ 128 | ╞═══════════════╪═══════════════╪═══════════════════╪══════════════════════╡ 129 | │ Expose │ xexpose │ ExposureMask │ XExposeEvent(3) │ 130 | ├───────────────┼───────────────┼───────────────────┼──────────────────────┤ 131 | │ ButtonPress │ │ ButtonPressMask │ │ 132 | ├───────────────┤ ├───────────────────┤ XButtonEvent(3) │ 133 | │ ButtonRelease │ │ ButtonReleaseMask │ │ 134 | ├───────────────┤ ├───────────────────┼──────────────────────┤ 135 | │ │ │ PointerMotionMask │ │ 136 | │ │ xbutton │ ButtonMotionMask │ │ 137 | │ │ │ Button1MotionMask │ │ 138 | │ MotionNotify │ │ Button2MotionMask │ XMotionEvent(3) │ 139 | │ │ │ Button3MotionMask │ │ 140 | │ │ │ Button4MotionMask │ │ 141 | │ │ │ Button5MotionMask │ │ 142 | ├───────────────┼───────────────┼───────────────────┼──────────────────────┤ 143 | │ FocusIn │ │ FocusChangeMask │ │ 144 | ├───────────────┤ xfocus ├───────────────────┤ XFocusChangeEvent(3) │ 145 | │ FocusOut │ │ FocusChangeMask │ │ 146 | ├───────────────┼───────────────┼───────────────────┼──────────────────────┤ 147 | │ EnterNotify │ │ EnterWindowMask │ │ 148 | ├───────────────┤ xcrossing ├───────────────────┤ XCrossingEvent(3) │ 149 | │ LeaveNotify │ │ LeaveWindowMask │ │ 150 | ├───────────────┼───────────────┼───────────────────┼──────────────────────┤ 151 | │ KeyPress │ │ KeyPressMask │ │ 152 | ├───────────────┤ xkey ├───────────────────┤ XKeyEvent(3) │ 153 | │ KeyRelease │ │ KeyReleaseMask │ │ 154 | └───────────────┴───────────────┴───────────────────┴──────────────────────┘ 155 | 156 | xcount events 157 | In the xcount program, we deal with three different types of events. 158 | Read the manual for all those events. 159 | 160 | Expose Event 161 | We registered for Expose events using the ExposureMask bit mask. 162 | Thus, we receive an Expose event whenever the window is exposed 163 | (when it is mapped or when another window uncovers it, for 164 | example). Whenever a window is covered, it loses everything 165 | that was drawn on it; so we must respond to this event by 166 | redrawing the window. The fields .x and .y are the top-left 167 | coordinate of the window region that was exposed and needs to be 168 | redrawn. The fields .width and .height are the size of this 169 | region. The field .count is the number of other expose events 170 | waiting in the event queue. We can respond to events of this 171 | type in two ways: first responding to each event by redrawing 172 | each region that needs to be redrawn; or responding to a single 173 | expose event (the one whose .count is 0, we ignore the others) 174 | by redrawing the entire window (and thus all the regions that 175 | needs to be redrawn). 176 | 177 | Button Event 178 | We registered for ButtonPress events using the ButtonPressMask 179 | bit mask. Thus, we receive a ButtonPress event whenever the 180 | user press a mouse button on our window. The .button field is 181 | the button that was pressed. The .x and .y fields are the 182 | coordinates (in pixels) of the button press. 183 | 184 | Key Event 185 | We registered for KeyPress events using the KeyPressMask bit 186 | mask. Thus, we receive a KeyPress event whenever the user press 187 | a key on our window. The .keycode is the keycode of the button. 188 | Keycodes are mapped to actual key symbols, which correspond to 189 | the symbols in our keyboard. We can translate keycodes to key 190 | symbols with XkbKeycodeToKeysym(3). In our case, we exit from 191 | the loop whenever the user press the key corresponding to the 192 | keysym XK_q (the Q key). 193 | 194 | 195 | ENVIRONMENT 196 | The following environment variables affect the execution of xcount. 197 | 198 | DISPLAY 199 | The display to start xnotify on. 200 | 201 | SEE ALSO 202 | XNextEvent(3), XExposeEvent(3), XButtonEvent(3), XKeyEvent(3) 203 | -------------------------------------------------------------------------------- /xcount/xcount.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #define POSX 200 10 | #define POSY 200 11 | #define WIDTH 350 12 | #define HEIGHT 250 13 | #define BORDER 10 14 | #define STRINGX 175 15 | #define STRINGY 125 16 | 17 | static Display *dpy; 18 | static Window root; 19 | static int screen; 20 | static GC gc; 21 | 22 | /* setup top-level window */ 23 | static Window 24 | createwin(int x, int y, int w, int h, int b) 25 | { 26 | XSetWindowAttributes swa; 27 | Window win; 28 | 29 | /* create top-level window */ 30 | swa.background_pixel = WhitePixel(dpy, screen); 31 | swa.border_pixel = BlackPixel(dpy, screen); 32 | swa.event_mask = ExposureMask | ButtonPressMask | KeyPressMask; 33 | win = XCreateWindow(dpy, root, x, y, w, h, b, 34 | CopyFromParent, CopyFromParent, CopyFromParent, 35 | CWBackPixel | CWBorderPixel | CWEventMask, 36 | &swa); 37 | 38 | return win; 39 | } 40 | 41 | /* draw string on window */ 42 | static void 43 | draw(Window win, char *s) 44 | { 45 | /* first we clear what is already drawn, for we to not overwrite */ 46 | XSetForeground(dpy, gc, WhitePixel(dpy, screen)); 47 | XFillRectangle(dpy, win, gc, 0, 0, WIDTH, HEIGHT); 48 | 49 | /* now we can draw our string */ 50 | XSetForeground(dpy, gc, BlackPixel(dpy, screen)); 51 | XDrawString(dpy, win, gc, STRINGX, STRINGY, s, strlen(s)); 52 | } 53 | 54 | /* enter the event loop */ 55 | static void 56 | run(void) 57 | { 58 | XEvent ev; 59 | int n = 0; /* the number we are counting */ 60 | char buf[32] = "0"; /* the string to write in the window */ 61 | 62 | while(XNextEvent(dpy, &ev) == 0) { 63 | switch(ev.type) { 64 | case Expose: 65 | if (ev.xexpose.count == 0) 66 | draw(ev.xexpose.window, buf); 67 | break; 68 | case ButtonPress: 69 | if (ev.xbutton.button == Button1) 70 | n++; 71 | else if (ev.xbutton.button == Button2) 72 | n = 0; 73 | else if (ev.xbutton.button == Button3) 74 | n--; 75 | snprintf(buf, sizeof buf, "%d", n); 76 | draw(ev.xbutton.window, buf); 77 | break; 78 | case KeyPress: 79 | if (XkbKeycodeToKeysym(dpy, ev.xkey.keycode, 0, 0) == XK_q) 80 | return; 81 | break; 82 | } 83 | } 84 | } 85 | 86 | /* xcounter: count with the mouse */ 87 | int 88 | main(void) 89 | { 90 | Window win; 91 | 92 | /* open connection to the server */ 93 | if ((dpy = XOpenDisplay(NULL)) == NULL) 94 | errx(1, "could not open display"); 95 | screen = DefaultScreen(dpy); 96 | root = RootWindow(dpy, screen); 97 | 98 | /* setup resources */ 99 | win = createwin(POSX, POSY, WIDTH, HEIGHT, BORDER); 100 | gc = XCreateGC(dpy, root, 0, NULL); 101 | 102 | /* map the windows */ 103 | XMapWindow(dpy, win); 104 | 105 | /* run main loop */ 106 | run(); 107 | 108 | /* unmap the window */ 109 | XUnmapWindow(dpy, win); 110 | 111 | /* close resources */ 112 | XDestroyWindow(dpy, win); 113 | 114 | /* close connection to the server */ 115 | XCloseDisplay(dpy); 116 | 117 | return 0; 118 | } 119 | -------------------------------------------------------------------------------- /xhello/Makefile: -------------------------------------------------------------------------------- 1 | PROG = xhello 2 | SRCS = ${PROG}.c 3 | OBJS = ${SRCS:.c=.o} 4 | 5 | INCS = -I/usr/X11R6/include 6 | LIBS = -L/usr/X11R6/lib -lX11 7 | 8 | CC = cc 9 | CFLAGS = -g -O0 -Wall -Wextra ${INCS} 10 | LDFLAGS = ${LIBS} 11 | 12 | all: ${PROG} 13 | 14 | ${PROG}: ${OBJS} 15 | ${CC} -o $@ ${OBJS} ${LDFLAGS} 16 | 17 | .c.o: 18 | ${CC} ${CFLAGS} -c $< 19 | 20 | clean: 21 | -rm ${OBJS} ${PROG} 22 | 23 | test: ${PROG} 24 | ./${PROG} 25 | 26 | .PHONY: all clean test 27 | -------------------------------------------------------------------------------- /xhello/README: -------------------------------------------------------------------------------- 1 | XHELLO(1) X Notes XHELLO(1) 2 | 3 | NAME 4 | xhello - create and display a simple window 5 | 6 | SYNOPSIS 7 | xhello 8 | 9 | DESCRIPTION 10 | This program is a simple X11 “hello world” program. It creates and 11 | displays a basic window with default white background. Clicking on 12 | the window will close the application. 13 | 14 | The objective of this program is to understand and apply the basic 15 | concepts about X11 programming. 16 | 17 | CONCEPTS 18 | The basic structure of a X11 program can be divided in seven steps, as 19 | we can see in the main() function in the source code of xhello. 20 | 21 | 1. Open connection to the server. 22 | The first thing our application needs to do is to open the connection 23 | with the server. But before showing how to do that, we need to 24 | understand how the client-server model works. 25 | 26 | The X11 system uses a client-server model. The programs we create are 27 | called clients, and they have to communicate with the server. Actions 28 | (such as creating a window or drawing into the window) are performed by 29 | the server, not by our application. What our client does is to request 30 | the server to perform a certain action. 31 | 32 | The functions that send requests to the server usually do not send them 33 | immediately but store them in a buffer, called the output buffer. The 34 | output buffer is guaranteed to be flushed after a call to the functions 35 | Xsync(3) or XFlush(3), after a call to a function that returns a value 36 | from the server (these functions block until the answer is received), 37 | and in some other conditions. 38 | 39 | When a client application connects to the server, it must do so by 40 | specifying a display. A display is to the X11 system what a terminal 41 | is to the underlying operating system. It is a collection of screens 42 | and input devices (a pointer and a keyboard). Displays are controlled 43 | by the X server. 44 | 45 | Now that we understand what the client-server model is, how the client 46 | perform (or requests for) actions and what a display is, we can open a 47 | connection to the server. We need to use the function XOpenDisplay(3) 48 | to do this. It receives a single argument and returns a pointer to a 49 | Display structure. 50 | 51 | The argument XOpenDisplay(3) receives is a string (char *) specifying 52 | the address to the X server host and the number of the display device 53 | to be used. We set this value to NULL so it defaults to the value of 54 | the DISPLAY environment variable. 55 | 56 | The XOpenDisplay(3) function returns a pointer to a Display structure. 57 | This structure represents the connection we have open with the server. 58 | It hides the output buffer and the event queue (we'll talk about the 59 | event queue later). If it fails to connect to the server, it returns 60 | NULL. 61 | 62 | We should not access members of this structure directly, instead we 63 | should use some macros that get values from it. The following is a 64 | summary of some of those macros. See the manual for them for more 65 | information (there is a single manual for all those macros). 66 | 67 | DefaultScreen(3) 68 | Returns the default screen number of the display. Usually a 69 | display has only one screen. Screen is the area into which 70 | graphics may be rendered. A screen does not maps directly 71 | to a phisical monitor, as a screen may be a collection of 72 | monitors. 73 | 74 | DisplayWidth(3), DisplayHeight(3) 75 | Returns the width and height of a screen. (The names of these 76 | macros are misleading, they should be called ScreenWidth and 77 | ScreenHeight, since they return the size of the screen). In a 78 | multimonitor display, the screen width may be the sum of the 79 | monitors width if they are oriented horizontally, one next to 80 | the other; and the screen height may be the sum of the monitors 81 | height if they are oriented vertically, one above the other. 82 | 83 | RootWindow(3) 84 | Returns the identifier for the root window of a screen. We will 85 | talk about resource identifiers and windows later. Each screen 86 | has a root window that covers the whole screen. 87 | 88 | DefaultColormap(3) 89 | Returns the identifier for the default colormap of a screen. We 90 | will talk about resource identifiers later. A color map is the 91 | table of colors allocated for drawing on a drawable resource. 92 | 93 | DefaultVisual(3) 94 | Returns a pointer to the structure for the default visual type 95 | of a screen. A visual structure contains information about the 96 | various types of display hardware in determining the way pixels 97 | are translated into visible colors. For example, it specifies 98 | whether we have a gray scale or true color screen. 99 | 100 | DefaultDepth(3) 101 | Returns the default color depth of a screen. It specifies the 102 | number of bits (or planes) used to represent a color index in 103 | the color map. The number of colors is 2 to the power of this 104 | depth. 105 | 106 | BlackPixel(3), WhitePixel(3) 107 | Returns the black and white pixel values for the specified 108 | screen. 109 | 110 | DisplayString(3) 111 | Returns the string that was passed to XOpenDisplay(3) when the 112 | current display was opened. It is useful to applications that 113 | invoke fork(2). 114 | 115 | Most Xlib functions requires the pointer to the Display as an argument, 116 | in order to send a request to the server. Others may also require as 117 | argument an identifier returned by one of those macros. Since these 118 | values are required by so many functions, it is usual to store them in 119 | global variables, rather than in local variables. 120 | 121 | 2. Create resources and other objects. 122 | Resources (such as windows) are not stored in our application's memory, 123 | and our application does not manage them directly. Instead, resources 124 | are managed and stored by the server. A client operates on resources 125 | using the identifier to the resource, requesting the server to perform 126 | the operation on it. 127 | 128 | When we request the X server to create an resource for us, the relevant 129 | function returns an integer, called “handler” or “identifier”, for that 130 | resource. The resource actually resides in the X server's memory, not 131 | in our application's memory. We can later manipulate this resource by 132 | supplying this identifier to various functions. The server keeps a 133 | mapping between these identifiers and the actual resources it manages. 134 | 135 | There are other kinds of objects that are stored in our client, such as 136 | a structure containing information about a resource. Those objects are 137 | managed and stored by our client, and we must free them with XFree(3) 138 | or another function explicitly specified for the object. 139 | 140 | The most important resource is a Window. The creation of a window is 141 | requested by the function XCreateWindow(3). This function receives 142 | several arguments, listed below. 143 | 144 | Window geometry. 145 | The window geometry is a set of integer values such as the X and 146 | Y position of the window, its width and height, and the size of 147 | its border. These values may not dictate the final geometry of 148 | the window. The window manager can change these values for, for 149 | instance, make the window fit on the screen or place it in a 150 | certain position on the screen. 151 | 152 | Parent window. 153 | The windows in X11 exist in an hierarchical system. Each window 154 | (except for the root window) have a parent window, and can has 155 | children windows. The root window is what appears below other 156 | windows; the wallpaper is normally drawn on the root window. 157 | A window that is a child of the root window is said to be a 158 | “top-level window”, they are the regular windows managed by 159 | the window manager. 160 | 161 | Depth, class and visual. 162 | Those arguments specify the depth, visual and class of a window; 163 | these values are assigned when the window is created, and cannot 164 | be changed. There are two classes of windows: InputOutput and 165 | InputOnly. The main difference between the two classes is that 166 | an InputOnly window cannot be used as a drawable (a resource 167 | into which we draw stuff). All those variables can be inherited 168 | from the parent window; we just need to specify “CopyFromParent” 169 | as the argument for XCreateWindow(3). 170 | 171 | Valuemask and pointer to attribute structure. 172 | These two arguments specify the attributes of the window. The 173 | pointer is a pointer to a structure whose members are the 174 | attributes of the window; and valuemask is a bitwise inclusive 175 | OR of the members of this structure that we have set. 176 | 177 | The attributes we have set for our window are its background color, the 178 | the color of its border (the window manager may change this color), and 179 | the bitwise OR of the events for which we want to register for (we will 180 | talk about events later). In this case, we register for button presses 181 | on the window, so we receive an event when a mouse button is pressed. 182 | 183 | Other attributes we can set are the color or pattern to be used for the 184 | border or background; how are partial window contents reallocated 185 | during resizing; when are the contents of the window saved as they 186 | become covered; whether should the window be allowed to be displayed 187 | without being controlled by the window manager (the override-redirect); 188 | which colormap should be used to interpret pixel values drawn in the 189 | window; which cursor should be displayed when the pointer is on the 190 | window; etc. 191 | 192 | 3. Map the windows. 193 | For a window to appear on the screen, it has to be mapped. We call the 194 | function XMapWindow to request the server to map a given window for us. 195 | It does not occur immediately, as we have already seen. Instead, the 196 | request for mapping is stored in the output buffer and it only occurs 197 | when we flush the output buffer. 198 | 199 | 4. Run the event loop 200 | X programs use an asynchronous programming model called “event-driven 201 | programming”. This means that the program mostly sits idle, waiting 202 | for events sent by the X server, and then acts upon these events. An 203 | event may say “the user pressed the 1st mouse button in spot x,y”, or 204 | “the window you control needs to be redrawn”. 205 | 206 | Xlib stores received events in a queue. Clients can inspect and 207 | retrieve events from the queue. While the X server sends events, 208 | clients must explicitly call functions for accessing the events 209 | in the queue. Some of these functions may block; in this case, 210 | they also flush the output buffer. 211 | 212 | All the requests we have done until now (creating the window and 213 | mapping it) will not be done until we call XNextEvent(3), which will 214 | block until it get an event we have registered for. As said in the 215 | previous paragraph, this kind of function flush the output buffer, so 216 | the requests are finally sent to the X server. 217 | 218 | In the case for our program (xhello), we have registered for button 219 | press events. And the way we handle this event is to return from the 220 | event loop when we press a mouse button. 221 | 222 | 5. Unmap the window 223 | After returning from the event loop, we need to do do the cleanup. 224 | First we unmap the window for it to not appear on the screen anymore. 225 | We do this with the XUnmapWindow(3) function. 226 | 227 | This step is optional, however, because when we request for the window 228 | to be destroyed on the server (the next step), it will be automatically 229 | unmapped. 230 | 231 | 6. Close the created resources and objects 232 | Remember that resources reside on the server's memory, not on our 233 | client application's memory. When our program is about to exit, we 234 | must request the server to destroy and free those resources. 235 | 236 | There are various functions to close resources. For windows, we call 237 | XDestroyWindow(3). 238 | 239 | In this case, for xhello, this step is optional, because when we close 240 | the connection to the X server (the next step), all resources are 241 | automatically destroyed. However, if we have allocated any object on 242 | the clients memory, we have to free them with XFree(3) or another 243 | appropriate function. 244 | 245 | 7. Close the connection to the X server 246 | When the program finishes, it needs to close the connection to the X 247 | server. We do this by calling XCloseDisplay(3). 248 | 249 | Calling this function causes all windows created by the program (if any 250 | are left) to be automatically closed by the server, and any resources 251 | stored on the server on behalf of the client to be freed. 252 | 253 | This function also flushes the output buffer, so any request we have 254 | made is performed. 255 | 256 | ENVIRONMENT 257 | The following environment variables affect the execution of xhello 258 | 259 | DISPLAY 260 | The display to start xnotify on. 261 | 262 | SEE ALSO 263 | XOpenDisplay(3), XCreateWindow(3), RootWindow(3), XMapWindow(3) 264 | 265 | Guy Keren, Basic Graphics Programming With The Xlib Library 266 | -------------------------------------------------------------------------------- /xhello/xhello.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #define POSX 200 8 | #define POSY 200 9 | #define WIDTH 350 10 | #define HEIGHT 250 11 | #define BORDER 10 12 | 13 | static Display *dpy; 14 | static Window root; 15 | static int screen; 16 | 17 | /* setup top-level window */ 18 | static Window 19 | createwin(int x, int y, int w, int h, int b) 20 | { 21 | XSetWindowAttributes swa; 22 | Window win; 23 | 24 | /* create top-level window */ 25 | swa.background_pixel = WhitePixel(dpy, screen); 26 | swa.border_pixel = BlackPixel(dpy, screen); 27 | swa.event_mask = ButtonPressMask; 28 | win = XCreateWindow(dpy, root, x, y, w, h, b, 29 | CopyFromParent, CopyFromParent, CopyFromParent, 30 | CWBackPixel | CWBorderPixel | CWEventMask, 31 | &swa); 32 | 33 | return win; 34 | } 35 | 36 | /* enter the event loop */ 37 | static void 38 | run(void) 39 | { 40 | XEvent ev; 41 | 42 | while(XNextEvent(dpy, &ev) == 0) { 43 | switch(ev.type) { 44 | case ButtonPress: 45 | return; 46 | } 47 | } 48 | } 49 | 50 | /* xhello: create and display a basic window with default white background */ 51 | int 52 | main(void) 53 | { 54 | Window win; 55 | 56 | /* open connection to the server */ 57 | if ((dpy = XOpenDisplay(NULL)) == NULL) 58 | errx(1, "could not open display"); 59 | screen = DefaultScreen(dpy); 60 | root = RootWindow(dpy, screen); 61 | 62 | /* setup resources */ 63 | win = createwin(POSX, POSY, WIDTH, HEIGHT, BORDER); 64 | 65 | /* map the window */ 66 | XMapWindow(dpy, win); 67 | 68 | /* run main loop */ 69 | run(); 70 | 71 | /* unmap the window */ 72 | XUnmapWindow(dpy, win); 73 | 74 | /* close resources */ 75 | XDestroyWindow(dpy, win); 76 | 77 | /* close connection to the server */ 78 | XCloseDisplay(dpy); 79 | 80 | return 0; 81 | } 82 | -------------------------------------------------------------------------------- /xoutput1/Makefile: -------------------------------------------------------------------------------- 1 | PROG = xoutput 2 | SRCS = ${PROG}.c 3 | OBJS = ${SRCS:.c=.o} 4 | 5 | INCS = -I/usr/X11R6/include 6 | LIBS = -L/usr/X11R6/lib -lX11 7 | 8 | CC = cc 9 | CFLAGS = -g -O0 -Wall -Wextra ${INCS} 10 | LDFLAGS = ${LIBS} 11 | 12 | all: ${PROG} 13 | 14 | ${PROG}: ${OBJS} 15 | ${CC} -o $@ ${OBJS} ${LDFLAGS} 16 | 17 | .c.o: 18 | ${CC} ${CFLAGS} -c $< 19 | 20 | clean: 21 | -rm ${OBJS} ${PROG} 22 | 23 | test: ${PROG} 24 | ./${PROG} 25 | 26 | .PHONY: all clean test 27 | -------------------------------------------------------------------------------- /xoutput1/README: -------------------------------------------------------------------------------- 1 | XOUTPUT(1) X Notes XOUTPUT(1) 2 | 3 | NAME 4 | xoutput - display text on window 5 | 6 | SYNOPSIS 7 | xoutput string 8 | 9 | DESCRIPTION 10 | xoutput displays a window containing the text passed as argument 11 | centered in it. xoutput reads only one argument, so remember to quote 12 | a string containing spaces for the shell to not break it into several 13 | arguments. 14 | 15 | The window can be closed by clicking on it. 16 | 17 | The objective of this program is to understand and apply the basic 18 | concepts about drawing UTF-8 text using the core X11 font system. 19 | 20 | CONCEPTS 21 | X11 has two font system: the older core X11 font system, and the newer 22 | Xft font system. The core X11 font system originally supported only 23 | monochrome bitmap fonts, but now it can deal with scalable fonts. This 24 | older font system, however, does not support features such as 25 | anti-aliasing and sub-pixel rasterization, which are desirable to have 26 | smooth fonts. Also, the core X11 font system uses a rather inelegant 27 | syntax for selecting fonts called “X Logical Font Description” (XLFD). 28 | 29 | The following is an example of font described with this system: 30 | 31 | -misc-fixed-medium-r-semicondensed--13-120-75-75-c-60-iso8859-1 32 | 33 | The name contains fourteen elements, with each element field preceded 34 | by a hyphen, -. Not all elements are required to be present in a font 35 | name and a field may be empty. Names can be simplified for the user by 36 | the wildcards * and ?. 37 | 38 | -*-dejavu sans mono-medium-r-normal--*-80-*-*-*-*-iso10646-1 39 | 40 | See the sources on the SEE ALSO section to know more about XLFD, and 41 | to select fonts with it. 42 | 43 | To draw a string on a drawable (such as a pixmap or a window) we could 44 | use the XDrawString(3) drawing function, which uses the font specified 45 | on the graphics context we create. See the source code of xcount(1) 46 | for an example of how to use XDrawString(3). The problem with this 47 | function is that we have trouble displaying strings encoded in UTF-8. 48 | To handle such strings, we'll use a set of functions that can deal with 49 | multi-byte encodings such as UTF-8. 50 | 51 | Instead of relying on the font specified in the GC we'll use a set of 52 | fonts. But we still need a graphics context for other stuff, such as 53 | foreground and background color. Check the README of xpaint for more 54 | information about graphics context. To simplify stuff, instead of 55 | creating a GC and set its values, this time we will create a GC with 56 | default values by calling XCreateGC with a value mask of zero. 57 | 58 | We'll also need to set our locale, as it will be needed when loading 59 | the fontset to get the proper character encoding. 60 | 61 | Setting the Locale 62 | First, we need to establish the locale. This is done in two steps: 63 | call setlocale(3) to set the locale, and call XSupportsLocale(3) to 64 | determine if the Xlib implementation supports the current locale. 65 | 66 | setlocale(3) takes two arguments: a locale category and the locale 67 | name. 68 | 69 | The locale category specifies which behaviors of the program 70 | should be affected by the locale. For example, in most operating 71 | systems (OpenBSD not included), LC_NUMERIC affects the decimal 72 | separator output by printf(3); LC_COLLATE affects how strings are 73 | compared with strcoll(3); and so forth. In our case, we need the 74 | behavior set by LC_CTYPE, which affects the character encoding for 75 | text output. In the code we are using LC_ALL, which affects all 76 | behaviors, we could have used LC_CTYPE, however. 77 | 78 | The locale name specifies the localization database that should be 79 | used to localize the program. It must be a string of the form 80 | “LANGUAGE_TERRITORY.ENCODING”, such as “pt_BR.UTF-8”, or the empty 81 | string, which causes setlocale(3) to get the locale name from 82 | environment variables such as LC_CTYPE and LC_COLLATE (or LC_ALL, 83 | which overrides all the others). 84 | 85 | XSupportsLocale(3) must be called immediatelly after setlocale(3) to 86 | determine whether the Xlib implementation supports the current locale. 87 | This function takes no argument and returns True if it supports and 88 | False otherwise. 89 | 90 | Create the Fontset 91 | A fontset is created with XCreateFontSet(3). This function checks the 92 | locale to determine which charsets are required, and loads a set of 93 | fonts that supply those charsets. This function takes a pointer to 94 | display (as usual), a string with a list of font names and some 95 | pointers to variables to be filled in. 96 | 97 | The font name list is a comma-separated list of wildcarded font names. 98 | Generally, you will want to use a very generic font name list, allowing 99 | the user to override it. 100 | 101 | The variables filled by this function are a char **, which gets an 102 | array of strings specifying the charsets for which no font could be 103 | found; an int, which gets the size of that array; and a char *, which 104 | gets the default string that will be drawn in place of characters from 105 | the missing charsets. 106 | 107 | The list of missing charsets should be freed with a call to 108 | XFreeStringList(3). The returned default string should not be freed. 109 | 110 | The fontset must be destroyed after use with a call to XFreeFontSet(3). 111 | 112 | We can get some metrics from the fonts in the fontset with a call to 113 | XExtentsOfFontSet(3). It takes the fontset as sole argument, and 114 | returns a pointer to a XFontSetExtents structure. This structure 115 | should not be freed, as it is used internally by Xlib. 116 | 117 | typedef struct { 118 | XRectangle max_ink_extents; 119 | XRectangle max_logical_extents; 120 | } XFontSetExtents; 121 | 122 | A XRectangle contains four integer members: .x, .y, .width and .height. 123 | It specifies the upper left corner of a rectangle and a positive width 124 | and height. 125 | 126 | The .max_ink_extents rectangle specifies the geometry of all glyphs in 127 | all fonts of the fontset. Note that this geometry is not the size of 128 | the largest glyph, but the size large enough to accomodate the largest 129 | descent, the largest ascent, and so on. 130 | 131 | The .max_logical_extents is the size of .max_ink_extents plus 132 | intercharacter and interline spacing. 133 | 134 | Draw the text 135 | To draw text on a Drawable (a Pixmap or a Window) we call the function 136 | XmbDrawString(3). It gets a pointer to a Display, the drawable, the 137 | fontset, the graphics context (from which it will get the foreground 138 | color, etc), the x and y position of the baseline of the first 139 | character, and the string and its length. 140 | 141 | void XmbDrawString(Display *dpy, Drawable d, XFontSet fontset, 142 | GC gc, int x, int y, char *text, int len); 143 | 144 | Since we want the text to be centered, we need to get the height and 145 | width of the text in order to compute its position. This is done by 146 | calling XmbTextExtents(3), which has the following prototype: 147 | 148 | int XmbTextExtents(XFontSet fontset, char *text, int len, 149 | XRectangle *ink, XRectangle *logical); 150 | 151 | It returns as its value the number of pixels the given string would 152 | require in the x dimension if drawn. It also returns two XRectangles 153 | specifying the size of the drawn text. The second XRectangle contains 154 | all the glyphs plus intercharacter and interline spacing. 155 | 156 | Note that those functions begin with Xmb-, the “mb” is for multi-byte, 157 | which is what we want for UTF-8 (a multi-byte encoding). There is also 158 | similar functions that begin with Xwc- instead, the “wc” is for 159 | wide-character. 160 | 161 | Note that we draw on a Pixmap rather than directly on a Window. We 162 | then copy the Pixmap to our Window whenever we get an Expose event. 163 | 164 | ENVIRONMENT 165 | The following environment variables affect the execution of xoutput. 166 | 167 | DISPLAY 168 | The display to start xnotify on. 169 | 170 | LC_CTYPE 171 | Used to get supported character encoding. 172 | 173 | BUGS 174 | No smooth fonts. 175 | 176 | SEE ALSO 177 | setlocale(3), XSupportsLocale(3), XCreateFontSet(3), 178 | XFreeStringList(3), XFontSetExtents(3), XmbDrawString(3), 179 | XmbTextExtents(3) 180 | 181 | Adrian Nye, Xlib Programming Manual, O'Reilly Media, 1992, 182 | ISBN 1-56592-002-3. 183 | 184 | https://wiki.archlinux.org/index.php/X_Logical_Font_Description 185 | 186 | https://www.x.org/releases/current/doc/xorg-docs/xlfd/xlfd.html 187 | -------------------------------------------------------------------------------- /xoutput1/xoutput.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #define POSX 200 10 | #define POSY 200 11 | #define WIDTH 350 12 | #define HEIGHT 250 13 | #define BORDER 10 14 | #define FONT "-*-dejavu sans mono-medium-r-normal--*-80-*-*-*-*-iso10646-1" 15 | 16 | static Display *dpy; 17 | static Window root; 18 | static int screen; 19 | static int depth; 20 | 21 | /* setup top-level window */ 22 | static Window 23 | createwin(int x, int y, int w, int h, int b) 24 | { 25 | XSetWindowAttributes swa; 26 | Window win; 27 | 28 | /* create top-level window */ 29 | swa.background_pixel = WhitePixel(dpy, screen); 30 | swa.border_pixel = BlackPixel(dpy, screen); 31 | swa.event_mask = ExposureMask | ButtonPressMask; 32 | win = XCreateWindow(dpy, root, x, y, w, h, b, 33 | CopyFromParent, CopyFromParent, CopyFromParent, 34 | CWBackPixel | CWBorderPixel | CWEventMask, 35 | &swa); 36 | 37 | return win; 38 | } 39 | 40 | /* create pixmap */ 41 | static Pixmap 42 | createpix(GC gc, int w, int h) 43 | { 44 | Pixmap pix; 45 | 46 | pix = XCreatePixmap(dpy, root, w, h, depth); 47 | 48 | /* 49 | * we change gc foreground for we to clean the pixmap, 50 | * and then we revert it to its previous value 51 | */ 52 | XSetForeground(dpy, gc, WhitePixel(dpy, screen)); 53 | XFillRectangle(dpy, pix, gc, 0, 0, w, h); 54 | XSetForeground(dpy, gc, BlackPixel(dpy, screen)); 55 | 56 | return pix; 57 | } 58 | 59 | /* create fontset */ 60 | static XFontSet 61 | createfontset(const char *fontname) 62 | { 63 | XFontSet fontset; 64 | char **mc; /* dummy variable; allocated array of missing charsets */ 65 | int nmc; /* dummy variable; number of missing charsets */ 66 | char *ds; /* dummy variable; default string drawn in place of unknown chars */ 67 | 68 | if ((fontset = XCreateFontSet(dpy, fontname, &mc, &nmc, &ds)) == NULL) 69 | errx(1, "XCreateFontSet: could not create fontset"); 70 | XFreeStringList(mc); 71 | return fontset; 72 | } 73 | 74 | /* draw text on pixmap using given graphics context and fontset */ 75 | static void 76 | draw(Drawable pix, GC gc, XFontSet fontset, const char *text) 77 | { 78 | XRectangle box, dummy; 79 | int x, y; 80 | int len; 81 | 82 | len = (int)strlen(text); 83 | XmbTextExtents(fontset, text, len, &dummy, &box); 84 | x = (WIDTH - box.width) / 2 - box.x; 85 | y = (HEIGHT - box.height) / 2 - box.y; 86 | XmbDrawString(dpy, pix, fontset, gc, x, y, text, len); 87 | } 88 | 89 | /* enter the event loop */ 90 | static void 91 | run(GC gc, Pixmap pix) 92 | { 93 | XEvent ev; 94 | 95 | while(XNextEvent(dpy, &ev) == 0) { 96 | switch(ev.type) { 97 | case Expose: 98 | XCopyArea(dpy, pix, ev.xexpose.window, gc, 99 | ev.xexpose.x, ev.xexpose.y, 100 | ev.xexpose.width, ev.xexpose.height, 101 | ev.xexpose.x, ev.xexpose.y); 102 | break; 103 | case ButtonPress: 104 | return; 105 | } 106 | } 107 | } 108 | 109 | /* xoutput: display text on window */ 110 | int 111 | main(int argc, char *argv[]) 112 | { 113 | Window win; 114 | Pixmap pix; 115 | XFontSet fontset; 116 | GC gc; 117 | 118 | if (argc != 2) { /* we need one argument */ 119 | fprintf(stderr, "usage: xoutput string\n"); 120 | return 1; 121 | } 122 | 123 | /* set locale */ 124 | if (!setlocale(LC_ALL, "") || !XSupportsLocale()) 125 | warnx("warning: no locale support"); 126 | 127 | /* open connection to the server */ 128 | if ((dpy = XOpenDisplay(NULL)) == NULL) 129 | errx(1, "could not open display"); 130 | screen = DefaultScreen(dpy); 131 | depth = DefaultDepth(dpy, screen); 132 | root = RootWindow(dpy, screen); 133 | 134 | /* setup resources */ 135 | win = createwin(POSX, POSY, WIDTH, HEIGHT, BORDER); 136 | gc = XCreateGC(dpy, root, 0, NULL); 137 | pix = createpix(gc, WIDTH, HEIGHT); 138 | fontset = createfontset(FONT); 139 | 140 | /* draw argv[1] on the pixmap */ 141 | draw(pix, gc, fontset, argv[1]); 142 | 143 | /* map the window */ 144 | XMapWindow(dpy, win); 145 | 146 | /* run main loop */ 147 | run(gc, pix); 148 | 149 | /* unmap the window */ 150 | XUnmapWindow(dpy, win); 151 | 152 | /* close resources */ 153 | XDestroyWindow(dpy, win); 154 | XFreePixmap(dpy, pix); 155 | XFreeGC(dpy, gc); 156 | XFreeFontSet(dpy, fontset); 157 | 158 | /* close connection to the server */ 159 | XCloseDisplay(dpy); 160 | 161 | return 0; 162 | } 163 | -------------------------------------------------------------------------------- /xoutput2/Makefile: -------------------------------------------------------------------------------- 1 | PROG = xoutput 2 | SRCS = ${PROG}.c 3 | OBJS = ${SRCS:.c=.o} 4 | 5 | INCS = -I/usr/X11R6/include -I/usr/include/freetype2 -I/usr/X11R6/include/freetype2 6 | LIBS = -L/usr/X11R6/lib -lfontconfig -lXft -lX11 7 | 8 | CC = cc 9 | CFLAGS = -g -O0 -Wall -Wextra ${INCS} 10 | LDFLAGS = ${LIBS} 11 | 12 | all: ${PROG} 13 | 14 | ${PROG}: ${OBJS} 15 | ${CC} -o $@ ${OBJS} ${LDFLAGS} 16 | 17 | .c.o: 18 | ${CC} ${CFLAGS} -c $< 19 | 20 | clean: 21 | -rm ${OBJS} ${PROG} 22 | 23 | test: ${PROG} 24 | ./${PROG} 25 | 26 | .PHONY: all clean test 27 | -------------------------------------------------------------------------------- /xoutput2/README: -------------------------------------------------------------------------------- 1 | XOUTPUT(1) X Notes XOUTPUT(1) 2 | 3 | NAME 4 | xoutput - display text on window 5 | 6 | SYNOPSIS 7 | xoutput string 8 | 9 | DESCRIPTION 10 | xoutput displays a window containing the text passed as argument 11 | centered in it. xoutput reads only one argument, so remember to quote 12 | a string containing spaces for the shell to not break it into several 13 | arguments. 14 | 15 | The window can be closed by clicking on it. 16 | 17 | The objective of this program is to understand and apply the basic 18 | concepts about drawing UTF-8 text using Xft. 19 | 20 | CONCEPTS 21 | X11 has two font system: the older core X11 font system, and the newer 22 | Xft font system. The newer Xft font system, unlike the older one, 23 | supports features such as anti-aliasing and sub-pixel rasterization. 24 | Xft uses another library, called fontconfig, to do font selection and 25 | configuration. This is desirable, because fontconfig does not depend 26 | on X11, so fonts can be configured for printing without having X11 27 | running. 28 | 29 | Fontconfig does both font configuration (building an configuration from 30 | XML files) and font matching (which accepts font patterns and returns 31 | the nearest matching font). We will use its font matching capabilities 32 | to get the font that nearest match a pattern string we supply to it. 33 | This string is formed by a font family name and some NAME=VALUE pairs 34 | preceded by a colon. 35 | 36 | FAMILY:NAME1=VALUE1:NAME2=VALUE2... 37 | 38 | For example: 39 | 40 | "Dupree:style=Regular:slant=0:weight=80:width=100" 41 | 42 | See the sources on the SEE ALSO section to know more about Fontconfig, 43 | how to configure it (using its XML configuration files) and how to 44 | match fonts (using its pattern string syntax). 45 | 46 | We'll need to include a new header in our source: 47 | 48 | #include 49 | 50 | When compiling, we may need to specify a new directory to our search 51 | path for included files. This directory contains the headers for 52 | freetype2, a library used by Xft to do font rasterization. 53 | 54 | -I/usr/include/freetype2 55 | 56 | In OpenBSD, this should be: 57 | 58 | -I/usr/X11R6/include/freetype2 59 | 60 | When linking, we'll need to link to fontconfig and Xft. 61 | 62 | -lfontconfig -lXft 63 | 64 | Open the Font 65 | First, we need to open a XftFont. We'll write the function openfont() 66 | that takes a font name and returns a pointer to a XftFont. We'll go 67 | through this function in parts. 68 | 69 | static XftFont *openfont(const char *fontname) { 70 | 71 | First, we need FontConfig to parse our pattern string and generate the 72 | internal data structure of the pattern. We use the FcNameParse(3) for 73 | that. It takes the font name as argument and returns a pointer to a 74 | FcPattern. All fontconfig functions and data types begin with Fc*. We 75 | need to check whether this pointer is null to test for errors. We are 76 | using functions from to handle errors. 77 | 78 | FcPattern *pattern = FcNameParse(fontname); 79 | if (pattern == NULL) 80 | errx(1, "openfont: could not parse font name"); 81 | 82 | The FcPattern structure contains our search pattern, but it is not 83 | ready yet. We must ask fontconfig to fill in missing items. We need 84 | to call FcDefaultSubstitute(3) on our pattern to supply default values 85 | for underspecified patterns (for example, if the weight and slant are 86 | not specified, they will be set to Medium and Roman, respectively). We 87 | also need to call FcConfigSubstitute(3) to substitute items based on 88 | the user's fontconfig configuration. It receives a pointer to a 89 | configuration structure, which in our case is NULL (to use the default 90 | configuration); the pattern to be filled; and the kind of operations we 91 | want to perform, which in our case is FcMatchPattern (pattern 92 | operations). FcDefaultSubstitute(3) returns void, and 93 | FcConfigSubstitute(3) returns either FcTrue (in success) or FcFalse (in 94 | case of allocation failure), which are constants of type FcBool. 95 | 96 | FcDefaultSubstitute(pattern); 97 | FcBool status = FcConfigSubstitute(NULL, pattern, FcMatchPattern); 98 | if (status == FcFalse) 99 | errx(1, "openfont: could not perform pattern substitution"); 100 | 101 | Now, we need to find a font matching our pattern. We use FcFontMatch(3) 102 | for that. It takes a pointer to a configuration structure (which in 103 | our case is NULL), the search pattern, and a pointer to a FcResult 104 | variable that will get the result of the matching. This FcResult can 105 | be FcResultNoMatch, FcResultOutOfMemory, etc, depending on the error. 106 | What matter to us is whether it is FcResultMatch (indicating success). 107 | This function also returns a pointer to a FcPattern structure (this 108 | kind of structure can store either a searching pattern or a matching 109 | pattern). 110 | 111 | FcResult result; 112 | FcPattern *match = FcFontMatch(NULL, pattern, &result); 113 | if (match == NULL || result != FcResultMatch) 114 | errx(1, "openfont: could not find font matching pattern"); 115 | 116 | Finally, we can open the font into a XftFont structure with the 117 | XftFontOpenPattern(3) function call. 118 | 119 | XftFont *font = XftFontOpenPattern(dpy, match); 120 | if (font == NULL) 121 | errx(1, "openfont: could not open font"); 122 | 123 | Before we return the font to the calling function, we need to free the 124 | created patterns with FcPatternDestroy(3). 125 | 126 | FcPatternDestroy(pattern); 127 | FcPatternDestroy(match); 128 | 129 | Now we can return the font. 130 | 131 | return font; 132 | } 133 | 134 | Phew, that's a lot of calls! But we can reduce that. Xft provides 135 | some convenience wrapper functions that call fontconfig functions for 136 | us. The first of them is XftFontMatch(3), which can replace the calls 137 | to FcDefaultSubstitute(3), FcConfigSubstitute(3) and FcFontMatch(3). 138 | The second of them is XftFontOpenName(3), which can replace the entire 139 | openfont() function. Why have we taken the longest path? For 140 | completeness' sake and educational purposes, of course. 141 | 142 | Also, we need to remember to close the font later with XftFontClose(3). 143 | 144 | Allocate the Color 145 | Xft drawing functions do not use a GC (graphics context) to get the 146 | foreground color of the glyphs. Instead, we need to allocate a 147 | XftColor with XftColorAllocName(3) or other color allocation function. 148 | 149 | This function takes the default visual and colormap. We get them using 150 | the DefaultVisual(3) and DefaultColormap(3) macros. 151 | 152 | We call this function in the wrapper function alloccolor(), see the 153 | source code of xoutput to see how it works. 154 | 155 | XftDraw 156 | Xft drawing functions do not take as argument an X Drawable such as a 157 | Pixmap. Instead, it takes a pointer to an XftDraw, a structure that 158 | contains a Drawable and related information such as the Display, the 159 | Visual and the Colormap. We create an XftDraw with XftDrawCreate(3). 160 | 161 | XftDraw *draw = XftDrawCreate(dpy, pixmap, visual, colormap); 162 | 163 | Drawing into this XftDraw will actually draw into the Drawable (the 164 | Pixmap, in our case). 165 | 166 | An XftDraw can be freed with XftDrawDestroy(3). We can free it right 167 | after drawing the text. 168 | 169 | Text Extents 170 | It may be necessary to get the extents of the drawn text before drawing 171 | it. We use XftTextExtentsUtf8(3) to get the size of the text that will 172 | be drawn. It takes the Display, XftFont, the string and its size and a 173 | pointer to a XGlyphInfo structure into which it will return the extents 174 | of the drawing. 175 | 176 | XGlyphInfo ext; 177 | XftTextExtentsUtf8(dpy, font, text, strlen(text), &ext); 178 | 179 | A XGlyphInfo structure contains the .x, .y, .width and .height members, 180 | that gets the position and size of the drawing. It also contains the 181 | .xOff and .yOff members, that gets the position of the next glyph to 182 | be drawn if the text continues. 183 | 184 | Draw text 185 | Finally, we can draw text into our Drawable (via an XftDraw). To do 186 | that, we call XftDrawStringUtf8(3). It takes a pointer to an XftDraw, 187 | a pointer to an XftColor, a pointer to an XftFont, the x and y position 188 | of the baseline of the text to be drawn, and the text string and its 189 | length. 190 | 191 | XftDrawStringUtf8(draw, color, font, x, y, text, strlen(text)); 192 | 193 | In our case, we draw once into the pixmap (via an XftDraw) and then 194 | copy its content into the window when we get an Expose event. 195 | 196 | Closing Stuff 197 | When we're done, we free the XftDraw if we haven't freed it yet. We 198 | also need to free the XftColor and the XftFont with XftColorFree(3) 199 | and XftFontClose(3). 200 | 201 | ENVIRONMENT 202 | The following environment variables affect the execution of xoutput. 203 | 204 | DISPLAY 205 | The display to start xnotify on. 206 | 207 | BUGS 208 | No support to fallback fonts. If a glyph does not exist in the font, 209 | it will be drawn as the missing glyph (generally a box). 210 | 211 | SEE ALSO 212 | Xft(3) 213 | 214 | https://wiki.archlinux.org/index.php/Font_configuration 215 | 216 | https://www.freedesktop.org/software/fontconfig/fontconfig-devel/t1.html 217 | 218 | https://www.x.org/releases/current/doc/xorg-docs/fonts/fonts.html 219 | -------------------------------------------------------------------------------- /xoutput2/xoutput.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #define POSX 200 10 | #define POSY 200 11 | #define WIDTH 350 12 | #define HEIGHT 250 13 | #define BORDER 10 14 | 15 | static char *fontname = "monospace:size=16"; 16 | static char *colorname = "#000000"; 17 | 18 | static Display *dpy; 19 | static Visual *visual; 20 | static Window root; 21 | static Colormap colormap; 22 | static int depth; 23 | static int screen; 24 | 25 | /* setup top-level window */ 26 | static Window 27 | createwin(int x, int y, int w, int h, int b) 28 | { 29 | XSetWindowAttributes swa; 30 | Window win; 31 | 32 | /* create top-level window */ 33 | swa.background_pixel = WhitePixel(dpy, screen); 34 | swa.border_pixel = BlackPixel(dpy, screen); 35 | swa.event_mask = ExposureMask | ButtonPressMask; 36 | win = XCreateWindow(dpy, root, x, y, w, h, b, 37 | CopyFromParent, CopyFromParent, CopyFromParent, 38 | CWBackPixel | CWBorderPixel | CWEventMask, 39 | &swa); 40 | 41 | return win; 42 | } 43 | 44 | static Pixmap 45 | createpix(GC gc, int w, int h) 46 | { 47 | Pixmap pix; 48 | 49 | pix = XCreatePixmap(dpy, root, w, h, depth); 50 | 51 | /* 52 | * we change gc foreground for we to clean the pixmap, 53 | * and then we revert it to its previous value 54 | */ 55 | XSetForeground(dpy, gc, WhitePixel(dpy, screen)); 56 | XFillRectangle(dpy, pix, gc, 0, 0, w, h); 57 | XSetForeground(dpy, gc, BlackPixel(dpy, screen)); 58 | 59 | return pix; 60 | } 61 | 62 | /* open font from its name */ 63 | static XftFont * 64 | openfont(const char *fontname) 65 | { 66 | FcPattern *pattern; 67 | FcPattern *match; 68 | FcResult result; 69 | FcBool status; 70 | XftFont *font; 71 | 72 | /* parse pattern */ 73 | pattern = FcNameParse(fontname); 74 | if (pattern == NULL) 75 | errx(1 ,"openfont: could not parse font name"); 76 | 77 | /* fill in missing items in our pattern */ 78 | FcDefaultSubstitute(pattern); 79 | status = FcConfigSubstitute(NULL, pattern, FcMatchPattern); 80 | if (status == FcFalse) 81 | errx(1, "openfont: could not perform pattern substitution"); 82 | 83 | /* find font matching pattern */ 84 | match = FcFontMatch(NULL, pattern, &result); 85 | if (match == NULL || result != FcResultMatch) 86 | errx(1, "openfont: could not find font matching pattern"); 87 | 88 | /* open font */ 89 | font = XftFontOpenPattern(dpy, match); 90 | if (font == NULL) 91 | errx(1, "openfont: could not open font"); 92 | 93 | /* close patterns */ 94 | FcPatternDestroy(pattern); 95 | FcPatternDestroy(match); 96 | 97 | return font; 98 | } 99 | 100 | /* allocate color for Xft */ 101 | static void 102 | alloccolor(const char *colorname, XftColor *color) 103 | { 104 | if (!XftColorAllocName(dpy, visual, colormap, colorname, color)) 105 | errx(1, "could not allocate color"); 106 | } 107 | 108 | /* draw text on pixmap */ 109 | static void 110 | draw(Drawable pix, XftFont *font, XftColor *color, const char *text) 111 | { 112 | XGlyphInfo ext; 113 | XftDraw *draw; 114 | int x, y; 115 | size_t len; 116 | 117 | draw = XftDrawCreate(dpy, pix, visual, colormap); 118 | 119 | len = strlen(text); 120 | 121 | /* get text extents */ 122 | XftTextExtentsUtf8(dpy, font, text, len, &ext); 123 | x = (WIDTH - ext.width) / 2; 124 | y = (HEIGHT + ext.height) / 2; 125 | 126 | /* draw text */ 127 | XftDrawStringUtf8(draw, color, font, x, y, text, len); 128 | 129 | XftDrawDestroy(draw); 130 | } 131 | 132 | /* enter the event loop */ 133 | static void 134 | run(GC gc, Pixmap pix) 135 | { 136 | XEvent ev; 137 | 138 | while(XNextEvent(dpy, &ev) == 0) { 139 | switch(ev.type) { 140 | case Expose: 141 | XCopyArea(dpy, pix, ev.xexpose.window, gc, 142 | ev.xexpose.x, ev.xexpose.y, 143 | ev.xexpose.width, ev.xexpose.height, 144 | ev.xexpose.x, ev.xexpose.y); 145 | break; 146 | case ButtonPress: 147 | return; 148 | } 149 | } 150 | } 151 | 152 | /* xoutput: display text on window */ 153 | int 154 | main(int argc, char *argv[]) 155 | { 156 | XftColor color; 157 | XftFont *font; 158 | Pixmap pix; 159 | Window win; 160 | GC gc; 161 | 162 | if (argc != 2) { /* we need one argument */ 163 | fprintf(stderr, "usage: xoutput string\n"); 164 | return 1; 165 | } 166 | 167 | /* open connection to the server */ 168 | if ((dpy = XOpenDisplay(NULL)) == NULL) 169 | errx(1, "could not open display"); 170 | screen = DefaultScreen(dpy); 171 | colormap = DefaultColormap(dpy, screen); 172 | visual = DefaultVisual(dpy, screen); 173 | depth = DefaultDepth(dpy, screen); 174 | root = RootWindow(dpy, screen); 175 | 176 | /* setup resources */ 177 | win = createwin(POSX, POSY, WIDTH, HEIGHT, BORDER); 178 | gc = XCreateGC(dpy, root, 0, NULL); 179 | pix = createpix(gc, WIDTH, HEIGHT); 180 | font = openfont(fontname); 181 | alloccolor(colorname, &color); 182 | 183 | /* draw argv[1] on the pixmap */ 184 | draw(pix, font, &color, argv[1]); 185 | 186 | /* map the window */ 187 | XMapWindow(dpy, win); 188 | 189 | /* run main loop */ 190 | run(gc, pix); 191 | 192 | /* unmap the window */ 193 | XUnmapWindow(dpy, win); 194 | 195 | /* close resources */ 196 | XDestroyWindow(dpy, win); 197 | XFreePixmap(dpy, pix); 198 | XFreeGC(dpy, gc); 199 | XftColorFree(dpy, visual, colormap, &color); 200 | XftFontClose(dpy, font); 201 | 202 | /* close connection to the server */ 203 | XCloseDisplay(dpy); 204 | 205 | return 0; 206 | } 207 | -------------------------------------------------------------------------------- /xoutput3/Makefile: -------------------------------------------------------------------------------- 1 | PROG = xoutput 2 | SRCS = ${PROG}.c 3 | OBJS = ${SRCS:.c=.o} 4 | 5 | INCS = -I/usr/X11R6/include -I/usr/include/freetype2 -I/usr/X11R6/include/freetype2 6 | LIBS = -L/usr/X11R6/lib -lfontconfig -lXft -lX11 7 | 8 | CC = cc 9 | CFLAGS = -g -O0 -Wall -Wextra ${INCS} 10 | LDFLAGS = ${LIBS} 11 | 12 | all: ${PROG} 13 | 14 | ${PROG}: ${OBJS} 15 | ${CC} -o $@ ${OBJS} ${LDFLAGS} 16 | 17 | .c.o: 18 | ${CC} ${CFLAGS} -c $< 19 | 20 | clean: 21 | -rm ${OBJS} ${PROG} 22 | 23 | test: ${PROG} 24 | ./${PROG} 25 | 26 | .PHONY: all clean test 27 | -------------------------------------------------------------------------------- /xoutput3/README: -------------------------------------------------------------------------------- 1 | XOUTPUT(1) X Notes XOUTPUT(1) 2 | 3 | NAME 4 | xoutput - display text on window 5 | 6 | SYNOPSIS 7 | xoutput string 8 | 9 | DESCRIPTION 10 | xoutput displays a window containing the text passed as argument 11 | centered in it. xoutput reads only one argument, so remember to quote 12 | a string containing spaces for the shell to not break it into several 13 | arguments. 14 | 15 | The window can be closed by clicking on it. 16 | 17 | The objective of this program is to handle multiple fonts using 18 | fontconfig and Xft. 19 | 20 | CONCEPTS 21 | Our last version of xoutput was limited to one font. Thus, if a glyph 22 | were not supported by a font, this glyph would not be drawn; instead a 23 | replacement glyph, probably a box, was drawn in its place. Differently 24 | from the core X11 text drawing function we used in the first version of 25 | xoutput (which used a fontset to get glyphs from) Xft uses a single 26 | font. There is not an API within Xft to switch among multiple fonts. 27 | So we have to write in our code a function that does that. 28 | 29 | In this version of xoutput we're using a manually selected set of fonts. 30 | So, if you want to display Latin and Japanese characters, specify fonts 31 | supporting both Latin and Japanese charsets. In the code, I specified 32 | three fonts: monospace, DejaVu Sans Mono, and NotoSans Mono CJK HK 33 | (check the fontnames[] array). Your system may not have those fonts, 34 | so adapt this list to your system. In a next version of xoutput, 35 | we'll use fontconfig to generate for us a fontset with the default user 36 | configuration. 37 | 38 | The Fontset 39 | Differently from our previous version, fontnames now is an array of 40 | strings, which has the names of the fonts we'll use. 41 | 42 | There are two new data types we create: FontEntry and FontSet. FontSet 43 | contains just an array of FontEntry and the number of elements in this 44 | array. A FontEntry contains a FcPattern, a FcCharSet, a XftFont and a 45 | integer flag. Since we specified three fonts in fontnames[], (you can 46 | specify more) this array will have three FontEntry's. 47 | 48 | typedef struct FontEntry { 49 | FcPattern *match; 50 | FcCharSet *charset; 51 | XftFont *font; 52 | int open; 53 | } FontEntry; 54 | 55 | FcPattern *match; 56 | This is the pattern matching a font we have specified. 57 | 58 | FcCharSet *charset; 59 | A FcCharSet contains the set of Unicode characters in the font. 60 | We will use this element to check whether a given character can 61 | be drawn using a given font. 62 | 63 | XftFont *font; 64 | This is the actual font we open using XftFontOpenPattern(3). We 65 | will not open all the fonts when we create the fontset. Instead, 66 | we will open fonts on demand: if we want to draw a glyph, and a 67 | font is the first one to support this glyph, we'll open this 68 | font and use it. 69 | 70 | int open; 71 | Just a flag that is set to nonzero when we try to open a font. 72 | We use it to not try to open a font twice when we could not open 73 | it a first time. 74 | 75 | There are other ways to specify a fontset. You can use a linked list, 76 | for example. Since we will traverse the fontset linearly, from begin 77 | to end, looking for a font that supports a character, a linked list is 78 | also feasible. 79 | 80 | Create the Font Set 81 | We'll write a function createfontset() to create a font set. First we 82 | allocate a FontSet and allocate an array of FontEntry's in it, and set 83 | fontset->nfonts to the number of fonts we have. 84 | 85 | Now, we loop through the entries of the array of FontEntry's. Here is 86 | the code for this loop, line by line. 87 | 88 | First, we set the members of the FontEntry to zeroes and nulls. A NULL 89 | .match and .charset members indicate that we could not find a match to 90 | this font entry. The .font and .open members will be zero and NULL for 91 | now until we open the fonts on demand. 92 | 93 | fontset->fonts[i].open = 0; 94 | fontset->fonts[i].font = NULL; 95 | fontset->fonts[i].match = NULL; 96 | fontset->fonts[i].charset = NULL; 97 | 98 | Then, we try to parse the i-th font name and open a pattern on it. 99 | Just like we have done in the previous version. But now, instead of 100 | exiting on error, we warn that a font name could not be parsed, and 101 | continue on the fontset creation. 102 | 103 | FcPattern *pattern = FcNameParse(fontnames[i]); 104 | if (pattern == NULL) { 105 | warnx("openfont: could not parse font name"); 106 | continue; 107 | } 108 | 109 | Our pattern is not ready yet. We do the required substitutions with 110 | fontconfig substitute functions. If we could not perform them, we warn 111 | and destroy the pattern we have created, and continue. 112 | 113 | FcDefaultSubstitute(pattern); 114 | FcBool status = FcDefaultSubstitute(NULL, pattern, FcMatchPattern); 115 | if (status == FcFalse) { 116 | warnx("openfont: could not perform pattern substitution"); 117 | FcPatternDestroy(pattern); 118 | continue; 119 | } 120 | 121 | After we have done the substitutions, we can find a font matching our 122 | pattern. If we couldn't find, we warns, destroy the searching pattern 123 | and continue. 124 | 125 | FcResult result; 126 | FcPattern *match = FcFontMatch(NULL, pattern, &result); 127 | if (match == NULL || result != FcResultMatch) { 128 | warnx("openfont: could not find font matching pattern"); 129 | FcPatternDestroy(pattern); 130 | continue; 131 | } 132 | 133 | Then, we need to get the character sets the font support. We use the 134 | fontconfig function FcPatternGetCharSet(3). Its second argument is the 135 | name of the entry we are interested in (the charset). And the third 136 | argument must be zero. They are necessary because FcPatternGetCharSet 137 | is a wrapper around FcPatternGet, which returns an arbitrary entry from 138 | a match pattern. We should not free the charset, as the value returned 139 | in it does not point to a copy of the entry, but to the entry itself. 140 | 141 | FcCharSet *charset = NULL 142 | result = FcPatternGetCharSet(match, FC_CHARSET, 0, &charset); 143 | if (charset == NULL || result != FcResultMatch) { 144 | warnx("openfont: could not get charset of font"); 145 | FcPatternDestroy(pattern); 146 | FcPatternDestroy(match); 147 | continue; 148 | } 149 | 150 | Finally we set the FontEntry's match and charset members to what we 151 | have get in this iteration of the loop. 152 | 153 | fontset->fonts[i].match = match; 154 | fontset->fonts[i].charset = charset; 155 | 156 | And we can destroy the search pattern. We should not destroy match 157 | pattern, as we will use that to refer to the font when we open it. 158 | 159 | FcPatternDestroy(pattern); 160 | 161 | We free the malloc'ed font set with destroyufontset(). 162 | 163 | Draw the text 164 | Our drawing function now calls drawtext(), a function we create to 165 | check character-by-character in our string for the font that support 166 | it. 167 | 168 | There are two loops, the outer one loops through the string while it 169 | contains a character. The inner loop also loops through the string 170 | while it contains a character, but also while the current font to use 171 | is equal to the next font to use. 172 | 173 | In this inner loop we get the next UTF-8 character using a call to 174 | getnextutf8char(). Then we get the font, from the fontset that 175 | supports it. If this font is different from the one supporting the 176 | previous character, we exit the loop. 177 | 178 | Out of the inner loop, we get the length of the part of the string that 179 | can be drawn using the current font, and draw this part of the string. 180 | 181 | Next we get the position where the next part of the string should be 182 | drawn in the next iteration. We use XftTextExtentsUtf8 for it. And 183 | increment the x position. 184 | 185 | Finally, we make the string point to the next part of it. 186 | 187 | There is also a function, called textextents that get the size of the 188 | drawn text. In fact, its structure is similar to drawtext. We will 189 | merge those two functions in the next version of xoutput. 190 | 191 | Get Font Supporting UTF-8 Character 192 | The function getfontucode loops through our fontset looking for a font 193 | that supports the given UTF-8 character. 194 | 195 | In each iteration of the loop, we check whether charset is NULL, if it 196 | is, we could not have got it in the function that creates the fontset. 197 | But if we have a charset for the current font, then we use the function 198 | FcCharSetHasChar to check whether the font's charset contains the given 199 | UTF-8 char. 200 | 201 | If the charset contains the given character, then we test whether we 202 | have opened the font. If we haven't we open it. Then we return the 203 | font. 204 | 205 | If no font could be found to support the character, we still have to 206 | return a font. In a second loop, we return the first font that we 207 | found. 208 | 209 | If no font was found, we return NULL. This case will be handled in 210 | drawtext(). If getfontucode returns NULL, we exit on error. 211 | 212 | Get Next UTF-8 character. 213 | This function is a tricky one that requires knowledge of the UTF-8 214 | encoding and some Unicode blocks, like the surrogate halves. 215 | 216 | This function takes a string and a pointer to string. It returns a 217 | FcChar32, which is fontconfig's type for a 32-bit character (an UTF-8 218 | character can have at most 32 bits). The returned value is the code 219 | point for the first UTF-8 character in the first string passed as 220 | argument. It also return in the second string the pointer to the 221 | next UTF-8 character. 222 | 223 | ENVIRONMENT 224 | The following environment variables affect the execution of xoutput. 225 | 226 | DISPLAY 227 | The display to start xnotify on. 228 | 229 | BUGS 230 | The programmer (or user) has to predict which characters will be drawn 231 | and specify the list of fonts supporting those characters. It would be 232 | better to get this list of fonts from the set of fonts the user has 233 | installed. 234 | 235 | SEE ALSO 236 | Xft(3) 237 | 238 | https://www.freedesktop.org/software/fontconfig/fontconfig-devel/t1.html 239 | -------------------------------------------------------------------------------- /xoutput3/xoutput.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #define POSX 200 10 | #define POSY 200 11 | #define WIDTH 350 12 | #define HEIGHT 250 13 | #define BORDER 10 14 | 15 | /* get length of array */ 16 | #define LEN(x) (sizeof (x) / sizeof (x[0])) 17 | 18 | /* get whether x is between a and b (inclusive) */ 19 | #define BETWEEN(x, a, b) ((a) <= (x) && (x) <= (b)) 20 | 21 | /* get maximum between x and y */ 22 | #define MAX(x,y) ((x)>(y)?(x):(y)) 23 | 24 | /* each entry of our fontset */ 25 | typedef struct FontEntry { 26 | FcPattern *match; /* the pattern matching the font */ 27 | FcCharSet *charset; /* the charset of the font */ 28 | XftFont *font; /* the opened font */ 29 | int open; /* whether we have tried to open the font */ 30 | } FontEntry; 31 | 32 | /* hold an array of FontEntry and its size */ 33 | typedef struct FontSet { 34 | struct FontEntry *fonts; 35 | size_t nfonts; 36 | } FontSet; 37 | 38 | static char *fontnames[] = { 39 | "monospace:size=16", 40 | "DejaVu Sans Mono:size=16", 41 | "NotoSans Mono CJK HK:size=16" 42 | }; 43 | static char *colorname = "#000000"; 44 | 45 | static Display *dpy; 46 | static Visual *visual; 47 | static Window root; 48 | static Colormap colormap; 49 | static int depth; 50 | static int screen; 51 | 52 | /* setup top-level window */ 53 | static Window 54 | createwin(int x, int y, int w, int h, int b) 55 | { 56 | XSetWindowAttributes swa; 57 | Window win; 58 | 59 | /* create top-level window */ 60 | swa.background_pixel = WhitePixel(dpy, screen); 61 | swa.border_pixel = BlackPixel(dpy, screen); 62 | swa.event_mask = ExposureMask | ButtonPressMask; 63 | win = XCreateWindow(dpy, root, x, y, w, h, b, 64 | CopyFromParent, CopyFromParent, CopyFromParent, 65 | CWBackPixel | CWBorderPixel | CWEventMask, 66 | &swa); 67 | 68 | return win; 69 | } 70 | 71 | static Pixmap 72 | createpix(GC gc, int w, int h) 73 | { 74 | Pixmap pix; 75 | 76 | pix = XCreatePixmap(dpy, root, w, h, depth); 77 | 78 | /* 79 | * we change gc foreground for we to clean the pixmap, 80 | * and then we revert it to its previous value 81 | */ 82 | XSetForeground(dpy, gc, WhitePixel(dpy, screen)); 83 | XFillRectangle(dpy, pix, gc, 0, 0, w, h); 84 | XSetForeground(dpy, gc, BlackPixel(dpy, screen)); 85 | 86 | return pix; 87 | } 88 | 89 | /* create fontset from an array of font names */ 90 | static FontSet * 91 | createfontset(char *fontnames[], size_t nfonts) 92 | { 93 | FontSet *fontset; 94 | FcCharSet *charset; 95 | FcPattern *pattern; 96 | FcPattern *match; 97 | FcResult result; 98 | FcBool status; 99 | size_t i; 100 | 101 | if ((fontset = malloc(sizeof *fontset)) == NULL) 102 | err(1, "malloc"); 103 | if ((fontset->fonts = malloc(nfonts * sizeof *(fontset->fonts))) == NULL) 104 | err(1, "malloc"); 105 | fontset->nfonts = nfonts; 106 | 107 | for (i = 0; i < nfonts; i++) { 108 | fontset->fonts[i].open = 0; 109 | fontset->fonts[i].font = NULL; 110 | fontset->fonts[i].match = NULL; 111 | fontset->fonts[i].charset = NULL; 112 | 113 | /* parse pattern */ 114 | pattern = FcNameParse(fontnames[i]); 115 | if (pattern == NULL) { 116 | warnx("openfont: could not parse font name"); 117 | continue; 118 | } 119 | 120 | /* fill in missing items in our pattern */ 121 | FcDefaultSubstitute(pattern); 122 | status = FcConfigSubstitute(NULL, pattern, FcMatchPattern); 123 | if (status == FcFalse) { 124 | warnx("openfont: could not perform pattern substitution"); 125 | FcPatternDestroy(pattern); 126 | continue; 127 | } 128 | 129 | /* find font matching pattern */ 130 | match = FcFontMatch(NULL, pattern, &result); 131 | if (match == NULL || result != FcResultMatch) { 132 | warnx("openfont: could not find font matching pattern"); 133 | FcPatternDestroy(pattern); 134 | continue; 135 | } 136 | 137 | /* get charset of font */ 138 | charset = NULL; 139 | result = FcPatternGetCharSet(match, FC_CHARSET, 0, &charset); 140 | if (charset == NULL || result != FcResultMatch) { 141 | warnx("openfont: could not get charset of font %s", fontnames[i]); 142 | FcPatternDestroy(pattern); 143 | FcPatternDestroy(match); 144 | continue; 145 | } 146 | 147 | /* assign match pattern and charset to font in our fontset */ 148 | fontset->fonts[i].match = match; 149 | fontset->fonts[i].charset = charset; 150 | 151 | /* close patterns */ 152 | FcPatternDestroy(pattern); 153 | } 154 | return fontset; 155 | } 156 | 157 | /* destroy fontset and all its fonts */ 158 | static void 159 | destroyfontset(FontSet *fontset) 160 | { 161 | free(fontset->fonts); 162 | free(fontset); 163 | } 164 | 165 | /* allocate color for Xft */ 166 | static void 167 | alloccolor(const char *colorname, XftColor *color) 168 | { 169 | if (!XftColorAllocName(dpy, visual, colormap, colorname, color)) 170 | errx(1, "could not allocate color"); 171 | } 172 | 173 | /* get next utf8 char from s return its codepoint and set next_ret to pointer to end of character */ 174 | static FcChar32 175 | getnextutf8char(const char *s, const char **next_ret) 176 | { 177 | static const unsigned char utfbyte[] = {0x80, 0x00, 0xC0, 0xE0, 0xF0}; 178 | static const unsigned char utfmask[] = {0xC0, 0x80, 0xE0, 0xF0, 0xF8}; 179 | static const FcChar32 utfmin[] = {0, 0x00, 0x80, 0x800, 0x10000}; 180 | static const FcChar32 utfmax[] = {0, 0x7F, 0x7FF, 0xFFFF, 0x10FFFF}; 181 | /* 0xFFFD is the replacement character, used to represent unknown characters */ 182 | static const FcChar32 unknown = 0xFFFD; 183 | FcChar32 ucode; /* FcChar32 type holds 32 bits */ 184 | size_t usize = 0; /* n' of bytes of the utf8 character */ 185 | size_t i; 186 | 187 | *next_ret = s+1; 188 | 189 | /* get code of first byte of utf8 character */ 190 | for (i = 0; i < sizeof utfmask; i++) { 191 | if (((unsigned char)*s & utfmask[i]) == utfbyte[i]) { 192 | usize = i; 193 | ucode = (unsigned char)*s & ~utfmask[i]; 194 | break; 195 | } 196 | } 197 | 198 | /* if first byte is a continuation byte or is not allowed, return unknown */ 199 | if (i == sizeof utfmask || usize == 0) 200 | return unknown; 201 | 202 | /* check the other usize-1 bytes */ 203 | s++; 204 | for (i = 1; i < usize; i++) { 205 | *next_ret = s+1; 206 | /* if byte is nul or is not a continuation byte, return unknown */ 207 | if (*s == '\0' || ((unsigned char)*s & utfmask[0]) != utfbyte[0]) 208 | return unknown; 209 | /* 6 is the number of relevant bits in the continuation byte */ 210 | ucode = (ucode << 6) | ((unsigned char)*s & ~utfmask[0]); 211 | s++; 212 | } 213 | 214 | /* check if ucode is invalid or in utf-16 surrogate halves */ 215 | if (!BETWEEN(ucode, utfmin[usize], utfmax[usize]) || BETWEEN (ucode, 0xD800, 0xDFFF)) 216 | return unknown; 217 | 218 | return ucode; 219 | } 220 | 221 | /* get which font contains a given code point */ 222 | static XftFont * 223 | getfontucode(FontSet *fontset, FcChar32 ucode) 224 | { 225 | size_t i; 226 | 227 | for (i = 0; i < fontset->nfonts; i++) { 228 | if (fontset->fonts[i].charset == NULL) { 229 | continue; 230 | } 231 | if (FcCharSetHasChar(fontset->fonts[i].charset, ucode) == FcTrue) { 232 | if (fontset->fonts[i].open == 0) { 233 | fontset->fonts[i].open = 1; 234 | fontset->fonts[i].font = XftFontOpenPattern(dpy, fontset->fonts[i].match); 235 | } 236 | if (fontset->fonts[i].font != NULL) { 237 | return fontset->fonts[i].font; 238 | } 239 | } 240 | } 241 | 242 | /* if we have not found a font, we still have to return one */ 243 | for (i = 0; i < fontset->nfonts; i++) 244 | if (fontset->fonts[i].font) 245 | return fontset->fonts[i].font; 246 | 247 | return NULL; 248 | } 249 | 250 | /* get width and ascend height of text when drawn */ 251 | static void 252 | textextents(FontSet *fontset, const char *text, int *w, int *h) 253 | { 254 | XftFont *currfont, *nextfont = NULL; 255 | XGlyphInfo ext; 256 | FcChar32 ucode; 257 | const char *next, *tmp; 258 | size_t len = 0; 259 | 260 | *w = *h = 0; 261 | while (*text) { 262 | tmp = text; 263 | do { 264 | next = tmp; 265 | currfont = nextfont; 266 | ucode = getnextutf8char(next, &tmp); 267 | nextfont = getfontucode(fontset, ucode); 268 | if (nextfont == NULL) 269 | errx(1, "drawtext: could not find font for character"); 270 | } while (*next && currfont == nextfont); 271 | if (!currfont) 272 | continue; 273 | len = next - text; 274 | XftTextExtentsUtf8(dpy, currfont, (XftChar8 *)text, len, &ext); 275 | *w += ext.xOff; 276 | *h = MAX(*h, ext.height - ext.y); 277 | text = next; 278 | } 279 | } 280 | 281 | /* draw text into XftDraw */ 282 | static void 283 | drawtext(FontSet *fontset, XftDraw *draw, XftColor *color, int x, int y, const char *text) 284 | { 285 | XftFont *currfont, *nextfont = NULL; 286 | XGlyphInfo ext; 287 | FcChar32 ucode; 288 | const char *next, *tmp; 289 | size_t len = 0; 290 | 291 | while (*text) { 292 | tmp = text; 293 | do { 294 | next = tmp; 295 | currfont = nextfont; 296 | ucode = getnextutf8char(next, &tmp); 297 | nextfont = getfontucode(fontset, ucode); 298 | if (nextfont == NULL) 299 | errx(1, "drawtext: could not find font for character"); 300 | } while (*next && currfont == nextfont); 301 | if (!currfont) 302 | continue; 303 | len = next - text; 304 | XftDrawStringUtf8(draw, color, currfont, x, y, (XftChar8 *)text, len); 305 | XftTextExtentsUtf8(dpy, currfont, (XftChar8 *)text, len, &ext); 306 | x += ext.xOff; 307 | text = next; 308 | } 309 | } 310 | 311 | /* draw text on pixmap */ 312 | static void 313 | draw(Drawable pix, FontSet *fontset, XftColor *color, const char *text) 314 | { 315 | XftDraw *draw; 316 | int x, y, w, h; 317 | 318 | draw = XftDrawCreate(dpy, pix, visual, colormap); 319 | 320 | /* get text extents */ 321 | textextents(fontset, text, &w, &h); 322 | x = (WIDTH - w) / 2; 323 | y = (HEIGHT + h) / 2; 324 | 325 | /* draw text */ 326 | drawtext(fontset, draw, color, x, y, text); 327 | 328 | XftDrawDestroy(draw); 329 | } 330 | 331 | /* enter the event loop */ 332 | static void 333 | run(GC gc, Pixmap pix) 334 | { 335 | XEvent ev; 336 | 337 | while(XNextEvent(dpy, &ev) == 0) { 338 | switch(ev.type) { 339 | case Expose: 340 | XCopyArea(dpy, pix, ev.xexpose.window, gc, 341 | ev.xexpose.x, ev.xexpose.y, 342 | ev.xexpose.width, ev.xexpose.height, 343 | ev.xexpose.x, ev.xexpose.y); 344 | break; 345 | case ButtonPress: 346 | return; 347 | } 348 | } 349 | } 350 | 351 | /* xoutput: display text on window */ 352 | int 353 | main(int argc, char *argv[]) 354 | { 355 | FontSet *fontset; 356 | XftColor color; 357 | Pixmap pix; 358 | Window win; 359 | GC gc; 360 | 361 | if (argc != 2) { /* we need one argument */ 362 | fprintf(stderr, "usage: xoutput string\n"); 363 | return 1; 364 | } 365 | 366 | /* open connection to the server */ 367 | if ((dpy = XOpenDisplay(NULL)) == NULL) 368 | errx(1, "could not open display"); 369 | screen = DefaultScreen(dpy); 370 | colormap = DefaultColormap(dpy, screen); 371 | visual = DefaultVisual(dpy, screen); 372 | depth = DefaultDepth(dpy, screen); 373 | root = RootWindow(dpy, screen); 374 | 375 | /* setup resources */ 376 | win = createwin(POSX, POSY, WIDTH, HEIGHT, BORDER); 377 | gc = XCreateGC(dpy, root, 0, NULL); 378 | pix = createpix(gc, WIDTH, HEIGHT); 379 | fontset = createfontset(fontnames, LEN(fontnames)); 380 | alloccolor(colorname, &color); 381 | 382 | /* draw argv[1] on the pixmap */ 383 | draw(pix, fontset, &color, argv[1]); 384 | 385 | /* map the window */ 386 | XMapWindow(dpy, win); 387 | 388 | /* run main loop */ 389 | run(gc, pix); 390 | 391 | /* unmap the window */ 392 | XUnmapWindow(dpy, win); 393 | 394 | /* close resources */ 395 | XDestroyWindow(dpy, win); 396 | XFreePixmap(dpy, pix); 397 | XFreeGC(dpy, gc); 398 | XftColorFree(dpy, visual, colormap, &color); 399 | destroyfontset(fontset); 400 | 401 | /* close connection to the server */ 402 | XCloseDisplay(dpy); 403 | 404 | return 0; 405 | } 406 | -------------------------------------------------------------------------------- /xpaint1/Makefile: -------------------------------------------------------------------------------- 1 | PROG = xpaint 2 | SRCS = ${PROG}.c 3 | OBJS = ${SRCS:.c=.o} 4 | 5 | INCS = -I/usr/X11R6/include 6 | LIBS = -L/usr/X11R6/lib -lX11 7 | 8 | CC = cc 9 | CFLAGS = -g -O0 -Wall -Wextra ${INCS} 10 | LDFLAGS = ${LIBS} 11 | 12 | all: ${PROG} 13 | 14 | ${PROG}: ${OBJS} 15 | ${CC} -o $@ ${OBJS} ${LDFLAGS} 16 | 17 | .c.o: 18 | ${CC} ${CFLAGS} -c $< 19 | 20 | clean: 21 | -rm ${OBJS} ${PROG} 22 | 23 | test: ${PROG} 24 | ./${PROG} 25 | 26 | .PHONY: all clean test 27 | -------------------------------------------------------------------------------- /xpaint1/README: -------------------------------------------------------------------------------- 1 | XPAINT(1) X Notes XPAINT(1) 2 | 3 | NAME 4 | xpaint - paint lines with the mouse 5 | 6 | SYNOPSIS 7 | xpaint 8 | 9 | DESCRIPTION 10 | This program displays a blank window. Pressing the left mouse button 11 | and moving the pointer draws a line on the window. 12 | 13 | The objective of this program is to understand and apply the basic 14 | concepts about graphics context and drawing functions. 15 | 16 | CONCEPTS 17 | Using Xlib functions we can draw lines, polygons, rectangles, circles, 18 | and several other stuff on our windows. There are two concepts we need 19 | to know that are related to drawing: the graphics context and the 20 | drawing functions. 21 | 22 | Graphics Context 23 | There are several variables that control how to stuff should be drawn: 24 | the foreground color, the background color, the width of the lines we 25 | draw, how the lines connect to each other, etc. Rather than supplying 26 | all those variables to the drawing functions, we create a graphics 27 | context. 28 | 29 | A graphics context, of type GC, is a resource that specifies all the 30 | details for the drawing functions. We create a graphics context with 31 | the XCreateGC(3) function. This function receives the pointer to the 32 | Display structure; a drawable (we'll talk about drawables in another 33 | lesson), which, for our purposes, is the same as a Window; and a 34 | pointer to a structure containing the various options of our graphics 35 | context and a bitwise inclusive OR of the members of this structure 36 | that we have set. 37 | 38 | The window we must pass to XCreateGC(3) is used for the function to get 39 | the color depth of the window. We can pass the root window or any 40 | window that has the same depth and the same root window as the one 41 | we want to draw on. 42 | 43 | We can create a default graphics context by passing the value mask as 44 | zero and the pointer as NULL. But in our program, we have specified 45 | the graphics context by ourselves. We create a XGCValues structure and 46 | fill its members with the values we want. The following list specifies 47 | some members of this structure and their respective mask in parentheses. 48 | Read the man page for XCreateGC(3) for more information on the members 49 | of this structure, their meaning, and their corresponding mask. 50 | 51 | .foreground (GCForeground) 52 | The foreground color. 53 | 54 | .background (GCBackground) 55 | The background color. 56 | 57 | .cap_style (GCCapStyle) 58 | How the tip of a line is drawn. Its value can be CapButt (the 59 | line tip is square), CapRound (The line tip is round), or 60 | CapProjecting (the line tip is a square and projects beyond 61 | the tip). 62 | 63 | .join_style (GCJoinStyle) 64 | How the corners between two straight lines are drawn. Its value 65 | can be JoinMiter (lines extend to meet at an angle), JoinRound 66 | (the corner is a circular arc), or JoinBevel (the corner has a 67 | triangular notch filled). 68 | 69 | .fill_style (GCFillStyle) 70 | How shapes are filled. Its value can be FillSolid (paint 71 | with the foreground color), FillTiled (tile), or FillStippled 72 | (foreground masked by stiple). 73 | 74 | .line_style (GCLineStyle) 75 | How lines are drawn. Its value can be LineSolid (the full path 76 | of the line is drawn), LineDoubleDash (the full path of the line 77 | is drawn, but the even dashes are filled differently from the 78 | odd dashes), or LineOnOffDash (only the even dashes are drawn). 79 | 80 | .line_width (GCLineWidth) 81 | The width of the line. 82 | 83 | Experiment changing the values of those members in the code of xpaint 84 | to see how it affects the drawing. 85 | 86 | Drawing functions 87 | For most of the primitives (lines, points, rectangles, etc.) that can 88 | be drawn, there is a singular function (to draw a single object), and 89 | a plural function (to draw a set of objects). For example, we can draw 90 | a single line with XDrawLine(3) or several points with XDrawLines(3). 91 | Each plural function receives a pointer to an array of structures that 92 | specify the each object to be drawn. For example, XDrawPoints(3) 93 | receives a pointer to an array of a XPoint structure, which has only 94 | two members, an x and a y integers. 95 | 96 | For some primitives (mostly shapes) there is a Draw- function, that only 97 | draws the outline of the shape, and a Fill- function that draws a filled 98 | shape. For example, XDrawRectangle(3) only draws the outline of a 99 | rectangle, while XFillRectangle(3) draws the outline of a rectangle and 100 | fill its inner part. Some primitives, like points and lines do not 101 | have a Fill- function. 102 | 103 | The table below lists the singular drawing functions, their plural form, 104 | and the manual for them. 105 | 106 | ┌────────────────────┬────────────────────┬────────────────────┬────────────────────┐ 107 | │ Singular Draw- │ Plural draw- │ Singular Fill- │ Plural Fill- │ 108 | ╞════════════════════╪════════════════════╪════════════════════╪════════════════════╡ 109 | │ XDrawPoint(3) │ XDrawPoints(3) │ - │ - │ 110 | ├────────────────────┼────────────────────┼────────────────────┼────────────────────┤ 111 | │ XDrawLine(3) │ XDrawLines(3) │ - │ - │ 112 | ├────────────────────┼────────────────────┼────────────────────┼────────────────────┤ 113 | │ - │ - │ XFillPolygon(3) │ - │ 114 | ├────────────────────┼────────────────────┼────────────────────┼────────────────────┤ 115 | │ XDrawArc(3) │ XDrawArcs(3) │ XFillArc(3) │ XFillArcs(3) │ 116 | ├────────────────────┼────────────────────┼────────────────────┼────────────────────┤ 117 | │ XDrawRectangle(3) │ XDrawRectangles(3) │ XFillRectangle(3) │ XFillRectangles(3) │ 118 | └────────────────────┴────────────────────┴────────────────────┴────────────────────┘ 119 | 120 | Changing a Graphics Context 121 | After we have created a graphics context, we can change some of its 122 | attributes with the following functions. 123 | 124 | XSetLineAttributes(3) 125 | This function can be used to change the attributes related to 126 | line drawing (line width, line style, cap style, and join 127 | style). 128 | 129 | XSetForeground(3) 130 | This function can be used to change the foreground color. 131 | 132 | XSetBackground(3) 133 | This function can be used to change the background color. 134 | 135 | ENVIRONMENT 136 | The following environment variables affect the execution of xpaint. 137 | 138 | DISPLAY 139 | The display to start xnotify on. 140 | 141 | BUGS 142 | Whenever another window covers the xpaint window, the drawing in the 143 | region covered is lost. We will fix this problem in another version. 144 | 145 | We have not dealt with text drawing. That's because we will use 146 | another library for drawing text with TrueType fonts. To know how 147 | to draw text with Xlib, see the code for xcount. 148 | 149 | SEE ALSO 150 | XCreateGC(3), XSetLineAttributes(3), XSetForeground(3), 151 | XSetBackground(3) 152 | -------------------------------------------------------------------------------- /xpaint1/xpaint.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #define POSX 200 9 | #define POSY 200 10 | #define WIDTH 350 11 | #define HEIGHT 250 12 | #define BORDER 10 13 | #define LINE 2 14 | 15 | static Display *dpy; 16 | static Window root; 17 | static int screen; 18 | 19 | /* setup top-level window */ 20 | static Window 21 | createwin(int x, int y, int w, int h, int b) 22 | { 23 | XSetWindowAttributes swa; 24 | Window win; 25 | 26 | /* create top-level window */ 27 | swa.background_pixel = WhitePixel(dpy, screen); 28 | swa.border_pixel = BlackPixel(dpy, screen); 29 | swa.event_mask = ButtonPressMask | Button1MotionMask | ButtonReleaseMask | KeyPressMask; 30 | win = XCreateWindow(dpy, root, x, y, w, h, b, 31 | CopyFromParent, CopyFromParent, CopyFromParent, 32 | CWBackPixel | CWBorderPixel | CWEventMask, 33 | &swa); 34 | 35 | return win; 36 | } 37 | 38 | /* create a graphics context */ 39 | static GC 40 | creategc(int linewidth) 41 | { 42 | GC gc; 43 | XGCValues values; 44 | unsigned long valuemask; 45 | 46 | /* setup the values for our graphics context */ 47 | values.foreground = BlackPixel(dpy, screen); 48 | values.background = WhitePixel(dpy, screen); 49 | values.cap_style = CapButt; /* line's end-point is straight */ 50 | values.join_style = JoinBevel; /* join lines in "bevelian" style */ 51 | values.fill_style = FillSolid; 52 | values.line_style = LineSolid; 53 | values.line_width = linewidth; /* line width in pixels */ 54 | 55 | /* set valuemask to the values we have set */ 56 | valuemask = GCForeground | GCBackground | GCCapStyle | GCJoinStyle | 57 | GCFillStyle | GCLineStyle | GCLineWidth; 58 | 59 | /* create our graphics context */ 60 | gc = XCreateGC(dpy, root, valuemask, &values); 61 | 62 | return gc; 63 | } 64 | 65 | /* enter the event loop */ 66 | static void 67 | run(GC gc) 68 | { 69 | XEvent ev; 70 | int init = 0; /* whether we are drawing a line */ 71 | int lx, ly; /* last x and y positions */ 72 | 73 | while(XNextEvent(dpy, &ev) == 0) { 74 | switch(ev.type) { 75 | case ButtonPress: 76 | if (ev.xbutton.button == Button1) 77 | XDrawPoint(dpy, ev.xbutton.window, gc, ev.xbutton.x, ev.xbutton.y); 78 | break; 79 | case MotionNotify: 80 | if (init) { 81 | XDrawLine(dpy, ev.xbutton.window, gc, lx, ly, ev.xbutton.x, ev.xbutton.y); 82 | } else { 83 | XDrawPoint(dpy, ev.xbutton.window, gc, ev.xbutton.x, ev.xbutton.y); 84 | init = 1; 85 | } 86 | lx = ev.xbutton.x; 87 | ly = ev.xbutton.y; 88 | break; 89 | case ButtonRelease: 90 | init = 0; 91 | break; 92 | case KeyPress: 93 | if (XkbKeycodeToKeysym(dpy, ev.xkey.keycode, 0, 0) == XK_q) 94 | return; 95 | break; 96 | } 97 | } 98 | } 99 | 100 | /* xpaint: paint a line with the mouse */ 101 | int 102 | main(void) 103 | { 104 | Window win; 105 | GC gc; 106 | 107 | /* open connection to the server */ 108 | if ((dpy = XOpenDisplay(NULL)) == NULL) 109 | errx(1, "could not open display"); 110 | screen = DefaultScreen(dpy); 111 | root = RootWindow(dpy, screen); 112 | 113 | /* setup resources */ 114 | win = createwin(POSX, POSY, WIDTH, HEIGHT, BORDER); 115 | gc = creategc(LINE); 116 | 117 | /* map the window */ 118 | XMapWindow(dpy, win); 119 | 120 | /* run main loop */ 121 | run(gc); 122 | 123 | /* unmap the window */ 124 | XUnmapWindow(dpy, win); 125 | 126 | /* close resources */ 127 | XDestroyWindow(dpy, win); 128 | XFreeGC(dpy, gc); 129 | 130 | /* close connection to the server */ 131 | XCloseDisplay(dpy); 132 | 133 | return 0; 134 | } 135 | -------------------------------------------------------------------------------- /xpaint2/Makefile: -------------------------------------------------------------------------------- 1 | PROG = xpaint 2 | SRCS = ${PROG}.c 3 | OBJS = ${SRCS:.c=.o} 4 | 5 | INCS = -I/usr/X11R6/include 6 | LIBS = -L/usr/X11R6/lib -lX11 7 | 8 | CC = cc 9 | CFLAGS = -g -O0 -Wall -Wextra ${INCS} 10 | LDFLAGS = ${LIBS} 11 | 12 | all: ${PROG} 13 | 14 | ${PROG}: ${OBJS} 15 | ${CC} -o $@ ${OBJS} ${LDFLAGS} 16 | 17 | .c.o: 18 | ${CC} ${CFLAGS} -c $< 19 | 20 | clean: 21 | -rm ${OBJS} ${PROG} 22 | 23 | test: ${PROG} 24 | ./${PROG} 25 | 26 | .PHONY: all clean test 27 | -------------------------------------------------------------------------------- /xpaint2/README: -------------------------------------------------------------------------------- 1 | XPAINT(1) X Notes XPAINT(1) 2 | 3 | NAME 4 | xpaint - paint lines with the mouse 5 | 6 | SYNOPSIS 7 | xpaint 8 | 9 | DESCRIPTION 10 | This program displays a blank window. Pressing the left mouse button 11 | and moving the pointer draws a line on the window. 12 | 13 | The objective of this program is to understand and apply the basic 14 | concepts about pixmaps. 15 | 16 | CONCEPTS 17 | This version of xpaint fixes a bug of the previous version. In the 18 | previous version, whenever another window covers the xpaint window, 19 | the drawing in the region covered is lost. 20 | 21 | That occurred because, for saving memory, the X server does not save 22 | the contents of each window. Instead, it saves the content of the 23 | screen. If a window is not visible on the screen, its content is lost. 24 | In fact, when we draw on a window we are actually drawing directly on 25 | the screen. 26 | 27 | To save what we have drawn, we need to draw into a pixmap, and then 28 | copy the contents of the pixmap to the window whenever it receives an 29 | expose event. 30 | 31 | The Expose Event 32 | To get Expose events on a window, we need to register for it using the 33 | ExposureMask bit mask. Then, we'll receive an Expose event whenever 34 | the window is exposed (that is, when it is mapped or when another 35 | window uncovers it, for example). 36 | 37 | We usually respond to this event by redrawing everything that was lost. 38 | 39 | Pixmaps 40 | A pixmap is like an image that is stored in the X server. Both pixmaps 41 | and windows are called drawables. The drawing functions actually draw 42 | on drawables, so it can draw directly on a window or on a pixmap. The 43 | difference is that the contents of a pixmap is saved on the X server, 44 | and is not lost until we destroy the pixmap. 45 | 46 | A pixmap is created with the the XCreatePixmap(3) function. This 47 | function receives a pointer to the Display structure, another drawable, 48 | the width and height of the pixmap we want to create, and a color 49 | depth. The drawable we must pass to XCreateGC(3) is used for the 50 | function to get the screen on which the pixmap will be drawn. We 51 | can pass the root window or the window into which the contents of 52 | the pixmap will be copied to. 53 | 54 | The pixmap often comes with garbage in it. So it is a good idea to 55 | fill the pixmap with a blank rectangle (or a rectangle with the color 56 | of our background). To do this, we temporarily set the foreground of 57 | our graphics context to our background color, draw a rectangle covering 58 | all the pixmap, and then revert the graphics context to its previous 59 | state. 60 | 61 | Copying pixmap to window 62 | We can copy the contents of an drawable to another. To do this, we 63 | use the XCopyArea(3) function. In our case, we copy the contents of 64 | the pixmap we are drawing in to the window that we see on the screen. 65 | 66 | The XCopyArea(3) function receives several arguments. The first 67 | argument, as always, is a pointer to the Display structure. The second 68 | and third arguments are the source and destination drawables. The 69 | fourth argument is the graphics context. The fifth and sixth arguments 70 | are the coordinate of the top-left point of the region on the source 71 | drawable that should be copied. The seventh and eighth arguments 72 | specify the size of this region. And the ninth and tenth arguments are 73 | the coordinate of the top-left point on the destination drawable that 74 | we want to copy that region to. 75 | 76 | As we can see, we can copy only a certain rectangular region of a 77 | drawable into another. That's what we do in this second version of 78 | xpaint. We only copy the exposed region. Since the pixmap has the 79 | same size of the window, the source and destination coordinates are 80 | the same. 81 | 82 | Free Pixmap 83 | After we have used our pixmap, we must destroy it on the X server with 84 | the XFreePixmap(3) function. 85 | 86 | Double-buffering 87 | It is a good practice to always draw on a pixmap rather than directly 88 | on the window. This is called double-buffering. When we draw directly 89 | on the window, we can see glitches during redrawing. Using double 90 | buffering avoids it. 91 | 92 | ENVIRONMENT 93 | The following environment variables affect the execution of xpaint. 94 | 95 | DISPLAY 96 | The display to start xnotify on. 97 | 98 | BUGS 99 | The window can change size, but we created a pixmap with the initial 100 | size of the window. That means that, when we resize the window, the 101 | pixmap maintains its size and we can only draw in the upper left part 102 | of the window with the same size as the pixmap. 103 | 104 | As an exercise, try to fix this. Search about the ConfigureNotify 105 | event, which notifies whenever a window changes its configuration (such 106 | as its size). Register for this event and, whenever you receive it, 107 | create a new pixmap with size larger enough to fit into the window and 108 | to get the contents of the older pixmap. Then copy the contents of the 109 | old pixmap into the newer one, and use the newer one as your canvas. 110 | Remember to free the older pixmap. 111 | 112 | SEE ALSO 113 | XCreatePixmap(3), XFreePixmap(3), XCopyArea(3), XConfigureEvent(3) 114 | -------------------------------------------------------------------------------- /xpaint2/xpaint.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #define POSX 200 9 | #define POSY 200 10 | #define WIDTH 350 11 | #define HEIGHT 250 12 | #define BORDER 10 13 | #define LINE 2 14 | 15 | static Display *dpy; 16 | static Window root; 17 | static int screen; 18 | static int depth; 19 | 20 | /* setup top-level window */ 21 | static Window 22 | createwin(int x, int y, int w, int h, int b) 23 | { 24 | XSetWindowAttributes swa; 25 | Window win; 26 | 27 | /* create top-level window */ 28 | swa.background_pixel = WhitePixel(dpy, screen); 29 | swa.border_pixel = BlackPixel(dpy, screen); 30 | swa.event_mask = ExposureMask | ButtonPressMask | Button1MotionMask | ButtonReleaseMask | KeyPressMask; 31 | win = XCreateWindow(dpy, root, x, y, w, h, b, 32 | CopyFromParent, CopyFromParent, CopyFromParent, 33 | CWBackPixel | CWBorderPixel | CWEventMask, 34 | &swa); 35 | 36 | return win; 37 | } 38 | 39 | /* create a graphics context */ 40 | static GC 41 | creategc(int linewidth) 42 | { 43 | GC gc; 44 | XGCValues values; 45 | unsigned long valuemask; 46 | 47 | /* setup the values for our graphics context */ 48 | values.foreground = BlackPixel(dpy, screen); 49 | values.background = WhitePixel(dpy, screen); 50 | values.cap_style = CapButt; /* line's end-point is straight */ 51 | values.join_style = JoinBevel; /* join lines in "bevelian" style */ 52 | values.fill_style = FillSolid; 53 | values.line_style = LineSolid; 54 | values.line_width = linewidth; /* line width in pixels */ 55 | 56 | /* set valuemask to the values we have set */ 57 | valuemask = GCForeground | GCBackground | GCCapStyle | GCJoinStyle | 58 | GCFillStyle | GCLineStyle | GCLineWidth; 59 | 60 | /* create our graphics context */ 61 | gc = XCreateGC(dpy, root, valuemask, &values); 62 | 63 | return gc; 64 | } 65 | 66 | static Pixmap 67 | createpix(GC gc, int w, int h) 68 | { 69 | Pixmap pix; 70 | 71 | pix = XCreatePixmap(dpy, root, w, h, depth); 72 | 73 | /* 74 | * we change gc foreground for we to clean the pixmap, 75 | * and then we revert it to its previous value 76 | */ 77 | XSetForeground(dpy, gc, WhitePixel(dpy, screen)); 78 | XFillRectangle(dpy, pix, gc, 0, 0, w, h); 79 | XSetForeground(dpy, gc, BlackPixel(dpy, screen)); 80 | 81 | return pix; 82 | } 83 | 84 | /* enter the event loop */ 85 | static void 86 | run(GC gc, Pixmap pix) 87 | { 88 | XEvent ev; 89 | int init = 0; /* whether we are drawing a line */ 90 | int lx, ly; /* last x and y positions */ 91 | 92 | while(XNextEvent(dpy, &ev) == 0) { 93 | switch(ev.type) { 94 | case Expose: 95 | XCopyArea(dpy, pix, ev.xexpose.window, gc, 96 | ev.xexpose.x, ev.xexpose.y, 97 | ev.xexpose.width, ev.xexpose.height, 98 | ev.xexpose.x, ev.xexpose.y); 99 | break; 100 | case ButtonPress: 101 | XDrawPoint(dpy, pix, gc, ev.xbutton.x, ev.xbutton.y); 102 | XDrawPoint(dpy, pix, gc, ev.xbutton.x, ev.xbutton.y); 103 | XCopyArea(dpy, pix, ev.xbutton.window, gc, 0, 0, WIDTH, HEIGHT, 0, 0); 104 | break; 105 | case MotionNotify: 106 | if (init) { 107 | XDrawLine(dpy, pix, gc, lx, ly, ev.xbutton.x, ev.xbutton.y); 108 | } else { 109 | XDrawPoint(dpy, pix, gc, ev.xbutton.x, ev.xbutton.y); 110 | init = 1; 111 | } 112 | XCopyArea(dpy, pix, ev.xbutton.window, gc, 0, 0, WIDTH, HEIGHT, 0, 0); 113 | lx = ev.xbutton.x; 114 | ly = ev.xbutton.y; 115 | break; 116 | case ButtonRelease: 117 | init = 0; 118 | break; 119 | case KeyPress: 120 | if (XkbKeycodeToKeysym(dpy, ev.xkey.keycode, 0, 0) == XK_q) 121 | return; 122 | break; 123 | } 124 | } 125 | } 126 | 127 | /* xpaint: paint a line with the mouse */ 128 | int 129 | main(void) 130 | { 131 | Pixmap pix; 132 | Window win; 133 | GC gc; 134 | 135 | /* open connection to the server */ 136 | if ((dpy = XOpenDisplay(NULL)) == NULL) 137 | errx(1, "could not open display"); 138 | screen = DefaultScreen(dpy); 139 | depth = DefaultDepth(dpy, screen); 140 | root = RootWindow(dpy, screen); 141 | 142 | /* setup resources */ 143 | win = createwin(POSX, POSY, WIDTH, HEIGHT, BORDER); 144 | gc = creategc(LINE); 145 | pix = createpix(gc, WIDTH, HEIGHT); 146 | 147 | /* map the window */ 148 | XMapWindow(dpy, win); 149 | 150 | /* run main loop */ 151 | run(gc, pix); 152 | 153 | /* unmap the window */ 154 | XUnmapWindow(dpy, win); 155 | 156 | /* close resources */ 157 | XDestroyWindow(dpy, win); 158 | XFreePixmap(dpy, pix); 159 | XFreeGC(dpy, gc); 160 | 161 | /* close connection to the server */ 162 | XCloseDisplay(dpy); 163 | 164 | return 0; 165 | } 166 | --------------------------------------------------------------------------------