2018-08-08 14:24:03 +00:00
# include <sys/stat.h>
# include <sys/time.h>
# include <math.h>
# include <cairo-xlib.h>
# include "initcairo.h"
# define ERROR(condition, msg, ...) if(condition){fprintf(stderr, msg, ##__VA_ARGS__); return 0;}
static Bool alwaysTruePredicate ( Display * display , XEvent * event , XPointer arg )
{
return true ;
}
2019-01-18 14:26:04 +00:00
// this computes center, radius and scalefactor out of ctx->matrix
2018-08-17 13:41:17 +00:00
void updateDimensions ( DrawingContext * ctx )
2018-08-08 14:24:03 +00:00
{
2018-08-17 13:41:17 +00:00
double det = ctx - > matrix . xx * ctx - > matrix . yy - ctx - > matrix . xy * ctx - > matrix . yx ;
double cx = ( double ) ctx - > width / 2 ;
double cy = ( double ) ctx - > height / 2 ;
double r = 2 * sqrt ( ( cx * cx + cy * cy ) / det ) ; // this is not safe anymore if we have non-uniform scaling
// don't use cairo_device_to_user() since the matrix might not be our CTM yet
cx - = ctx - > matrix . x0 ;
cy - = ctx - > matrix . y0 ;
ctx - > center_x = ( ctx - > matrix . yy * cx - ctx - > matrix . xy * cy ) / det ;
ctx - > center_y = ( - ctx - > matrix . yx * cx + ctx - > matrix . xx * cy ) / det ;
ctx - > radius = r ;
ctx - > scalefactor = sqrt ( det ) ;
}
GraphicsInfo * initCairo ( int screen , int mask , int width , int height , const char * name )
{
GraphicsInfo * info = malloc ( sizeof ( GraphicsInfo ) ) ;
DrawingContext * ctx = malloc ( sizeof ( DrawingContext ) ) ;
info - > context = ctx ;
2018-08-08 14:24:03 +00:00
mask | = StructureNotifyMask | ExposureMask | KeyPressMask | ButtonPressMask | PointerMotionMask ;
info - > display = XOpenDisplay ( NULL ) ;
ERROR ( ! info - > display , " Failed to open X display \n " ) ;
Visual * visual = XDefaultVisual ( info - > display , screen ) ;
int depth = XDefaultDepth ( info - > display , screen ) ;
Window root = XRootWindow ( info - > display , screen ) ;
XSetWindowAttributes swa ;
info - > cmap = XCreateColormap ( info - > display , root , visual , AllocNone ) ;
swa . colormap = info - > cmap ;
swa . background_pixmap = None ;
swa . border_pixel = 0 ;
swa . event_mask = mask ;
info - > win = XCreateWindow ( info - > display , root , 0 , 0 , width , height , 0 , depth , InputOutput , visual , CWBorderPixel | CWColormap | CWEventMask , & swa ) ;
ERROR ( ! info - > win , " Failed to create window. \n " ) ;
XStoreName ( info - > display , info - > win , name ) ;
XMapWindow ( info - > display , info - > win ) ;
2018-08-18 16:27:33 +00:00
// create xlib surface
2018-08-17 13:41:17 +00:00
info - > surface = cairo_xlib_surface_create ( info - > display , info - > win , visual , width , height ) ;
cairo_xlib_surface_set_size ( info - > surface , width , height ) ;
2018-08-18 16:27:33 +00:00
info - > front_context = cairo_create ( info - > surface ) ;
cairo_set_antialias ( info - > front_context , CAIRO_ANTIALIAS_NONE ) ;
// create backbuffer surface
int stride = cairo_format_stride_for_width ( CAIRO_FORMAT_ARGB32 , width ) ;
info - > buffer = malloc ( stride * height ) ;
info - > buffer_surface = cairo_image_surface_create_for_data ( info - > buffer , CAIRO_FORMAT_ARGB32 , width , height , stride ) ;
info - > context - > cairo = cairo_create ( info - > buffer_surface ) ;
2018-08-17 13:41:17 +00:00
cairo_matrix_init_identity ( & info - > context - > matrix ) ;
info - > context - > width = width ;
info - > context - > height = height ;
2018-08-08 14:24:03 +00:00
info - > wm_delete_window = XInternAtom ( info - > display , " WM_DELETE_WINDOW " , 0 ) ;
info - > wm_protocols = XInternAtom ( info - > display , " WM_PROTOCOLS " , 0 ) ;
XSetWMProtocols ( info - > display , info - > win , & info - > wm_delete_window , 1 ) ;
2018-08-18 16:27:33 +00:00
XFlush ( info - > display ) ;
2018-08-17 13:41:17 +00:00
return info ;
2018-08-08 14:24:03 +00:00
}
void destroyCairo ( GraphicsInfo * info )
{
2018-08-18 16:27:33 +00:00
cairo_destroy ( info - > context - > cairo ) ;
cairo_destroy ( info - > front_context ) ;
cairo_surface_destroy ( info - > surface ) ;
cairo_surface_destroy ( info - > buffer_surface ) ;
free ( info - > buffer ) ;
2018-08-08 14:24:03 +00:00
XDestroyWindow ( info - > display , info - > win ) ;
XFreeColormap ( info - > display , info - > cmap ) ;
XCloseDisplay ( info - > display ) ;
2018-08-17 13:41:17 +00:00
free ( info - > context ) ;
free ( info ) ;
2018-08-08 14:24:03 +00:00
}
void startTimer ( GraphicsInfo * info )
{
gettimeofday ( & info - > start_time , NULL ) ;
info - > frames = 0 ;
}
void waitUpdateTimer ( GraphicsInfo * info )
{
struct timeval current_time ;
double new_elapsed ;
gettimeofday ( & current_time , NULL ) ;
new_elapsed = current_time . tv_sec - info - > start_time . tv_sec ;
new_elapsed + = ( current_time . tv_usec - info - > start_time . tv_usec ) * 1e-6 ;
info - > frametime = new_elapsed - info - > elapsed ;
if ( info - > frametime < 0.01 ) { // frames < 10ms are considered too short; sleep a while and then measure again
usleep ( 10000 - info - > frametime * 1e6 ) ;
gettimeofday ( & current_time , NULL ) ;
new_elapsed = current_time . tv_sec - info - > start_time . tv_sec ;
new_elapsed + = ( current_time . tv_usec - info - > start_time . tv_usec ) * 1e-6 ;
info - > frametime = new_elapsed - info - > elapsed ;
}
info - > elapsed = new_elapsed ;
info - > frames + + ;
}
2018-08-17 13:41:17 +00:00
int processStandardEvent ( GraphicsInfo * info , XEvent * ev , void ( * draw ) ( DrawingContext * ) )
2018-08-08 14:24:03 +00:00
{
int state ;
static int last_x , last_y ;
2018-08-18 16:27:33 +00:00
int stride ;
int status = STATUS_NOTHING ;
2018-08-08 14:24:03 +00:00
switch ( ev - > type ) {
case Expose :
2018-08-18 16:27:33 +00:00
return STATUS_REDRAW ;
2018-08-08 14:24:03 +00:00
case ConfigureNotify :
printf ( " ConfigureNotify Event, new dimensions: %d %d %d %d \n " , ev - > xconfigure . x , ev - > xconfigure . y , ev - > xconfigure . width , ev - > xconfigure . height ) ;
2018-08-17 13:41:17 +00:00
info - > context - > width = ev - > xconfigure . width ;
info - > context - > height = ev - > xconfigure . height ;
2018-08-18 16:27:33 +00:00
cairo_xlib_surface_set_size ( info - > surface , info - > context - > width , info - > context - > height ) ;
cairo_destroy ( info - > context - > cairo ) ;
cairo_surface_destroy ( info - > buffer_surface ) ;
free ( info - > buffer ) ;
stride = cairo_format_stride_for_width ( CAIRO_FORMAT_ARGB32 , info - > context - > width ) ;
info - > buffer = malloc ( stride * info - > context - > height ) ;
info - > buffer_surface = cairo_image_surface_create_for_data ( info - > buffer , CAIRO_FORMAT_ARGB32 , info - > context - > width , info - > context - > height , stride ) ;
info - > context - > cairo = cairo_create ( info - > buffer_surface ) ;
return STATUS_REDRAW ;
2018-08-08 14:24:03 +00:00
case KeyPress :
state = ev - > xkey . state & ( ShiftMask | LockMask | ControlMask ) ;
if ( state = = 0 & & ev - > xkey . keycode = = 24 )
2018-08-18 16:27:33 +00:00
return STATUS_QUIT ;
return STATUS_NOTHING ;
2018-08-08 14:24:03 +00:00
case ButtonPress :
if ( ev - > xbutton . button = = 4 ) {
cairo_matrix_t transform ;
cairo_matrix_init_identity ( & transform ) ;
cairo_matrix_translate ( & transform , ev - > xbutton . x , ev - > xbutton . y ) ;
cairo_matrix_scale ( & transform , 5.0 / 4.0 , 5.0 / 4.0 ) ;
cairo_matrix_translate ( & transform , - ev - > xbutton . x , - ev - > xbutton . y ) ;
2018-08-17 13:41:17 +00:00
cairo_matrix_multiply ( & info - > context - > matrix , & info - > context - > matrix , & transform ) ;
2018-08-18 16:27:33 +00:00
status = STATUS_REDRAW ;
2018-08-08 14:24:03 +00:00
} else if ( ev - > xbutton . button = = 5 ) {
cairo_matrix_t transform ;
cairo_matrix_init_identity ( & transform ) ;
cairo_matrix_translate ( & transform , ev - > xbutton . x , ev - > xbutton . y ) ;
cairo_matrix_scale ( & transform , 4.0 / 5.0 , 4.0 / 5.0 ) ;
cairo_matrix_translate ( & transform , - ev - > xbutton . x , - ev - > xbutton . y ) ;
2018-08-17 13:41:17 +00:00
cairo_matrix_multiply ( & info - > context - > matrix , & info - > context - > matrix , & transform ) ;
2018-08-18 16:27:33 +00:00
status = STATUS_REDRAW ;
2018-08-08 14:24:03 +00:00
}
2018-08-18 16:27:33 +00:00
2018-08-08 14:24:03 +00:00
last_x = ev - > xbutton . x ;
last_y = ev - > xbutton . y ;
2018-08-18 16:27:33 +00:00
return status ;
2018-08-08 14:24:03 +00:00
case MotionNotify :
if ( ev - > xmotion . state & Button1Mask & & ! ( ev - > xmotion . state & ShiftMask ) ) {
int dx = ev - > xmotion . x - last_x ;
int dy = ev - > xmotion . y - last_y ;
cairo_matrix_t transform ;
cairo_matrix_init_identity ( & transform ) ;
cairo_matrix_translate ( & transform , dx , dy ) ;
2018-08-17 13:41:17 +00:00
cairo_matrix_multiply ( & info - > context - > matrix , & info - > context - > matrix , & transform ) ;
2018-08-18 16:27:33 +00:00
status = STATUS_REDRAW ;
2018-08-08 14:24:03 +00:00
} else if ( ev - > xmotion . state & Button1Mask & & ev - > xmotion . state & ShiftMask ) {
2018-08-17 13:41:17 +00:00
double width = ( double ) cairo_xlib_surface_get_width ( info - > surface ) ;
double height = ( double ) cairo_xlib_surface_get_height ( info - > surface ) ;
2018-08-08 14:24:03 +00:00
double angle =
2018-08-17 13:41:17 +00:00
atan2 ( ( double ) ev - > xmotion . y - height / 2 , ( double ) ev - > xmotion . x - width / 2 ) -
atan2 ( ( double ) last_y - height / 2 , ( double ) last_x - width / 2 ) ;
2018-08-08 14:24:03 +00:00
cairo_matrix_t transform ;
cairo_matrix_init_identity ( & transform ) ;
2018-08-17 13:41:17 +00:00
cairo_matrix_translate ( & transform , width / 2 , height / 2 ) ;
2018-08-08 14:24:03 +00:00
cairo_matrix_rotate ( & transform , angle ) ;
2018-08-17 13:41:17 +00:00
cairo_matrix_translate ( & transform , - width / 2 , - height / 2 ) ;
cairo_matrix_multiply ( & info - > context - > matrix , & info - > context - > matrix , & transform ) ;
2018-08-18 16:27:33 +00:00
status = STATUS_REDRAW ;
2018-08-08 14:24:03 +00:00
}
2018-08-18 16:27:33 +00:00
2018-08-08 14:24:03 +00:00
last_x = ev - > xmotion . x ;
last_y = ev - > xmotion . y ;
2018-08-18 16:27:33 +00:00
return status ;
2018-08-08 14:24:03 +00:00
case ClientMessage :
if ( ( Atom ) ev - > xclient . message_type = = info - > wm_protocols & & ( Atom ) ev - > xclient . data . l [ 0 ] = = info - > wm_delete_window ) {
// printf("Window closed\n");
2018-08-18 16:27:33 +00:00
return STATUS_QUIT ;
2018-08-08 14:24:03 +00:00
}
2018-08-18 16:27:33 +00:00
return STATUS_NOTHING ;
2018-08-08 14:24:03 +00:00
}
2018-08-18 16:27:33 +00:00
return STATUS_NOTHING ;
2018-08-08 14:24:03 +00:00
}
2018-08-17 13:41:17 +00:00
int checkEvents ( GraphicsInfo * info , int ( * process ) ( GraphicsInfo * , XEvent * ) , void ( * draw ) ( DrawingContext * ) ) // get any events from the queue and the server, process them if neccessary, quit if wanted
2018-08-08 14:24:03 +00:00
{
XEvent ev ;
2018-08-18 16:27:33 +00:00
int x11_fd ;
fd_set fds ;
int status = STATUS_NOTHING , result ;
2018-08-08 14:24:03 +00:00
2018-08-18 16:27:33 +00:00
int x11_event = 0 ;
2018-08-08 14:24:03 +00:00
2018-08-18 16:27:33 +00:00
if ( XPending ( info - > display ) ) { // select() would miss events which are picked up already, so we have to catch them first
x11_event = 1 ;
} else {
x11_fd = ConnectionNumber ( info - > display ) ;
FD_ZERO ( & fds ) ;
FD_SET ( x11_fd , & fds ) ;
2018-08-08 14:24:03 +00:00
2018-08-18 16:27:33 +00:00
select ( x11_fd + 1 , & fds , NULL , NULL , NULL ) ;
2018-08-08 14:24:03 +00:00
2018-08-18 16:27:33 +00:00
if ( FD_ISSET ( x11_fd , & fds ) )
x11_event = 1 ;
}
2018-08-08 14:24:03 +00:00
2018-08-18 16:27:33 +00:00
if ( x11_event ) {
while ( XPending ( info - > display ) ) {
XNextEvent ( info - > display , & ev ) ;
2018-08-08 14:24:03 +00:00
2018-08-18 16:27:33 +00:00
if ( ev . xany . window ! = info - > win )
continue ;
2018-08-08 14:24:03 +00:00
2018-08-18 16:27:33 +00:00
result = processStandardEvent ( info , & ev , draw ) ;
if ( result = = STATUS_QUIT )
return STATUS_QUIT ;
else if ( result = = STATUS_REDRAW )
status = STATUS_REDRAW ;
result = process ( info , & ev ) ;
if ( result = = STATUS_QUIT )
return STATUS_QUIT ;
else if ( result = = STATUS_REDRAW )
status = STATUS_REDRAW ;
}
}
2018-08-08 14:24:03 +00:00
2018-08-18 16:27:33 +00:00
return status ;
2018-08-08 14:24:03 +00:00
}