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 ;
}
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-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 ) ;
info - > context - > cairo = cairo_create ( info - > surface ) ;
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-17 13:41:17 +00:00
return info ;
2018-08-08 14:24:03 +00:00
}
void destroyCairo ( GraphicsInfo * info )
{
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 ;
switch ( ev - > type ) {
case Expose :
2018-08-17 13:41:17 +00:00
draw ( info - > context ) ;
2018-08-08 14:24:03 +00:00
break ;
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 ;
cairo_xlib_surface_set_size ( info - > surface , ev - > xconfigure . width , ev - > xconfigure . height ) ;
draw ( info - > context ) ;
2018-08-08 14:24:03 +00:00
break ;
case KeyPress :
state = ev - > xkey . state & ( ShiftMask | LockMask | ControlMask ) ;
if ( state = = 0 & & ev - > xkey . keycode = = 24 )
return true ;
break ;
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 ) ;
draw ( info - > context ) ;
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 ) ;
draw ( info - > context ) ;
2018-08-08 14:24:03 +00:00
}
last_x = ev - > xbutton . x ;
last_y = ev - > xbutton . y ;
break ;
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 ) ;
draw ( info - > context ) ;
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 ) ;
draw ( info - > context ) ;
2018-08-08 14:24:03 +00:00
}
last_x = ev - > xmotion . x ;
last_y = ev - > xmotion . y ;
break ;
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");
return true ;
}
break ;
}
return 0 ;
}
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 ;
// while(XCheckIfEvent(info->display, &ev, alwaysTruePredicate, NULL)) { // we essentially want XCheckWindowEvent, but we want to avoid that events for other windows fill up the queue
XNextEvent ( info - > display , & ev ) ;
if ( ev . xany . window ! = info - > win )
return 0 ;
/* if(ev.type == KeyRelease) { // deal with autorepeat
XEvent nev ;
if ( XCheckIfEvent ( info - > display , & nev , alwaysTruePredicate , NULL ) ) { // is there another event?
if ( nev . type = = KeyPress & & nev . xkey . time = = ev . xkey . time & & nev . xkey . keycode = = ev . xkey . keycode ) // which is equal, but KeyPress? Then it's just auto-repeat
return 0 ; // so we ignore both
XPutBackEvent ( info - > display , & nev ) ; // otherwise put the event back, we will consider it in the next round
}
} */
2018-08-17 13:41:17 +00:00
if ( processStandardEvent ( info , & ev , draw ) )
2018-08-08 14:24:03 +00:00
return 1 ;
2018-08-17 13:41:17 +00:00
if ( process ( info , & ev ) )
2018-08-08 14:24:03 +00:00
return 1 ; // quit event queue and application
return 0 ;
}