I'm wondering how to draw directly on the root window in an X11 environment with Cairo (in C), in order to make widgets. I've copied some parts of the code of tint2, but it's quite enormous, and the only result I have is not satisfying.
I would be pleased to have a complete working sample code, or at least some tips or little programs to study.
Thank you guys !
The "bottom" window is the root window. The problem is that in some desktop environments we have windows on top of the root window, so if you change the root window, you won't see your changes: you need to change the window that's on the top.
This program does what you ask for: draw on the root window. To test it, I suggest you to:
ctrl+alt+f1
login as root
stop your desktop environment ("/etc/init.d/gdm stop", "/etc/init.d/kdm stop" or whatever is needed in your distro)
X -noreset -retro &
DISPLAY=:0.0 xterm &
DISPLAY=:0.0 metacity &
Then, go back to X (ctrl+alt+f7 or maybe f8) and run the program.
If you want to draw on Nautilus' top window, you will need to find out its window ID and then use it as the "w" variable. The "xwininfo" command might help you testing...
#include <assert.h>
#include <stdio.h>
#include <X11/Xlib.h>
#include <cairo.h>
#include <cairo-xlib.h>
int width, height;
void draw(cairo_t *cr) {
int quarter_w = width / 4;
int quarter_h = height / 4;
cairo_set_source_rgb(cr, 1.0, 0.0, 0.0);
cairo_rectangle(cr, quarter_w, quarter_h, quarter_w * 2, quarter_h * 2);
cairo_fill(cr);
}
int main() {
Display *d = XOpenDisplay(NULL);
assert(d);
int s = DefaultScreen(d);
Window w = RootWindow(d, s);
width = DisplayWidth(d, s);
height = DisplayHeight(d, s);
cairo_surface_t *surf = cairo_xlib_surface_create(d, w,
DefaultVisual(d, s),
width, height);
cairo_t *cr = cairo_create(surf);
XSelectInput(d, w, ExposureMask);
draw(cr);
XEvent ev;
while (1) {
XNextEvent(d, &ev);
printf("Event!\n");
if (ev.type == Expose) {
draw(cr);
}
}
cairo_destroy(cr);
cairo_surface_destroy(surf);
XCloseDisplay(d);
return 0;
}
Related
I need, in C in under a linux environment, to print documents with an image as a background.
I found an example which use Cairo vector graphics library to author a PostScript, and then sends it that off to CUPS for printing.
I modified the initial source code to integrate it with a background image (background-img.png) .
The text to be printed is more than one page, I would like to know how can I print on multiple pages keeping the same image as background and changing only the foreground text ?
How can I resize the background image to match the size of an A4 page ?
Here is the code used as a starting point :
// compile with:
// gcc -Wall -o cairo_print_png cairo_print_png.c `pkg-config --cflags --libs cairo` `cups-config --cflags --libs`
#include <stdio.h>
#include <cairo.h>
#include <cairo-ps.h>
#include <cups/cups.h>
// A4 width, height in points, from GhostView manual:
// http://www.gnu.org/software/gv/manual/html_node/Paper-Keywords-and-paper-size-in-points.html
#define WIDTH 595
#define HEIGHT 842
int main(int argc, char** argv) {
int widthPng, heightPng;
if (argc!= 2){
fprintf (stderr, "usage: %s word\n", argv[0]);
return 1;
}
// setup
char* tmpfilename = tempnam(NULL,NULL);
cairo_surface_t* surface = cairo_ps_surface_create(tmpfilename,
WIDTH,
HEIGHT);
cairo_t *context = cairo_create(surface);
// draw some text
cairo_select_font_face(context,
"mono",
CAIRO_FONT_SLANT_NORMAL,
CAIRO_FONT_WEIGHT_NORMAL);
cairo_set_font_size(context, 30);
cairo_move_to(context, WIDTH/2, HEIGHT/2);
cairo_show_text(context, argv[1]); // the text we got as a parameter
// draw a dotted box
const double pattern[] = {15.0, 10.0};
cairo_set_dash(context, pattern, 2, 0);
cairo_set_line_width(context, 5);
cairo_rectangle(context, WIDTH*0.33, HEIGHT*0.33, WIDTH*0.5, WIDTH*0.5);
cairo_stroke(context);
cairo_surface_t* surface_png = cairo_image_surface_create_from_png("background-img.png");
if (surface_png == NULL || cairo_surface_status (surface_png)) {
printf("***** load error *****\n");
}
widthPng = cairo_image_surface_get_width(surface_png);
heightPng = cairo_image_surface_get_height(surface_png);
cairo_surface_set_device_scale (surface_png,
widthPng/WIDTH*1.33,
heightPng/HEIGHT*1.29);
cairo_set_operator(context, CAIRO_OPERATOR_DEST_OVER);
cairo_set_source_surface(context, surface_png, 0, 0);
cairo_paint(context);
/*** second page ***/
{
cairo_t *cr;
cr = cairo_create (surface);
/* Duplicate the last frame onto another page. (This is just a
* way to sneak cairo_copy_page into the test).
*/
cairo_show_page (cr);
//draw text on second page
cairo_select_font_face(cr,
"mono",
CAIRO_FONT_SLANT_NORMAL,
CAIRO_FONT_WEIGHT_NORMAL);
cairo_set_font_size(cr, 30);
cairo_move_to(cr, WIDTH/2, HEIGHT/2);
cairo_show_text(cr, "text over second page");
cairo_set_operator(cr, CAIRO_OPERATOR_DEST_OVER);
cairo_set_source_surface(cr, surface_png, 0, 0);
cairo_paint(cr);
cairo_destroy (cr);
}
// finish up
cairo_show_page(context);
cairo_destroy(context);
cairo_surface_flush(surface);
cairo_surface_destroy(surface);
cairo_surface_flush(surface_png);
cairo_surface_destroy(surface_png);
// print
cupsPrintFile("Cups-PDF", tmpfilename, "cairo PS", 0, NULL);
unlink(tmpfilename);
return 0;
}
Update :
I added the text enclosed in "second page". I can create another page, but I can't resize the background image.
How can I resize the image to fit the A4 size?
Update
I used cairo_surface_set_device_scale to resize the image, and the mono font to use a fixed width font
Update
The problem now is that the code works with cairo-1.15.12-4 while if I try to compile with cairo-1.8.8-3.1 I get undefined reference to cairo_surface_set_device_scale'.
What can I solve the problem? At the moment I cannot update the libraries using rpm as I would have to update the whole operating system.
Can I somehow replace the cairo_surface_set_device_scale with functions that perform the same task?
Update:
In the Cairo Mailing Lists I found the following function that does the job :
cairo_surface_t *scale_to_half(cairo_surface_t *s, int orig_width, int
orig_height, double x_scale, double y_scale)
{
cairo_surface_t *result = cairo_surface_create_similar(s,
cairo_surface_get_content(s), orig_width*x_scale, orig_height*y_scale);
cairo_t *cr = cairo_create(result);
cairo_scale(cr, x_scale, y_scale);
cairo_set_source_surface(cr, s, 0, 0);
cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE);
cairo_paint(cr);
cairo_destroy(cr);
return result;
}
but the quality is very low compared to cairo_surface_set_device_scale.
I just want to make a sample app when the only possible action is close.
Is this possible with Unity(Ubuntu) ?
Do i make a mistake ?
#include <stdio.h>
#include <X11/Xlib.h>
#include <X11/Xos.h>
#include <X11/Xatom.h>
static void only_close(Display *display, Window window)
{
int retval;
Atom aa = XInternAtom(display, "_NET_WM_ALLOWED_ACTIONS", False);
Atom close = XInternAtom(display, " _NET_WM_ACTION_CLOSE", False);
retval = XChangeProperty(display, window, aa, XA_ATOM, 32, PropModeReplace, (unsigned char*)close, 1);
printf("###### XChangeProperty() reted %d\n", retval);
}
int main()
{
Display *dis;
Window win;
dis = XOpenDisplay(NULL);
win = XCreateSimpleWindow(dis, RootWindow(dis, 0), 1, 1, 500, 500, \
0, BlackPixel (dis, 0), BlackPixel(dis, 0));
XMapWindow(dis, win);
printf("window %i\n", (int)win);
only_close(dis, win);
XFlush(dis);
sleep(10);
return(0);
}
First, you need (unsigned char*)&close (an address of the data)
Second, you are setting the property too early, before WM has a chance to manage the window. A WM must discard the old value of this property when it first manages the window. Try after the first expose event, or just after a delay of 1 second.
Third, it is not guaranteed to work. On my machine (not Unity) the window actions in the taskbar are indeed disabled, but the window frame still has them the WM still allows them. I don't know if it's a bug in my WM or not.
I want to create 2 windows in linux that I'll later draw in from a separate thread. I currently have a non-deterministic bug where the second window that I create sometimes doesn't get created (no errors though).
Here is the code.
static void create_x_window(Display *display, Window *win, int width, int height)
{
int screen_num = DefaultScreen(display);
unsigned long background = WhitePixel(display, screen_num);
unsigned long border = BlackPixel(display, screen_num);
*win = XCreateSimpleWindow(display, DefaultRootWindow(display), /* display, parent */
0,0, /* x, y */
width, height, /* width, height */
2, border, /* border width & colour */
background); /* background colour */
XSelectInput(display, *win, ButtonPressMask|StructureNotifyMask);
XMapWindow(display, *win);
}
int main(void) {
XInitThreads(); // prevent threaded XIO errors
local_display = XOpenDisplay(":0.0");
Window self_win, remote_win;
XEvent self_event, remote_event;
create_x_window(local_display, &remote_win, 640,480);
// this line flushes buffer and blocks so that the window doesn't crash for a reason i dont know yet
XNextEvent(local_display, &remote_event);
create_x_window(local_display, &self_win, 320, 240);
// this line flushes buffer and blocks so that the window doesn't crash for a reason i dont know yet
XNextEvent(local_display, &self_event);
while (1) {
}
return 0;
}
I don't really care for capturing input in the windows, but I found a tutorial that had XSelectInput and XNextEvent (in an event loop) and I was having trouble making this work without either.
It's not a bug, it's a feature. You left out the event loop.
Although you cleverly called XNextEvent twice, the X protocol is asynchronous so the server may still be setting up the actual window while you call XNextEvent, so there is nothing to do.
Tutorial here.
I have simple xlib program which creates window. I think it has to show window on the upper-left corner of the screen because I pass 0, 0 to XCreateSimpleWindow function, but it's in upper-middle side. Why ?
#include <X11/Xlib.h>
#include <stdio.h>
#include <stdlib.h>
int
main(int argc, char* argv[])
{
Display* display;
int screen_num;
Window win;
unsigned int display_width,display_height;
unsigned int width, height;
char *display_name = getenv("DISPLAY");
display = XOpenDisplay(display_name);
screen_num = DefaultScreen(display);
display_width = DisplayWidth(display, screen_num);
display_height = DisplayHeight(display, screen_num);
width = (display_width / 3);
height = (display_height / 3);
win = XCreateSimpleWindow(display, RootWindow(display, screen_num), 0, 0, width, height, 1, BlackPixel(display, screen_num), WhitePixel(display, screen_num));
XMapWindow(display, win);
XSync(display, False);
while(1) { }
}
The top level windows are placed (and dimensioned) by the window manager which does whatever it suit it. Often the size is respected but the position not (in order to leave place for decoration, in order to respect placement policy of leaving toolbars clear, ...)
Try on a display without a window manager if you want your request to be respected (use VNC or similar to get such a display, don't try to use your desktop like this)
how can I set the mouse cursor position in an X window using a C program under Linux?
thanks :)
(like setcursorpos() in WIN)
EDIT:
I've tried this code, but doesn't work:
#include <curses.h>
main(){
move(100, 100);
refresh();
}
12.4 - Moving the Pointer
Although movement of the pointer
normally should be left to the control
of the end user, sometimes it is
necessary to move the pointer to a new
position under program control.
To move the pointer to an arbitrary
point in a window, use XWarpPointer().
Example:
Display *dpy;
Window root_window;
dpy = XOpenDisplay(0);
root_window = XRootWindow(dpy, 0);
XSelectInput(dpy, root_window, KeyReleaseMask);
XWarpPointer(dpy, None, root_window, 0, 0, 0, 0, 100, 100);
XFlush(dpy); // Flushes the output buffer, therefore updates the cursor's position. Thanks to Achernar.
This is old, but in case someone else comes across this issue. The answer provided by tusbar was correct but the command XFlush(dpy) must be added at the end to update the cursor's position. The libraries needed are: X11/X.h, X11/Xlib.h, X11/Xutil.h.
int main(int argc, char *argv[]){
//Get system window
Display *dpy;
Window root_window;
dpy = XOpenDisplay(0);
root_window = XRootWindow(dpy, 0);
XSelectInput(dpy, root_window, KeyReleaseMask);
XWarpPointer(dpy, None, root_window, 0, 0, 0, 0, 100, 100);
XFlush(dpy);
return 0;}
PS: You can use this command to build the code gcc main.c -lX11
You want to write a X11 program that uses the call XWarpPointer function to move the point to a relative or global position. (Xlib Programming Manual, Vol 1)
In general, using Xlib for programming the X Window System, is the most basic, and quite low-level interface for graphical programming on a Unix or Linux system. Most applications developed nowadays using a higher level library such as GTK or Qt for developing their GUI applications.
Curses or NCurses (New Curses) is for programming terminal-oriented interfaces, so are not useful in this case.
use Jordan Sissel's excellent utility xdotool.
http://www.semicomplete.com/projects/xdotool/
it provide XWarpPointer wrapper function like xdo_mousemove(), here is some example:
Display *display = NULL;
xdo_t *xdo = NULL;
void mouse_left_down(int x, int y)
{
xdo_mousemove(xdo, x, y, 0)
xdo_mousedown(xdo, CURRENTWINDOW, Button1);
}
void mouse_left_up(int x, int y)
{
xdo_mouseup(xdo, CURRENTWINDOW, Button1, 1, 0);
}
void mouse_left_double_click(int x, int y)
{
xdo_mousemove(xdo, x, y, 0);
xdo_click_multiple(xdo, CURRENTWINDOW, Button1, 1, 0);
doubleclick = TRUE;
}
int main()
{
display = XOpenDisplay(NULL);
if(display == NULL)
{
fprintf(stderr, "can't open display!\n");
return -1;
}
xdo = xdo_new((char*) display);
//some task here
// ...
return 0;
}
You can use XWarpPointer to move the mouse cursor in an X window.
XWarpPointer(display, src_w, dest_w, src_x, src_y, src_width, src_height, dest_x,
dest_y)
Display *display;
Window src_w, dest_w;
int src_x, src_y;
unsigned int src_width, src_height;
int dest_x, dest_y;
All modern terminals should support ANSI escape sequences. For anything more complicated (and more portable), however, you should look into using a library such as ncurses.