1 | /************************************************************************** |
---|
2 | sagepdf - a PDF viewer for SAGE tiled displays. |
---|
3 | Copyright (C) 2011 Christoph Willing |
---|
4 | |
---|
5 | This program is free software; you can redistribute it and/or modify |
---|
6 | it under the terms of the GNU General Public License as published by |
---|
7 | the Free Software Foundation; either version 2 of the License, or |
---|
8 | (at your option) any later version. |
---|
9 | |
---|
10 | This program is distributed in the hope that it will be useful, |
---|
11 | but WITHOUT ANY WARRANTY; without even the implied warranty of |
---|
12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
---|
13 | GNU General Public License for more details. |
---|
14 | |
---|
15 | You should have received a copy of the GNU General Public License along |
---|
16 | with this program; if not, write to the Free Software Foundation, Inc., |
---|
17 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. |
---|
18 | **************************************************************************/ |
---|
19 | |
---|
20 | |
---|
21 | #include <unistd.h> |
---|
22 | #include <stdio.h> |
---|
23 | #include <stdlib.h> |
---|
24 | #include <cairo.h> |
---|
25 | #include <libgen.h> |
---|
26 | #include <string.h> |
---|
27 | #include <gtk/gtk.h> |
---|
28 | #include <gdk/gdkkeysyms.h> |
---|
29 | #include <glib/poppler.h> |
---|
30 | #include <math.h> |
---|
31 | |
---|
32 | #define SAGEPDF_DEF_SCALEFACTOR (4) |
---|
33 | |
---|
34 | int npages; |
---|
35 | int firstPage = 0; |
---|
36 | PopplerDocument *document; |
---|
37 | PopplerPage *page; |
---|
38 | double width, height; |
---|
39 | cairo_t *cr, *sage_cr; |
---|
40 | cairo_surface_t *surface, *sage_surface; |
---|
41 | int scaleFactor; |
---|
42 | |
---|
43 | #ifndef NOSAGE |
---|
44 | // headers for SAGE |
---|
45 | #include "sail.h" |
---|
46 | #include "misc.h" |
---|
47 | sail sageInf; // sail object |
---|
48 | |
---|
49 | // for dxt compression |
---|
50 | #include "libdxt.h" |
---|
51 | |
---|
52 | using namespace std; |
---|
53 | bool useDXT = true; |
---|
54 | |
---|
55 | byte *sageBuffer = NULL; // buffers for sage and dxt data |
---|
56 | byte *dxt = NULL; |
---|
57 | byte *rgba = NULL; |
---|
58 | |
---|
59 | float lastX = 0; |
---|
60 | float lastY = 0; |
---|
61 | float dist = 0; |
---|
62 | |
---|
63 | GdkWindow *local_window = NULL; |
---|
64 | #endif |
---|
65 | |
---|
66 | float local_startX = 0; |
---|
67 | float local_startY = 0; |
---|
68 | float local_dist = 0; |
---|
69 | bool button1_is_pressed = false; |
---|
70 | |
---|
71 | |
---|
72 | void |
---|
73 | show_page() |
---|
74 | { |
---|
75 | /* Clear window */ |
---|
76 | cairo_rectangle(cr, 0, 0, width, height); |
---|
77 | cairo_stroke_preserve(cr); |
---|
78 | cairo_set_source_rgb(cr, 1, 1, 1); |
---|
79 | cairo_fill(cr); |
---|
80 | |
---|
81 | |
---|
82 | cairo_set_source_surface(cr, surface, 0, 0); |
---|
83 | cairo_translate(cr, 0, 0); |
---|
84 | cairo_scale(cr, 1, 1); |
---|
85 | poppler_page_render(page, cr); |
---|
86 | |
---|
87 | cairo_set_operator(cr, CAIRO_OPERATOR_DEST_OVER); |
---|
88 | cairo_set_source_rgb(cr, 1, 1, 1); |
---|
89 | cairo_paint(cr); |
---|
90 | |
---|
91 | |
---|
92 | #ifndef NOSAGE |
---|
93 | if( useDXT ) |
---|
94 | { |
---|
95 | int cairo_height, cairo_width, cairo_rowstride; |
---|
96 | unsigned int numBytes, *src; |
---|
97 | unsigned char *cairo_data, *dst; |
---|
98 | |
---|
99 | sage_surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, width*scaleFactor, height*scaleFactor); |
---|
100 | sage_cr = cairo_create(sage_surface); |
---|
101 | cairo_save (sage_cr); |
---|
102 | /* Flip */ |
---|
103 | cairo_translate(sage_cr, 0, height*scaleFactor); |
---|
104 | cairo_scale(sage_cr, scaleFactor, -scaleFactor); |
---|
105 | poppler_page_render (page, sage_cr); |
---|
106 | cairo_restore (sage_cr); |
---|
107 | |
---|
108 | cairo_set_operator(sage_cr, CAIRO_OPERATOR_DEST_OVER); |
---|
109 | cairo_set_source_rgba(sage_cr, 1, 1, 1, 1); |
---|
110 | cairo_paint(sage_cr); |
---|
111 | |
---|
112 | /* We'd like to apply the sage_surface data directly to the |
---|
113 | * DXT compression engine but it is in the wrong colour space. |
---|
114 | * Therefore we extract it manually to the rgba buffer. |
---|
115 | */ |
---|
116 | cairo_height = cairo_image_surface_get_height (sage_surface); |
---|
117 | cairo_width = cairo_image_surface_get_width (sage_surface); |
---|
118 | cairo_rowstride = cairo_image_surface_get_stride (sage_surface); |
---|
119 | cairo_data = cairo_image_surface_get_data (sage_surface); |
---|
120 | for (int y = 0; y < cairo_height; y++) |
---|
121 | { |
---|
122 | src = (unsigned int *) (cairo_data + y * cairo_rowstride); |
---|
123 | dst = rgba + y * cairo_rowstride; |
---|
124 | for (int x = 0; x < cairo_width; x++) |
---|
125 | { |
---|
126 | dst[0] = (*src >> 16) & 0xff; |
---|
127 | dst[1] = (*src >> 8) & 0xff; |
---|
128 | dst[2] = (*src >> 0) & 0xff; |
---|
129 | dst[3] = (*src >> 24) & 0xff; |
---|
130 | dst += 4; |
---|
131 | src++; |
---|
132 | } |
---|
133 | } |
---|
134 | |
---|
135 | numBytes = CompressDXT(rgba, dxt, scaleFactor * width, scaleFactor * height, FORMAT_DXT1, 1); |
---|
136 | sageBuffer = (byte*)sageInf.getBuffer(); |
---|
137 | memcpy(sageBuffer, dxt, scaleFactor * scaleFactor * width*height*4/8); |
---|
138 | sageInf.swapBuffer(); |
---|
139 | |
---|
140 | cairo_destroy(sage_cr); |
---|
141 | } |
---|
142 | else |
---|
143 | { |
---|
144 | sage_surface = cairo_image_surface_create_for_data ((unsigned char*)sageInf.getBuffer(), |
---|
145 | CAIRO_FORMAT_ARGB32, width * scaleFactor, height * scaleFactor, |
---|
146 | cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, width * scaleFactor)); |
---|
147 | sage_cr = cairo_create(sage_surface); |
---|
148 | |
---|
149 | /* Clear SAGE window */ |
---|
150 | cairo_rectangle(sage_cr, 0, 0, width * scaleFactor, height * scaleFactor); |
---|
151 | cairo_stroke_preserve(sage_cr); |
---|
152 | cairo_set_source_rgb(sage_cr, 1, 1, 1); |
---|
153 | cairo_fill(sage_cr); |
---|
154 | |
---|
155 | cairo_set_source_surface(sage_cr, sage_surface, 0, 0); |
---|
156 | cairo_scale(sage_cr, scaleFactor, scaleFactor); |
---|
157 | poppler_page_render(page, sage_cr); |
---|
158 | cairo_set_operator(sage_cr, CAIRO_OPERATOR_DEST_OVER); |
---|
159 | cairo_set_source_rgb(sage_cr, 1, 1, 1); |
---|
160 | cairo_paint(sage_cr); |
---|
161 | |
---|
162 | sageInf.swapBuffer(); |
---|
163 | cairo_destroy(sage_cr); |
---|
164 | } |
---|
165 | #endif |
---|
166 | |
---|
167 | } |
---|
168 | |
---|
169 | void |
---|
170 | show_absolute_page(int requested_page) |
---|
171 | { |
---|
172 | if( requested_page < 0 ) |
---|
173 | requested_page = 0; |
---|
174 | if( requested_page >= npages ) |
---|
175 | requested_page = npages - 1; |
---|
176 | |
---|
177 | /* Don't render again if we're already at that page */ |
---|
178 | if( poppler_page_get_index(page) == requested_page ) |
---|
179 | return; |
---|
180 | |
---|
181 | page = poppler_document_get_page(document, requested_page); |
---|
182 | show_page(); |
---|
183 | } |
---|
184 | |
---|
185 | void |
---|
186 | show_next_page(int next) |
---|
187 | { |
---|
188 | show_absolute_page(poppler_page_get_index(page) + next); |
---|
189 | /* |
---|
190 | int ncurrent = poppler_page_get_index(page) + next; |
---|
191 | if( (ncurrent > -1) && (ncurrent < npages) ) |
---|
192 | page = poppler_document_get_page(document, ncurrent); |
---|
193 | show_page(); |
---|
194 | */ |
---|
195 | } |
---|
196 | |
---|
197 | gint |
---|
198 | check_sage_messages(gpointer data) |
---|
199 | { |
---|
200 | #ifndef NOSAGE |
---|
201 | sageMessage msg; |
---|
202 | if (sageInf.checkMsg(msg, false) > 0) { |
---|
203 | char *data = (char*) msg.getData(); |
---|
204 | |
---|
205 | switch (msg.getCode()) { |
---|
206 | case APP_QUIT : { |
---|
207 | sageInf.shutdown(); |
---|
208 | gtk_main_quit(); |
---|
209 | break; |
---|
210 | |
---|
211 | case EVT_CLICK: |
---|
212 | // Click event x and y location normalized to size of window |
---|
213 | float clickX, clickY; |
---|
214 | |
---|
215 | // Click device Id, button Id, and is down flag |
---|
216 | int clickDeviceId, clickButtonId, clickIsDown, clickEvent; |
---|
217 | |
---|
218 | // Parse message |
---|
219 | sscanf(data, |
---|
220 | "%d %f %f %d %d %d", |
---|
221 | &clickDeviceId, &clickX, &clickY, |
---|
222 | &clickButtonId, &clickIsDown, &clickEvent); |
---|
223 | //printf("Pointer click at %f,%f\n", clickX, clickY); |
---|
224 | |
---|
225 | // record the click position so we know how far we moved |
---|
226 | if (clickIsDown && clickEvent == EVT_PAN) { |
---|
227 | lastX = clickX; |
---|
228 | lastY = clickY; |
---|
229 | } |
---|
230 | |
---|
231 | if( clickIsDown ) |
---|
232 | { |
---|
233 | //printf("Pointer click by button %d\n", clickButtonId); |
---|
234 | |
---|
235 | cr = gdk_cairo_create(local_window); |
---|
236 | if( clickButtonId == 1 ) |
---|
237 | show_next_page(1); |
---|
238 | else |
---|
239 | show_next_page(-1); |
---|
240 | } |
---|
241 | |
---|
242 | break; |
---|
243 | |
---|
244 | case EVT_PAN: |
---|
245 | // Pan event properties |
---|
246 | int panDeviceId; |
---|
247 | |
---|
248 | // Pan event x and y location and change in x, y and z direction |
---|
249 | // normalized to size of window |
---|
250 | float startX, startY, panDX, panDY, panDZ; |
---|
251 | sscanf(data, |
---|
252 | "%d %f %f %f %f %f", |
---|
253 | &panDeviceId, &startX, &startY, &panDX, &panDY, &panDZ); |
---|
254 | |
---|
255 | // keep track of distance |
---|
256 | dist += panDX; |
---|
257 | |
---|
258 | // we started a new drag |
---|
259 | if (lastX != startX) { |
---|
260 | lastX = startX; |
---|
261 | dist = 0; |
---|
262 | } |
---|
263 | else if( fabs(dist) > 0.07 ) { // if we dragged more than a certain distance, change a page |
---|
264 | cr = gdk_cairo_create(local_window); |
---|
265 | if (dist > 0) |
---|
266 | show_next_page(1); |
---|
267 | else |
---|
268 | show_next_page(-1); |
---|
269 | |
---|
270 | // reset the counter |
---|
271 | lastX = startX; |
---|
272 | dist = 0; |
---|
273 | } |
---|
274 | break; |
---|
275 | |
---|
276 | } |
---|
277 | } |
---|
278 | } |
---|
279 | #endif |
---|
280 | return TRUE; |
---|
281 | } |
---|
282 | |
---|
283 | static gboolean |
---|
284 | on_motion_notify_event(GtkWidget *widget, GdkEventMotion *event, gpointer data) |
---|
285 | { |
---|
286 | float local_lastX = event->x; |
---|
287 | local_dist = local_lastX - local_startX; |
---|
288 | |
---|
289 | if( button1_is_pressed ) |
---|
290 | if( fabs(local_dist / width) > 0.07 ) |
---|
291 | { |
---|
292 | cr = gdk_cairo_create(widget->window); |
---|
293 | local_startX = local_lastX; |
---|
294 | if( local_dist > 0 ) |
---|
295 | show_next_page(1); |
---|
296 | else |
---|
297 | show_next_page(-1); |
---|
298 | } |
---|
299 | |
---|
300 | return true; |
---|
301 | } |
---|
302 | |
---|
303 | static gboolean |
---|
304 | on_button_press_event(GtkWidget *widget, GdkEventButton *event, gpointer data) |
---|
305 | { |
---|
306 | cr = gdk_cairo_create(widget->window); |
---|
307 | button1_is_pressed = false; |
---|
308 | switch(event->button) |
---|
309 | { |
---|
310 | case 1: |
---|
311 | button1_is_pressed = true; |
---|
312 | local_startX = event->x; |
---|
313 | show_next_page(1); |
---|
314 | break; |
---|
315 | case 3: |
---|
316 | show_next_page(-1); |
---|
317 | break; |
---|
318 | default: |
---|
319 | // printf("Unhandled button press: %d\n", event->button); |
---|
320 | return false; |
---|
321 | break; |
---|
322 | } |
---|
323 | return true; |
---|
324 | } |
---|
325 | |
---|
326 | static gboolean |
---|
327 | on_key_release_event(GtkWidget *widget, GdkEventKey *event, gpointer data) |
---|
328 | { |
---|
329 | cr = gdk_cairo_create(widget->window); |
---|
330 | switch(event->keyval) |
---|
331 | { |
---|
332 | case GDK_Home: |
---|
333 | show_absolute_page(0); |
---|
334 | break; |
---|
335 | case GDK_End: |
---|
336 | show_absolute_page(npages); |
---|
337 | break; |
---|
338 | case GDK_Page_Up: |
---|
339 | case GDK_Left: |
---|
340 | show_next_page(-1); |
---|
341 | break; |
---|
342 | case GDK_Page_Down: |
---|
343 | case GDK_Right: |
---|
344 | show_next_page(1); |
---|
345 | break; |
---|
346 | case GDK_Escape: |
---|
347 | printf("Quit\n"); |
---|
348 | gtk_main_quit(); |
---|
349 | break; |
---|
350 | default: |
---|
351 | // printf("XXXXXXXXXX Unhandled key: %d\n", event->keyval); |
---|
352 | return false; |
---|
353 | break; |
---|
354 | } |
---|
355 | return true; |
---|
356 | } |
---|
357 | |
---|
358 | static gboolean |
---|
359 | on_expose_event(GtkWidget *widget, GdkEventExpose *event, gpointer data) |
---|
360 | { |
---|
361 | cr = gdk_cairo_create(widget->window); |
---|
362 | show_page(); |
---|
363 | |
---|
364 | return FALSE; |
---|
365 | } |
---|
366 | |
---|
367 | |
---|
368 | int main(int argc, char *argv[]) |
---|
369 | { |
---|
370 | unsigned int window_width=-1, window_height=-1; // sage window size |
---|
371 | GtkWidget *window; |
---|
372 | |
---|
373 | char *filename; |
---|
374 | char pathbuf[PATH_MAX]; |
---|
375 | char uri[256]; |
---|
376 | char *dirc, *basec, *dname, *bname; |
---|
377 | int i=0; |
---|
378 | |
---|
379 | gtk_init(&argc, &argv); |
---|
380 | scaleFactor = SAGEPDF_DEF_SCALEFACTOR; |
---|
381 | #ifndef NOSAGE |
---|
382 | sage::initUtil(); |
---|
383 | #endif |
---|
384 | |
---|
385 | // parse command line arguments |
---|
386 | if( argc < 2 ) |
---|
387 | { |
---|
388 | #ifndef NOSAGE |
---|
389 | sage::printLog("PDF> sagepdf filename [width] [height] [-show_original] [-page num] [-scale num]"); |
---|
390 | #else |
---|
391 | fprintf(stderr, "PDF> sagepdf filename [width] [height] [-show_original] [-page num] [-scale num]\n"); |
---|
392 | #endif |
---|
393 | exit(1); |
---|
394 | } |
---|
395 | for (int argNum=2; argNum<argc; argNum++) |
---|
396 | { |
---|
397 | if( strcmp(argv[argNum], "-page") == 0 ) |
---|
398 | { |
---|
399 | int p = atoi(argv[argNum+1]); |
---|
400 | if( p > 0 ) |
---|
401 | firstPage = p - 1; |
---|
402 | if( p < 0 ) |
---|
403 | firstPage = 0; |
---|
404 | argNum++; |
---|
405 | } |
---|
406 | #ifndef NOSAGE |
---|
407 | else if (strcmp(argv[argNum], "-show_original") == 0) |
---|
408 | { |
---|
409 | useDXT = false; |
---|
410 | } |
---|
411 | #endif |
---|
412 | else if( strcmp(argv[argNum], "-scale") == 0 ) |
---|
413 | { |
---|
414 | scaleFactor = atoi(argv[argNum+1]); |
---|
415 | if( scaleFactor < 1 ) |
---|
416 | scaleFactor = 1; |
---|
417 | argNum++; |
---|
418 | } |
---|
419 | else if(atoi(argv[argNum]) != 0 && atoi(argv[argNum+1]) != 0) |
---|
420 | { |
---|
421 | window_width = atoi( argv[argNum] ); |
---|
422 | window_height = atoi( argv[argNum+1] ); |
---|
423 | argNum++; |
---|
424 | } |
---|
425 | } |
---|
426 | |
---|
427 | /* Check that the filename is an absolute pathname */ |
---|
428 | filename = argv[1]; |
---|
429 | dirc = strdup(filename); |
---|
430 | basec = strdup(filename); |
---|
431 | dname = dirname(dirc); |
---|
432 | bname = basename(basec); |
---|
433 | |
---|
434 | if( dname[0] != '/' ) |
---|
435 | { |
---|
436 | fprintf(stderr, "Need absolute path for PDF file\n"); |
---|
437 | |
---|
438 | /* We could try to construct a path - |
---|
439 | * assume the path givern is relative to here |
---|
440 | */ |
---|
441 | if( ! getcwd(pathbuf, PATH_MAX) ) |
---|
442 | exit(1); |
---|
443 | sprintf(pathbuf + strlen(pathbuf), "/\0"); |
---|
444 | strcat(pathbuf, filename); |
---|
445 | fprintf(stderr, "Lets try %s\n", pathbuf); |
---|
446 | } |
---|
447 | else |
---|
448 | { |
---|
449 | sprintf(pathbuf, "%s\0", filename); |
---|
450 | } |
---|
451 | /* Can we read it ? */ |
---|
452 | if( access(pathbuf, R_OK ) ) |
---|
453 | { |
---|
454 | fprintf(stderr, "Can't access %s - exiting now ...\n", filename); |
---|
455 | exit(2); |
---|
456 | } |
---|
457 | |
---|
458 | sprintf(uri, "file://%s\0", pathbuf); |
---|
459 | document = poppler_document_new_from_file(uri, NULL, NULL); |
---|
460 | npages = poppler_document_get_n_pages(document); |
---|
461 | |
---|
462 | if( npages <= firstPage ) |
---|
463 | firstPage = npages - 1; |
---|
464 | page = poppler_document_get_page(document, firstPage); |
---|
465 | poppler_page_get_size(page, &width, &height); |
---|
466 | printf("Page %d has size %fx%f\n", firstPage, width, height); |
---|
467 | |
---|
468 | |
---|
469 | #ifndef NOSAGE |
---|
470 | if (useDXT) |
---|
471 | { |
---|
472 | if ((int)width % 4 != 0 || (int)height % 4 != 0) |
---|
473 | { |
---|
474 | fprintf(stderr, "\n**** Image cropped a few pixels to be a multiple of 4 for dxt"); |
---|
475 | width -= (int)width % 4; width = (int)width * 1.0; |
---|
476 | height -= (int)height % 4; height = (int)height * 1.0; |
---|
477 | } |
---|
478 | |
---|
479 | // allocate buffers |
---|
480 | rgba = (byte*) memalign(16, scaleFactor * scaleFactor * width*height*4); |
---|
481 | dxt = (byte*) memalign(16, scaleFactor * scaleFactor * width*height*4/8); |
---|
482 | } |
---|
483 | |
---|
484 | // SAGE setup |
---|
485 | sailConfig cfg; |
---|
486 | cfg.init("sagepdf.conf"); // every app has a config file named "appName.conf" |
---|
487 | std::cout << "SAIL configuration was initialized by sagepdf.conf" << std::endl; |
---|
488 | |
---|
489 | cfg.setAppName("sagepdf"); |
---|
490 | cfg.rank = 0; |
---|
491 | cfg.resX = width * scaleFactor; |
---|
492 | cfg.resY = height * scaleFactor; |
---|
493 | cfg.winWidth = width * scaleFactor; |
---|
494 | cfg.winHeight = height * scaleFactor; |
---|
495 | |
---|
496 | sageRect renderImageMap; |
---|
497 | renderImageMap.left = 0.0; |
---|
498 | renderImageMap.right = 1.0; |
---|
499 | renderImageMap.bottom = 0.0; |
---|
500 | renderImageMap.top = 1.0; |
---|
501 | |
---|
502 | cfg.imageMap = renderImageMap; |
---|
503 | if( useDXT ) |
---|
504 | { |
---|
505 | cfg.pixFmt = PIXFMT_DXT; |
---|
506 | cfg.rowOrd = BOTTOM_TO_TOP; |
---|
507 | } |
---|
508 | else |
---|
509 | { |
---|
510 | cfg.pixFmt = PIXFMT_8888_INV; |
---|
511 | cfg.rowOrd = TOP_TO_BOTTOM; |
---|
512 | } |
---|
513 | cfg.master = TRUE; |
---|
514 | |
---|
515 | sageInf.init(cfg); |
---|
516 | std::cout << "sail initialized " << std::endl; |
---|
517 | #endif |
---|
518 | |
---|
519 | //printf("Using page size %fx%f\n", width, height); |
---|
520 | surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, width, height); |
---|
521 | |
---|
522 | window = gtk_window_new(GTK_WINDOW_TOPLEVEL); |
---|
523 | gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER); |
---|
524 | gtk_window_set_default_size(GTK_WINDOW(window), width, height); |
---|
525 | gtk_widget_set_app_paintable(window, TRUE); |
---|
526 | |
---|
527 | |
---|
528 | g_signal_connect(window, "expose-event", G_CALLBACK (on_expose_event), NULL); |
---|
529 | g_signal_connect(window, "destroy", G_CALLBACK (gtk_main_quit), NULL); |
---|
530 | g_signal_connect(window, "key_release_event", G_CALLBACK (on_key_release_event), NULL); |
---|
531 | g_signal_connect(window, "button_press_event", G_CALLBACK (on_button_press_event), NULL); |
---|
532 | g_signal_connect(window, "motion_notify_event", G_CALLBACK (on_motion_notify_event), NULL); |
---|
533 | gtk_widget_set_events (window, GDK_KEY_RELEASE | |
---|
534 | GDK_BUTTON_PRESS_MASK | |
---|
535 | GDK_BUTTON_MOTION_MASK | |
---|
536 | GDK_POINTER_MOTION_HINT_MASK ); |
---|
537 | |
---|
538 | gtk_widget_show_all(window); |
---|
539 | #ifndef NOSAGE |
---|
540 | local_window = window->window; |
---|
541 | #endif |
---|
542 | |
---|
543 | g_timeout_add (200, check_sage_messages, NULL); |
---|
544 | |
---|
545 | gtk_main(); |
---|
546 | sageInf.shutdown(); |
---|
547 | cairo_surface_destroy(surface); |
---|
548 | |
---|
549 | return 0; |
---|
550 | } |
---|
551 | |
---|