/* See LICENSE file for license and copyright information */ #define _BSD_SOURCE || _XOPEN_SOURCE >= 500 #include #include #include #include #include #include #include #include #include #include #include /* macros */ #define LENGTH(x) sizeof(x)/sizeof((x)[0]) /* enums */ enum { NEXT, PREVIOUS, LEFT, RIGHT, UP, DOWN, BOTTOM, TOP, HIDE, NORMAL, HIGHLIGHT, INSERT, VISUAL, DELETE_LAST_WORD, DEFAULT, ERROR, WARNING, NEXT_GROUP, PREVIOUS_GROUP, ZOOM_IN, ZOOM_OUT, ZOOM_ORIGINAL, ZOOM_SPECIFIC, FORWARD, BACKWARD, ADJUST_BESTFIT, ADJUST_WIDTH, CONTINUOUS, DELETE_LAST, ADD_MARKER, EVAL_MARKER }; /* typedefs */ struct CElement { char *value; char *description; struct CElement *next; }; typedef struct CElement CompletionElement; struct CGroup { char *value; CompletionElement *elements; struct CGroup *next; }; typedef struct CGroup CompletionGroup; typedef struct { CompletionGroup* groups; } Completion; typedef struct { char* command; char* description; int command_id; gboolean is_group; GtkWidget* row; } CompletionRow; typedef struct { int n; void *data; } Argument; typedef struct { int mask; int key; void (*function)(Argument*); int mode; Argument argument; } Shortcut; typedef struct { int mask; int key; void (*function)(Argument*); Argument argument; } InputbarShortcut; typedef struct { char* command; char* abbr; gboolean (*function)(int, char**); Completion* (*completion)(char*); char* description; } Command; typedef struct { char* regex; void (*function)(char*, Argument*); Argument argument; } BufferCommand; typedef struct { char identifier; gboolean (*function)(char*, Argument*); int always; Argument argument; } SpecialCommand; typedef struct { PopplerPage *page; } Page; typedef struct { char* name; void* variable; char type; gboolean render; char* description; } Setting; typedef struct { int id; int page; } Marker; typedef struct { char* id; int page; } Bookmark; /* zathura */ struct { struct { GtkWindow *window; GtkBox *box; GtkBox *continuous; GtkScrolledWindow *view; GtkViewport *viewport; GtkWidget *statusbar; GtkBox *statusbar_entries; GtkEntry *inputbar; GtkWidget *index; GtkWidget *information; GtkWidget *drawing_area; } UI; struct { GdkColor default_fg; GdkColor default_bg; GdkColor inputbar_fg; GdkColor inputbar_bg; GdkColor statusbar_fg; GdkColor statusbar_bg; GdkColor completion_fg; GdkColor completion_bg; GdkColor completion_g_bg; GdkColor completion_g_fg; GdkColor completion_hl_fg; GdkColor completion_hl_bg; GdkColor notification_e_fg; GdkColor notification_e_bg; GdkColor notification_w_fg; GdkColor notification_w_bg; GdkColor recolor_darkcolor; GdkColor recolor_lightcolor; GdkColor search_highlight; PangoFontDescription *font; } Style; struct { GString *buffer; GList *history; int mode; int viewing_mode; gboolean recolor; GtkLabel *status_text; GtkLabel *status_buffer; GtkLabel *status_state; } Global; struct { char* filename; char* pages; int scroll_percentage; } State; struct { Marker* markers; int number_of_markers; int last; } Marker; struct { int wd; int fd; } Inotify; struct { GKeyFile *data; char *file; Bookmark *bookmarks; int number_of_bookmarks; } Bookmarks; struct { PopplerDocument *document; char *file; char *password; Page **pages; int page_number; int number_of_pages; int scale; int rotate; cairo_surface_t *surface; } PDF; struct { GStaticMutex pdflib_lock; GStaticMutex document_lock; GStaticMutex search_lock; } Lock; struct { GThread* search_thread; gboolean search_thread_running; GThread* inotify_thread; } Thread; } Zathura; /* function declarations */ void init_directories(); void init_zathura(); void add_marker(int); void build_index(GtkTreeModel*, GtkTreeIter*, PopplerIndexIter*); void change_mode(int); void highlight_result(int, PopplerRectangle*); void draw(int); void eval_marker(int); void notify(int, char*); gboolean open_file(char*, char*); void update_status(); void recalcRectangle(int, PopplerRectangle*); void setCompletionRowColor(GtkBox*, int, int); void set_page(int); void switch_view(GtkWidget*); GtkEventBox* createCompletionRow(GtkBox*, char*, char*, gboolean); /* thread declaration */ void* search(void*); void* watch_file(void*); /* shortcut declarations */ void sc_abort(Argument*); void sc_adjust_window(Argument*); void sc_change_buffer(Argument*); void sc_change_mode(Argument*); void sc_focus_inputbar(Argument*); void sc_navigate(Argument*); void sc_recolor(Argument*); void sc_rotate(Argument*); void sc_scroll(Argument*); void sc_search(Argument*); void sc_toggle_index(Argument*); void sc_toggle_inputbar(Argument*); void sc_toggle_statusbar(Argument*); void sc_quit(Argument*); /* inputbar shortcut declarations */ void isc_abort(Argument*); void isc_command_history(Argument*); void isc_completion(Argument*); void isc_string_manipulation(Argument*); /* command declarations */ gboolean cmd_bookmark(int, char**); gboolean cmd_open_bookmark(int, char**); gboolean cmd_close(int, char**); gboolean cmd_delete_bookmark(int, char**); gboolean cmd_export(int, char**); gboolean cmd_info(int, char**); gboolean cmd_open(int, char**); gboolean cmd_print(int, char**); gboolean cmd_rotate(int, char**); gboolean cmd_set(int, char**); gboolean cmd_quit(int, char**); gboolean cmd_save(int, char**); /* completion commands */ Completion* cc_bookmark(char*); Completion* cc_export(char*); Completion* cc_open(char*); Completion* cc_print(char*); Completion* cc_set(char*); /* buffer command declarations */ void bcmd_evalmarker(char*, Argument*); void bcmd_goto(char*, Argument*); void bcmd_scroll(char*, Argument*); void bcmd_setmarker(char*, Argument*); void bcmd_zoom(char*, Argument*); /* special command delcarations */ gboolean scmd_search(char*, Argument*); /* callback declarations */ gboolean cb_destroy(GtkWidget*, gpointer); gboolean cb_draw(GtkWidget*, GdkEventExpose*, gpointer); gboolean cb_view_kb_pressed(GtkWidget*, GdkEventKey*, gpointer); gboolean cb_index_selection_changed(GtkTreeSelection*, GtkWidget*); gboolean cb_inputbar_kb_pressed(GtkWidget*, GdkEventKey*, gpointer); gboolean cb_inputbar_activate(GtkEntry*, gpointer); /* configuration */ #include "config.h" /* function implementation */ void init_directories() { /* create zathura directory */ gchar *base_directory = g_build_filename(g_get_home_dir(), ZATHURA_DIR, NULL); g_mkdir_with_parents(base_directory, 0771); g_free(base_directory); /* create or open existing bookmark file */ Zathura.Bookmarks.data = g_key_file_new(); char* bookmarks = g_strdup_printf("%s/%s/%s", g_get_home_dir(), ZATHURA_DIR, BOOKMARK_FILE); if(!g_file_test(bookmarks, G_FILE_TEST_IS_REGULAR)) { /* file does not exist */ g_file_set_contents(bookmarks, "# Zathura bookmarks\n", -1, NULL); } GError* error = NULL; if(!g_key_file_load_from_file(Zathura.Bookmarks.data, bookmarks, G_KEY_FILE_KEEP_COMMENTS | G_KEY_FILE_KEEP_TRANSLATIONS, &error)) { notify(ERROR, g_strdup_printf("Could not load bookmark file: %s", error->message)); } Zathura.Bookmarks.file = g_strdup(bookmarks); g_free(bookmarks); } void init_zathura() { /* init mutexes */ g_static_mutex_init(&(Zathura.Lock.pdflib_lock)); g_static_mutex_init(&(Zathura.Lock.search_lock)); g_static_mutex_init(&(Zathura.Lock.document_lock)); /* look */ gdk_color_parse(default_fgcolor, &(Zathura.Style.default_fg)); gdk_color_parse(default_bgcolor, &(Zathura.Style.default_bg)); gdk_color_parse(inputbar_fgcolor, &(Zathura.Style.inputbar_fg)); gdk_color_parse(inputbar_bgcolor, &(Zathura.Style.inputbar_bg)); gdk_color_parse(statusbar_fgcolor, &(Zathura.Style.statusbar_fg)); gdk_color_parse(statusbar_bgcolor, &(Zathura.Style.statusbar_bg)); gdk_color_parse(completion_fgcolor, &(Zathura.Style.completion_fg)); gdk_color_parse(completion_bgcolor, &(Zathura.Style.completion_bg)); gdk_color_parse(completion_g_fgcolor, &(Zathura.Style.completion_g_fg)); gdk_color_parse(completion_g_fgcolor, &(Zathura.Style.completion_g_fg)); gdk_color_parse(completion_hl_fgcolor, &(Zathura.Style.completion_hl_fg)); gdk_color_parse(completion_hl_bgcolor, &(Zathura.Style.completion_hl_bg)); gdk_color_parse(notification_e_fgcolor, &(Zathura.Style.notification_e_fg)); gdk_color_parse(notification_e_bgcolor, &(Zathura.Style.notification_e_bg)); gdk_color_parse(notification_w_fgcolor, &(Zathura.Style.notification_w_fg)); gdk_color_parse(notification_w_bgcolor, &(Zathura.Style.notification_w_bg)); gdk_color_parse(recolor_darkcolor, &(Zathura.Style.recolor_darkcolor)); gdk_color_parse(recolor_lightcolor, &(Zathura.Style.recolor_lightcolor)); gdk_color_parse(search_highlight, &(Zathura.Style.search_highlight)); Zathura.Style.font = pango_font_description_from_string(font); /* other */ Zathura.Global.mode = NORMAL; Zathura.Global.viewing_mode = NORMAL; Zathura.Global.recolor = FALSE; Zathura.State.filename = (char*) DEFAULT_TEXT; Zathura.State.pages = ""; Zathura.State.scroll_percentage = 0; Zathura.Marker.markers = NULL; Zathura.Marker.number_of_markers = 0; Zathura.Marker.last = -1; Zathura.Inotify.fd = inotify_init(); /* UI */ Zathura.UI.window = GTK_WINDOW(gtk_window_new(GTK_WINDOW_TOPLEVEL)); Zathura.UI.box = GTK_BOX(gtk_vbox_new(FALSE, 0)); Zathura.UI.continuous = GTK_BOX(gtk_vbox_new(FALSE, 0)); Zathura.UI.view = GTK_SCROLLED_WINDOW(gtk_scrolled_window_new(NULL, NULL)); Zathura.UI.viewport = GTK_VIEWPORT(gtk_viewport_new(NULL, NULL)); Zathura.UI.drawing_area = gtk_drawing_area_new(); Zathura.UI.statusbar = gtk_event_box_new(); Zathura.UI.statusbar_entries = GTK_BOX(gtk_hbox_new(FALSE, 0)); Zathura.UI.inputbar = GTK_ENTRY(gtk_entry_new()); /* window */ gtk_window_set_title(Zathura.UI.window, "zathura"); GdkGeometry hints = { 1, 1 }; gtk_window_set_geometry_hints(Zathura.UI.window, NULL, &hints, GDK_HINT_MIN_SIZE); gtk_window_set_default_size(Zathura.UI.window, DEFAULT_WIDTH, DEFAULT_HEIGHT); g_signal_connect(G_OBJECT(Zathura.UI.window), "destroy", G_CALLBACK(cb_destroy), NULL); /* box */ gtk_box_set_spacing(Zathura.UI.box, 0); gtk_container_add(GTK_CONTAINER(Zathura.UI.window), GTK_WIDGET(Zathura.UI.box)); /* continuous */ gtk_box_set_spacing(Zathura.UI.continuous, 5); /* view */ g_signal_connect(G_OBJECT(Zathura.UI.view), "key-press-event", G_CALLBACK(cb_view_kb_pressed), NULL); gtk_container_add(GTK_CONTAINER(Zathura.UI.view), GTK_WIDGET(Zathura.UI.viewport)); gtk_viewport_set_shadow_type(Zathura.UI.viewport, GTK_SHADOW_NONE); #if SHOW_SCROLLBARS gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(Zathura.UI.view), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); #else gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(Zathura.UI.view), GTK_POLICY_NEVER, GTK_POLICY_NEVER); #endif /* drawing area */ gtk_widget_modify_bg(GTK_WIDGET(Zathura.UI.drawing_area), GTK_STATE_NORMAL, &(Zathura.Style.default_bg)); gtk_widget_show(Zathura.UI.drawing_area); g_signal_connect(G_OBJECT(Zathura.UI.drawing_area), "expose-event", G_CALLBACK(cb_draw), NULL); /* statusbar */ gtk_widget_modify_bg(GTK_WIDGET(Zathura.UI.statusbar), GTK_STATE_NORMAL, &(Zathura.Style.statusbar_bg)); Zathura.Global.status_text = GTK_LABEL(gtk_label_new(NULL)); Zathura.Global.status_state = GTK_LABEL(gtk_label_new(NULL)); Zathura.Global.status_buffer = GTK_LABEL(gtk_label_new(NULL)); gtk_widget_modify_fg(GTK_WIDGET(Zathura.Global.status_text), GTK_STATE_NORMAL, &(Zathura.Style.statusbar_fg)); gtk_widget_modify_fg(GTK_WIDGET(Zathura.Global.status_state), GTK_STATE_NORMAL, &(Zathura.Style.statusbar_fg)); gtk_widget_modify_fg(GTK_WIDGET(Zathura.Global.status_buffer), GTK_STATE_NORMAL, &(Zathura.Style.statusbar_fg)); gtk_widget_modify_font(GTK_WIDGET(Zathura.Global.status_text), Zathura.Style.font); gtk_widget_modify_font(GTK_WIDGET(Zathura.Global.status_state), Zathura.Style.font); gtk_widget_modify_font(GTK_WIDGET(Zathura.Global.status_buffer), Zathura.Style.font); gtk_misc_set_alignment(GTK_MISC(Zathura.Global.status_text), 0.0, 0.0); gtk_misc_set_alignment(GTK_MISC(Zathura.Global.status_state), 1.0, 0.0); gtk_misc_set_alignment(GTK_MISC(Zathura.Global.status_buffer), 1.0, 0.0); gtk_misc_set_padding(GTK_MISC(Zathura.Global.status_text), 2.0, 4.0); gtk_misc_set_padding(GTK_MISC(Zathura.Global.status_state), 2.0, 4.0); gtk_misc_set_padding(GTK_MISC(Zathura.Global.status_buffer), 2.0, 4.0); gtk_label_set_use_markup(Zathura.Global.status_text, TRUE); gtk_label_set_use_markup(Zathura.Global.status_state, TRUE); gtk_label_set_use_markup(Zathura.Global.status_buffer, TRUE); gtk_box_pack_start(Zathura.UI.statusbar_entries, GTK_WIDGET(Zathura.Global.status_text), TRUE, TRUE, 2); gtk_box_pack_start(Zathura.UI.statusbar_entries, GTK_WIDGET(Zathura.Global.status_buffer), FALSE, FALSE, 2); gtk_box_pack_start(Zathura.UI.statusbar_entries, GTK_WIDGET(Zathura.Global.status_state), FALSE, FALSE, 2); gtk_container_add(GTK_CONTAINER(Zathura.UI.statusbar), GTK_WIDGET(Zathura.UI.statusbar_entries)); /* inputbar */ gtk_entry_set_inner_border(Zathura.UI.inputbar, NULL); gtk_entry_set_has_frame( Zathura.UI.inputbar, FALSE); gtk_editable_set_editable( GTK_EDITABLE(Zathura.UI.inputbar), TRUE); gtk_widget_modify_base(GTK_WIDGET(Zathura.UI.inputbar), GTK_STATE_NORMAL, &(Zathura.Style.inputbar_bg)); gtk_widget_modify_text(GTK_WIDGET(Zathura.UI.inputbar), GTK_STATE_NORMAL, &(Zathura.Style.inputbar_fg)); gtk_widget_modify_font(GTK_WIDGET(Zathura.UI.inputbar), Zathura.Style.font); g_signal_connect(G_OBJECT(Zathura.UI.inputbar), "key-press-event", G_CALLBACK(cb_inputbar_kb_pressed), NULL); g_signal_connect(G_OBJECT(Zathura.UI.inputbar), "activate", G_CALLBACK(cb_inputbar_activate), NULL); /* packing */ gtk_box_pack_start(Zathura.UI.box, GTK_WIDGET(Zathura.UI.view), TRUE, TRUE, 0); gtk_box_pack_start(Zathura.UI.box, GTK_WIDGET(Zathura.UI.statusbar), FALSE, FALSE, 0); gtk_box_pack_end( Zathura.UI.box, GTK_WIDGET(Zathura.UI.inputbar), FALSE, FALSE, 0); } void add_marker(int id) { if( (id < 0x30) || (id > 0x7A)) return; /* current information */ int page_number = Zathura.PDF.page_number; /* search if entry already exists */ int i; for(i = 0; i < Zathura.Marker.number_of_markers; i++) { if(Zathura.Marker.markers[i].id == id) { Zathura.Marker.markers[i].page = page_number; Zathura.Marker.last = page_number; return; } } /* add new marker */ int marker_index = Zathura.Marker.number_of_markers++; Zathura.Marker.markers = realloc(Zathura.Marker.markers, sizeof(Marker) * (Zathura.Marker.number_of_markers)); Zathura.Marker.markers[marker_index].id = id; Zathura.Marker.markers[marker_index].page = page_number; Zathura.Marker.last = page_number; } void build_index(GtkTreeModel* model, GtkTreeIter* parent, PopplerIndexIter* index_iter) { g_static_mutex_lock(&(Zathura.Lock.pdflib_lock)); do { GtkTreeIter tree_iter; PopplerIndexIter *child; PopplerAction *action; gboolean expand; gchar *markup; action = poppler_index_iter_get_action(index_iter); expand = poppler_index_iter_is_open(index_iter); if(!action) continue; markup = g_markup_escape_text(action->any.title, -1); gtk_tree_store_append(GTK_TREE_STORE(model), &tree_iter, parent); gtk_tree_store_set(GTK_TREE_STORE(model), &tree_iter, 0, markup, 1, action, -1); g_object_weak_ref(G_OBJECT(model), (GWeakNotify) poppler_action_free, action); g_free(markup); child = poppler_index_iter_get_child(index_iter); g_static_mutex_unlock(&(Zathura.Lock.pdflib_lock)); if(child) build_index(model, &tree_iter, child); g_static_mutex_lock(&(Zathura.Lock.pdflib_lock)); poppler_index_iter_free(child); } while(poppler_index_iter_next(index_iter)); g_static_mutex_unlock(&(Zathura.Lock.pdflib_lock)); } void draw(int page_id) { if(!Zathura.PDF.document || page_id < 0 || page_id > Zathura.PDF.number_of_pages) return; double page_width, page_height; double width, height; double scale = ((double) Zathura.PDF.scale / 100.0); int rotate = Zathura.PDF.rotate; Page *current_page = Zathura.PDF.pages[page_id]; if(Zathura.PDF.surface) cairo_surface_destroy(Zathura.PDF.surface); Zathura.PDF.surface = NULL; g_static_mutex_lock(&(Zathura.Lock.pdflib_lock)); poppler_page_get_size(current_page->page, &page_width, &page_height); g_static_mutex_unlock(&(Zathura.Lock.pdflib_lock)); if(rotate == 0 || rotate == 180) { width = page_width * scale; height = page_height * scale; } else { width = page_height * scale; height = page_width * scale; } cairo_t *cairo; Zathura.PDF.surface = cairo_image_surface_create(CAIRO_FORMAT_RGB24, width, height); cairo = cairo_create(Zathura.PDF.surface); cairo_save(cairo); cairo_set_source_rgb(cairo, 1, 1, 1); cairo_rectangle(cairo, 0, 0, width, height); cairo_fill(cairo); cairo_restore(cairo); cairo_save(cairo); switch(rotate) { case 90: cairo_translate(cairo, width, 0); break; case 180: cairo_translate(cairo, width, height); break; case 270: cairo_translate(cairo, 0, height); break; default: cairo_translate(cairo, 0, 0); } if(scale != 1.0) cairo_scale(cairo, scale, scale); if(rotate != 0) cairo_rotate(cairo, rotate * G_PI / 180.0); g_static_mutex_lock(&(Zathura.Lock.pdflib_lock)); poppler_page_render(current_page->page, cairo); g_static_mutex_unlock(&(Zathura.Lock.pdflib_lock)); cairo_restore(cairo); cairo_destroy(cairo); if(Zathura.Global.recolor) { unsigned char* image = cairo_image_surface_get_data(Zathura.PDF.surface); int x, y; int width = cairo_image_surface_get_width(Zathura.PDF.surface); int height = cairo_image_surface_get_height(Zathura.PDF.surface); int rowstride = cairo_image_surface_get_stride(Zathura.PDF.surface); /* recolor code based on qimageblitz library flatten() function (http://sourceforge.net/projects/qimageblitz/) */ int r1 = Zathura.Style.recolor_darkcolor.red / 257; int g1 = Zathura.Style.recolor_darkcolor.green / 257; int b1 = Zathura.Style.recolor_darkcolor.blue / 257; int r2 = Zathura.Style.recolor_lightcolor.red / 257; int g2 = Zathura.Style.recolor_lightcolor.green / 257; int b2 = Zathura.Style.recolor_lightcolor.blue / 257; int min = 0x00; int max = 0xFF; int mean; float sr = ((float) r2 - r1) / (max - min); float sg = ((float) g2 - g1) / (max - min); float sb = ((float) b2 - b1) / (max - min); for (y = 0; y < height; y++) { unsigned char* data = image + y * rowstride; for (x = 0; x < width; x++) { mean = (data[0] + data[1] + data[2]) / 3; data[2] = sr * (mean - min) + r1 + 0.5; data[1] = sg * (mean - min) + g1 + 0.5; data[0] = sb * (mean - min) + b1 + 0.5; data += 4; } } } gtk_widget_set_size_request(Zathura.UI.drawing_area, width, height); gtk_widget_queue_draw(Zathura.UI.drawing_area); } void change_mode(int mode) { char* mode_text; switch(mode) { case INSERT: mode_text = "-- INSERT --"; break; case VISUAL: mode_text = "-- VISUAL --"; break; case ADD_MARKER: mode_text = ""; break; case EVAL_MARKER: mode_text = ""; break; default: mode_text = ""; mode = NORMAL; break; } Zathura.Global.mode = mode; notify(DEFAULT, mode_text); } void eval_marker(int id) { /* go to last marker */ if(id == 0x27) { int current_page = Zathura.PDF.page_number; set_page(Zathura.Marker.last); Zathura.Marker.last = current_page; return; } /* search markers */ int i; for(i = 0; i < Zathura.Marker.number_of_markers; i++) { if(Zathura.Marker.markers[i].id == id) { set_page(Zathura.Marker.markers[i].page); return; } } } void highlight_result(int page_id, PopplerRectangle* rectangle) { cairo_t *cairo = cairo_create(Zathura.PDF.surface); cairo_set_source_rgba(cairo, Zathura.Style.search_highlight.red, Zathura.Style.search_highlight.green, Zathura.Style.search_highlight.blue, TRANSPARENCY); recalcRectangle(page_id, rectangle); cairo_rectangle(cairo, rectangle->x1, rectangle->y1, (rectangle->x2 - rectangle->x1), (rectangle->y2 - rectangle->y1)); cairo_fill(cairo); } void notify(int level, char* message) { switch(level) { case ERROR: gtk_widget_modify_base(GTK_WIDGET(Zathura.UI.inputbar), GTK_STATE_NORMAL, &(Zathura.Style.notification_e_bg)); gtk_widget_modify_text(GTK_WIDGET(Zathura.UI.inputbar), GTK_STATE_NORMAL, &(Zathura.Style.notification_e_fg)); break; case WARNING: gtk_widget_modify_base(GTK_WIDGET(Zathura.UI.inputbar), GTK_STATE_NORMAL, &(Zathura.Style.notification_w_bg)); gtk_widget_modify_text(GTK_WIDGET(Zathura.UI.inputbar), GTK_STATE_NORMAL, &(Zathura.Style.notification_w_fg)); break; default: gtk_widget_modify_base(GTK_WIDGET(Zathura.UI.inputbar), GTK_STATE_NORMAL, &(Zathura.Style.inputbar_bg)); gtk_widget_modify_text(GTK_WIDGET(Zathura.UI.inputbar), GTK_STATE_NORMAL, &(Zathura.Style.inputbar_fg)); break; } if(message) gtk_entry_set_text(Zathura.UI.inputbar, message); } gboolean open_file(char* path, char* password) { /* get filename */ char* file = realpath(path, NULL); if(path[0] == '~') { char* home_path = getenv("HOME"); int file_len = strlen(home_path) + strlen(path) - 1; file = malloc(file_len); snprintf(file, file_len, "%s%s", getenv("HOME"), path + 1); } /* check if file exists */ if(!g_file_test(file, G_FILE_TEST_IS_REGULAR)) { notify(ERROR, "File does not exist"); free(file); return FALSE; } /* close old file */ cmd_close(-1, NULL); /* check saved password */ if(!password) password = (Zathura.PDF.password && strlen(Zathura.PDF.password) != 0) ? Zathura.PDF.password : NULL; /* open file */ GError* error = NULL; g_static_mutex_lock(&(Zathura.Lock.pdflib_lock)); g_static_mutex_lock(&(Zathura.Lock.document_lock)); Zathura.PDF.document = poppler_document_new_from_file(g_strdup_printf("file://%s", file), password ? password : NULL, &error); g_static_mutex_unlock(&(Zathura.Lock.document_lock)); g_static_mutex_unlock(&(Zathura.Lock.pdflib_lock)); if(!Zathura.PDF.document) { char* message = (error->code == 1) ? "(Use \":set password\" to set the password)" : ""; message = g_strdup_printf("Can not open file: %s %s", error->message, message); notify(ERROR, message); g_free(message); g_error_free(error); return FALSE; } /* inotify */ if(Zathura.Inotify.fd != -1) { if((Zathura.Inotify.wd = inotify_add_watch(Zathura.Inotify.fd, file, IN_CLOSE_WRITE)) != -1) Zathura.Thread.inotify_thread = g_thread_create(watch_file, NULL, TRUE, NULL); } g_static_mutex_lock(&(Zathura.Lock.pdflib_lock)); Zathura.PDF.number_of_pages = poppler_document_get_n_pages(Zathura.PDF.document); g_static_mutex_unlock(&(Zathura.Lock.pdflib_lock)); Zathura.PDF.file = file; Zathura.PDF.scale = 100; Zathura.PDF.rotate = 0; Zathura.PDF.pages = malloc(Zathura.PDF.number_of_pages * sizeof(Page*)); Zathura.State.filename = g_markup_escape_text(file, -1); /* get pages */ g_static_mutex_lock(&(Zathura.Lock.pdflib_lock)); int i; for(i = 0; i < Zathura.PDF.number_of_pages; i++) { Zathura.PDF.pages[i] = malloc(sizeof(Page)); Zathura.PDF.pages[i]->page = poppler_document_get_page(Zathura.PDF.document, i); } g_static_mutex_unlock(&(Zathura.Lock.pdflib_lock)); /* start page */ int start_page = 0; /* bookmarks */ if(Zathura.Bookmarks.data) { /* get last opened page */ if(g_key_file_has_group(Zathura.Bookmarks.data, file)) if(g_key_file_has_key(Zathura.Bookmarks.data, file, BM_PAGE_ENTRY, NULL)) start_page = g_key_file_get_integer(Zathura.Bookmarks.data, file, BM_PAGE_ENTRY, NULL); /* open and read bookmark file */ gsize i = 0; gsize number_of_keys = 0; char** keys = g_key_file_get_keys(Zathura.Bookmarks.data, file, &number_of_keys, NULL); for(i = 0; i < number_of_keys; i++) { if(strcmp(keys[i], BM_PAGE_ENTRY)) { Zathura.Bookmarks.bookmarks = realloc(Zathura.Bookmarks.bookmarks, (Zathura.Bookmarks.number_of_bookmarks + 1) * sizeof(Bookmark)); Zathura.Bookmarks.bookmarks[Zathura.Bookmarks.number_of_bookmarks].id = keys[i]; Zathura.Bookmarks.bookmarks[Zathura.Bookmarks.number_of_bookmarks].page = g_key_file_get_integer(Zathura.Bookmarks.data, file, keys[i], NULL); Zathura.Bookmarks.number_of_bookmarks++; } } } /* show document */ set_page(start_page); update_status(); return TRUE; } void update_status() { /* update text */ gtk_label_set_markup((GtkLabel*) Zathura.Global.status_text, Zathura.State.filename); /* update state */ char* zoom_level = (Zathura.PDF.scale != 0) ? g_strdup_printf("%d%%", Zathura.PDF.scale) : ""; char* status_text = g_strdup_printf("%s %d%% %s", zoom_level, Zathura.State.scroll_percentage, Zathura.State.pages); gtk_label_set_markup((GtkLabel*) Zathura.Global.status_state, status_text); } void recalcRectangle(int page_id, PopplerRectangle* rectangle) { double page_width, page_height; double x1 = rectangle->x1; double x2 = rectangle->x2; double y1 = rectangle->y1; double y2 = rectangle->y2; g_static_mutex_lock(&(Zathura.Lock.pdflib_lock)); poppler_page_get_size(Zathura.PDF.pages[page_id]->page, &page_width, &page_height); g_static_mutex_unlock(&(Zathura.Lock.pdflib_lock)); double scale = ((double) Zathura.PDF.scale / 100.0); int rotate = Zathura.PDF.rotate; switch(rotate) { case 90: rectangle->x1 = y2 * scale; rectangle->y1 = x1 * scale; rectangle->x2 = y1 * scale; rectangle->y2 = x2 * scale; break; case 180: rectangle->x1 = (page_width - x2) * scale; rectangle->y1 = y2 * scale; rectangle->x2 = (page_width - x1) * scale; rectangle->y2 = y1 * scale; break; case 270: rectangle->x1 = (page_height - y1) * scale; rectangle->y1 = (page_width - x2) * scale; rectangle->x2 = (page_height - y2) * scale; rectangle->y2 = (page_width - x1) * scale; break; default: rectangle->x1 = x1 * scale; rectangle->y1 = (page_height - y1) * scale; rectangle->x2 = x2 * scale; rectangle->y2 = (page_height - y2) * scale; } } GtkEventBox* createCompletionRow(GtkBox* results, char* command, char* description, gboolean group) { GtkBox *col = GTK_BOX(gtk_hbox_new(FALSE, 0)); GtkEventBox *row = GTK_EVENT_BOX(gtk_event_box_new()); GtkLabel *show_command = GTK_LABEL(gtk_label_new(NULL)); GtkLabel *show_description = GTK_LABEL(gtk_label_new(NULL)); gtk_misc_set_alignment(GTK_MISC(show_command), 0.0, 0.0); gtk_misc_set_alignment(GTK_MISC(show_description), 0.0, 0.0); if(group) { gtk_misc_set_padding(GTK_MISC(show_command), 2.0, 4.0); gtk_misc_set_padding(GTK_MISC(show_description), 2.0, 4.0); } else { gtk_misc_set_padding(GTK_MISC(show_command), 1.0, 1.0); gtk_misc_set_padding(GTK_MISC(show_description), 1.0, 1.0); } gtk_label_set_use_markup(show_command, TRUE); gtk_label_set_use_markup(show_description, TRUE); gtk_label_set_markup(show_command, g_markup_printf_escaped(FORMAT_COMMAND, command ? command : "")); gtk_label_set_markup(show_description, g_markup_printf_escaped(FORMAT_DESCRIPTION, description ? description : "")); if(group) { gtk_widget_modify_fg(GTK_WIDGET(show_command), GTK_STATE_NORMAL, &(Zathura.Style.completion_g_fg)); gtk_widget_modify_fg(GTK_WIDGET(show_description), GTK_STATE_NORMAL, &(Zathura.Style.completion_g_fg)); gtk_widget_modify_bg(GTK_WIDGET(row), GTK_STATE_NORMAL, &(Zathura.Style.completion_g_bg)); } else { gtk_widget_modify_fg(GTK_WIDGET(show_command), GTK_STATE_NORMAL, &(Zathura.Style.completion_fg)); gtk_widget_modify_fg(GTK_WIDGET(show_description), GTK_STATE_NORMAL, &(Zathura.Style.completion_fg)); gtk_widget_modify_bg(GTK_WIDGET(row), GTK_STATE_NORMAL, &(Zathura.Style.completion_bg)); } gtk_widget_modify_font(GTK_WIDGET(show_command), Zathura.Style.font); gtk_widget_modify_font(GTK_WIDGET(show_description), Zathura.Style.font); gtk_box_pack_start(GTK_BOX(col), GTK_WIDGET(show_command), TRUE, TRUE, 2); gtk_box_pack_start(GTK_BOX(col), GTK_WIDGET(show_description), FALSE, FALSE, 2); gtk_container_add(GTK_CONTAINER(row), GTK_WIDGET(col)); gtk_box_pack_start(results, GTK_WIDGET(row), FALSE, FALSE, 0); return row; } void setCompletionRowColor(GtkBox* results, int mode, int id) { GtkEventBox *row = (GtkEventBox*) g_list_nth_data(gtk_container_get_children(GTK_CONTAINER(results)), id); if(row) { GtkBox *col = (GtkBox*) g_list_nth_data(gtk_container_get_children(GTK_CONTAINER(row)), 0); GtkLabel *cmd = (GtkLabel*) g_list_nth_data(gtk_container_get_children(GTK_CONTAINER(col)), 0); GtkLabel *cdesc = (GtkLabel*) g_list_nth_data(gtk_container_get_children(GTK_CONTAINER(col)), 1); if(mode == NORMAL) { gtk_widget_modify_fg(GTK_WIDGET(cmd), GTK_STATE_NORMAL, &(Zathura.Style.completion_fg)); gtk_widget_modify_fg(GTK_WIDGET(cdesc), GTK_STATE_NORMAL, &(Zathura.Style.completion_fg)); gtk_widget_modify_bg(GTK_WIDGET(row), GTK_STATE_NORMAL, &(Zathura.Style.completion_bg)); } else { gtk_widget_modify_fg(GTK_WIDGET(cmd), GTK_STATE_NORMAL, &(Zathura.Style.completion_hl_fg)); gtk_widget_modify_fg(GTK_WIDGET(cdesc), GTK_STATE_NORMAL, &(Zathura.Style.completion_hl_fg)); gtk_widget_modify_bg(GTK_WIDGET(row), GTK_STATE_NORMAL, &(Zathura.Style.completion_hl_bg)); } } } void set_page(int page) { if(page > Zathura.PDF.number_of_pages || page < 0) { notify(WARNING, "Could not open page"); return; } Zathura.PDF.page_number = page; Zathura.State.pages = g_strdup_printf("[%i/%i]", page + 1, Zathura.PDF.number_of_pages); Argument argument; argument.n = TOP; sc_scroll(&argument); switch_view(Zathura.UI.drawing_area); draw(page); } void switch_view(GtkWidget* widget) { GtkWidget* child = gtk_bin_get_child(GTK_BIN(Zathura.UI.viewport)); if(child) { g_object_ref(child); gtk_container_remove(GTK_CONTAINER(Zathura.UI.viewport), child); } gtk_container_add(GTK_CONTAINER(Zathura.UI.viewport), GTK_WIDGET(widget)); } /* thread implementation */ void* search(void* parameter) { Argument* argument = (Argument*) parameter; static char* search_item; static int direction; static int next_page = 0; int page_counter; GList* results = NULL; GList* list = NULL; if(argument->data) search_item = g_strdup((char*) argument->data); g_static_mutex_lock(&(Zathura.Lock.document_lock)); if(!Zathura.PDF.document || !search_item || !strlen(search_item)) { g_static_mutex_lock(&(Zathura.Lock.search_lock)); Zathura.Thread.search_thread_running = FALSE; g_static_mutex_unlock(&(Zathura.Lock.search_lock)); g_static_mutex_lock(&(Zathura.Lock.document_lock)); g_thread_exit(NULL); } g_static_mutex_unlock(&(Zathura.Lock.document_lock)); /* search document */ if(argument->n) direction = (argument->n == BACKWARD) ? -1 : 1; for(page_counter = 1; page_counter <= Zathura.PDF.number_of_pages; page_counter++) { g_static_mutex_lock(&(Zathura.Lock.search_lock)); if(Zathura.Thread.search_thread_running == FALSE) { g_static_mutex_unlock(&(Zathura.Lock.search_lock)); g_thread_exit(NULL); } g_static_mutex_unlock(&(Zathura.Lock.search_lock)); // probably should wrap this in a mutex, but I'm lazy next_page = (Zathura.PDF.number_of_pages + Zathura.PDF.page_number + page_counter * direction) % Zathura.PDF.number_of_pages; g_static_mutex_lock(&(Zathura.Lock.pdflib_lock)); PopplerPage* page = poppler_document_get_page(Zathura.PDF.document, next_page); g_static_mutex_unlock(&(Zathura.Lock.pdflib_lock)); if(!page) g_thread_exit(NULL); g_static_mutex_lock(&(Zathura.Lock.pdflib_lock)); results = poppler_page_find_text(page, search_item); g_static_mutex_unlock(&(Zathura.Lock.pdflib_lock)); if(results) break; } /* draw results */ if(results) { gdk_threads_enter(); set_page(next_page); for(list = results; list && list->data; list = g_list_next(list)) highlight_result(next_page, (PopplerRectangle*) list->data); gdk_threads_leave(); } else { printf("Nothing found for %s\n", search_item); } g_static_mutex_lock(&(Zathura.Lock.search_lock)); Zathura.Thread.search_thread_running = FALSE; g_static_mutex_unlock(&(Zathura.Lock.search_lock)); g_thread_exit(NULL); return NULL; // suppress GCC warnings about return value } void* watch_file(void* parameter) { int blen = sizeof(struct inotify_event); while(1) { /* wait for event */ char buf[blen]; if(read(Zathura.Inotify.fd, buf, blen) < 0) continue; /* process event */ struct inotify_event *event = (struct inotify_event*) buf; if(event->mask & IN_CLOSE_WRITE) { /* save old information */ char* path = Zathura.PDF.file ? strdup(Zathura.PDF.file) : NULL; char* password = Zathura.PDF.password ? strdup(Zathura.PDF.password) : NULL; int scale = Zathura.PDF.scale; int page = Zathura.PDF.page_number; /* reopen and restore settings */ cmd_close(0, NULL); open_file(path, password); Zathura.PDF.scale = scale; gdk_threads_enter(); draw(page); gdk_threads_leave(); break; } } g_thread_exit(NULL); return NULL; } /* shortcut implementation */ void sc_abort(Argument* argument) { /* Clear buffer */ if(Zathura.Global.buffer) { g_string_free(Zathura.Global.buffer, TRUE); Zathura.Global.buffer = NULL; gtk_label_set_markup((GtkLabel*) Zathura.Global.status_buffer, ""); } /* Set back to normal mode */ change_mode(NORMAL); switch_view(Zathura.UI.drawing_area); } void sc_adjust_window(Argument* argument) { if(!Zathura.PDF.document) return; GtkAdjustment* adjustment; double view_size; double page_width; double page_height; if(argument->n == ADJUST_WIDTH) adjustment = gtk_scrolled_window_get_vadjustment(Zathura.UI.view); else adjustment = gtk_scrolled_window_get_hadjustment(Zathura.UI.view); view_size = gtk_adjustment_get_page_size(adjustment); g_static_mutex_lock(&(Zathura.Lock.pdflib_lock)); poppler_page_get_size(Zathura.PDF.pages[Zathura.PDF.page_number]->page, &page_width, &page_height); g_static_mutex_unlock(&(Zathura.Lock.pdflib_lock)); if ((Zathura.PDF.rotate == 90) || (Zathura.PDF.rotate == 270)) { double swap = page_width; page_width = page_height; page_height = swap; } if(argument->n == ADJUST_WIDTH) Zathura.PDF.scale = (view_size / page_height) * 100; else Zathura.PDF.scale = (view_size / page_width) * 100; draw(Zathura.PDF.page_number); } void sc_change_buffer(Argument* argument) { if(!Zathura.Global.buffer) return; int buffer_length = Zathura.Global.buffer->len; if(argument->n == DELETE_LAST) { if((buffer_length - 1) == 0) { g_string_free(Zathura.Global.buffer, TRUE); Zathura.Global.buffer = NULL; gtk_label_set_markup((GtkLabel*) Zathura.Global.status_buffer, ""); } else { GString* temp = g_string_new_len(Zathura.Global.buffer->str, buffer_length - 1); g_string_free(Zathura.Global.buffer, TRUE); Zathura.Global.buffer = temp; gtk_label_set_markup((GtkLabel*) Zathura.Global.status_buffer, Zathura.Global.buffer->str); } } } void sc_change_mode(Argument* argument) { if(argument) change_mode(argument->n); } void sc_focus_inputbar(Argument* argument) { if(argument->data) { notify(DEFAULT, argument->data); gtk_widget_grab_focus(GTK_WIDGET(Zathura.UI.inputbar)); gtk_editable_set_position(GTK_EDITABLE(Zathura.UI.inputbar), -1); } } void sc_navigate(Argument* argument) { if(!Zathura.PDF.document) return; int number_of_pages = Zathura.PDF.number_of_pages; int new_page = Zathura.PDF.page_number; if(argument->n == NEXT) new_page = (new_page + number_of_pages + 1) % number_of_pages; else if(argument->n == PREVIOUS) new_page = (new_page + number_of_pages - 1) % number_of_pages; set_page(new_page); update_status(); } void sc_recolor(Argument* argument) { Zathura.Global.recolor = !Zathura.Global.recolor; draw(Zathura.PDF.page_number); } void sc_rotate(Argument* argument) { Zathura.PDF.rotate = (Zathura.PDF.rotate + 90) % 360; draw(Zathura.PDF.page_number); } void sc_scroll(Argument* argument) { GtkAdjustment* adjustment; if( (argument->n == LEFT) || (argument->n == RIGHT) ) adjustment = gtk_scrolled_window_get_hadjustment(Zathura.UI.view); else adjustment = gtk_scrolled_window_get_vadjustment(Zathura.UI.view); gdouble view_size = gtk_adjustment_get_page_size(adjustment); gdouble value = gtk_adjustment_get_value(adjustment); gdouble max = gtk_adjustment_get_upper(adjustment) - view_size; if( (argument->n == LEFT) || (argument->n == DOWN)) gtk_adjustment_set_value(adjustment, (value - SCROLL_STEP) < 0 ? 0 : (value - SCROLL_STEP)); else if (argument->n == TOP) gtk_adjustment_set_value(adjustment, 0); else if(argument->n == BOTTOM) gtk_adjustment_set_value(adjustment, max); else gtk_adjustment_set_value(adjustment, (value + SCROLL_STEP) > max ? max : (value + SCROLL_STEP)); int percentage = 100 * (value / max); percentage = (percentage < 0) ? 0 : ((percentage > 100) ? 100 : percentage); if( (argument->n != LEFT) && (argument->n != RIGHT) ) Zathura.State.scroll_percentage = percentage; update_status(); } void sc_search(Argument* argument) { g_static_mutex_lock(&(Zathura.Lock.search_lock)); if(Zathura.Thread.search_thread_running) { Zathura.Thread.search_thread_running = FALSE; g_static_mutex_unlock(&(Zathura.Lock.search_lock)); gdk_threads_leave(); g_thread_join(Zathura.Thread.search_thread); gdk_threads_enter(); g_static_mutex_lock(&(Zathura.Lock.search_lock)); } Zathura.Thread.search_thread_running = TRUE; Zathura.Thread.search_thread = g_thread_create(search, (gpointer) argument, TRUE, NULL); g_static_mutex_unlock(&(Zathura.Lock.search_lock)); } void sc_toggle_index(Argument* argument) { if(!Zathura.PDF.document) return; if(!Zathura.UI.index) { GtkWidget *treeview; GtkTreeModel *model; PopplerIndexIter *index_iter; GtkCellRenderer *renderer; GtkTreeSelection *selection; Zathura.UI.index = gtk_scrolled_window_new(NULL, NULL); gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(Zathura.UI.index), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); g_static_mutex_lock(&(Zathura.Lock.pdflib_lock)); index_iter = poppler_index_iter_new(Zathura.PDF.document); g_static_mutex_unlock(&(Zathura.Lock.pdflib_lock)); if(index_iter) { model = GTK_TREE_MODEL(gtk_tree_store_new(2, G_TYPE_STRING, G_TYPE_POINTER)); build_index(model, NULL, index_iter); g_static_mutex_lock(&(Zathura.Lock.pdflib_lock)); poppler_index_iter_free(index_iter); g_static_mutex_unlock(&(Zathura.Lock.pdflib_lock)); treeview = gtk_tree_view_new_with_model(model); g_object_unref(model); renderer = gtk_cell_renderer_text_new(); gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(treeview), 0, "Title", renderer, "markup", 0, NULL); gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(treeview), FALSE); selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(treeview)); g_signal_connect(G_OBJECT(selection), "changed", G_CALLBACK(cb_index_selection_changed), NULL); gtk_container_add(GTK_CONTAINER(Zathura.UI.index), treeview); gtk_widget_show(GTK_WIDGET(treeview)); } else { notify(WARNING, "This document does not contain any index"); Zathura.UI.index = NULL; return; } } static gboolean show = TRUE; if(show) { gtk_widget_show(Zathura.UI.index); switch_view(Zathura.UI.index); } else { switch_view(Zathura.UI.drawing_area); } show = !show; } void sc_toggle_inputbar(Argument* argument) { if(GTK_WIDGET_VISIBLE(GTK_WIDGET(Zathura.UI.inputbar))) gtk_widget_hide(GTK_WIDGET(Zathura.UI.inputbar)); else gtk_widget_show(GTK_WIDGET(Zathura.UI.inputbar)); } void sc_toggle_statusbar(Argument* argument) { if(GTK_WIDGET_VISIBLE(GTK_WIDGET(Zathura.UI.statusbar))) gtk_widget_hide(GTK_WIDGET(Zathura.UI.statusbar)); else gtk_widget_show(GTK_WIDGET(Zathura.UI.statusbar)); } void sc_quit(Argument* argument) { cb_destroy(NULL, NULL); } /* inputbar shortcut declarations */ void isc_abort(Argument* argument) { Argument arg = { HIDE }; isc_completion(&arg); notify(DEFAULT, ""); gtk_widget_grab_focus(GTK_WIDGET(Zathura.UI.view)); } void isc_command_history(Argument* argument) { static int current = 0; int length = g_list_length(Zathura.Global.history); if(length > 0) { if(argument->n == NEXT) current = (length + current + 1) % length; else current = (length + current - 1) % length; gchar* command = (gchar*) g_list_nth_data(Zathura.Global.history, current); notify(DEFAULT, command); gtk_widget_grab_focus(GTK_WIDGET(Zathura.UI.inputbar)); gtk_editable_set_position(GTK_EDITABLE(Zathura.UI.inputbar), -1); } } void isc_completion(Argument* argument) { gchar *input = gtk_editable_get_chars(GTK_EDITABLE(Zathura.UI.inputbar), 1, -1); gchar identifier = gtk_editable_get_chars(GTK_EDITABLE(Zathura.UI.inputbar), 0, 1)[0]; int length = strlen(input); if(!length && !identifier) return; /* get current information*/ char* first_space = strstr(input, " "); char* current_command; char* current_parameter; int current_command_length; int current_parameter_length; if(!first_space) { current_command = input; current_command_length = length; current_parameter = NULL; current_parameter_length = 0; } else { int offset = abs(input - first_space); current_command = g_strndup(input, offset); current_command_length = strlen(current_command); current_parameter = input + offset + 1; current_parameter_length = strlen(current_parameter); } /* if the identifier does not match the command sign and * the completion should not be hidden, leave this function */ if((identifier != ':') && (argument->n != HIDE)) return; /* static elements */ static GtkBox *results = NULL; static CompletionRow *rows = NULL; static int current_item = 0; static int n_items = 0; static char *previous_command = NULL; static char *previous_parameter = NULL; static int previous_id = 0; static int previous_length = 0; static gboolean command_mode = TRUE; /* delete old list iff * the completion should be hidden * the current command differs from the previous one * the current parameter differs from the previous one */ if( (argument->n == HIDE) || (current_parameter && previous_parameter && strcmp(current_parameter, previous_parameter)) || (current_command && previous_command && strcmp(current_command, previous_command)) || (previous_length != length) ) { if(results) gtk_widget_destroy(GTK_WIDGET(results)); results = NULL; if(rows) free(rows); rows = NULL; current_item = 0; n_items = 0; command_mode = TRUE; if(argument->n == HIDE) return; } /* create new list iff * there is no current list * the current command differs from the previous one * the current parameter differs from the previous one */ if( (!results) ) { results = GTK_BOX(gtk_vbox_new(FALSE, 0)); /* create list based on parameters iff * there is a current parameter given * there is an old list with commands * the current command does not differ from the previous one * the current command has an completion function */ if( (previous_command) && (current_parameter) && !strcmp(current_command, previous_command) ) { if(previous_id < 0 || !commands[previous_id].completion) return; Completion *result = commands[previous_id].completion(current_parameter); if(!result || !result->groups) return; command_mode = FALSE; CompletionGroup* group = NULL; CompletionElement* element = NULL; rows = malloc(sizeof(CompletionRow)); for(group = result->groups; group != NULL; group = group->next) { int group_elements = 0; for(element = group->elements; element != NULL; element = element->next) { if( (element->value) && (current_parameter_length <= strlen(element->value)) && !strncmp(current_parameter, element->value, current_parameter_length) ) { rows = realloc(rows, (n_items + 1) * sizeof(CompletionRow)); rows[n_items].command = element->value; rows[n_items].description = element->description; rows[n_items].command_id = previous_id; rows[n_items].is_group = FALSE; rows[n_items++].row = GTK_WIDGET(createCompletionRow(results, element->value, element->description, FALSE)); group_elements++; } } if(group->value && group_elements > 0) { rows = realloc(rows, (n_items + 1) * sizeof(CompletionRow)); rows[n_items].command = group->value; rows[n_items].description = NULL; rows[n_items].command_id = -1; rows[n_items].is_group = TRUE; rows[n_items].row = GTK_WIDGET(createCompletionRow(results, group->value, NULL, TRUE)); /* Swap group element with first element of the list */ CompletionRow temp = rows[n_items - group_elements]; gtk_box_reorder_child(results, rows[n_items].row, n_items - group_elements); rows[n_items - group_elements] = rows[n_items]; rows[n_items] = temp; n_items++; } } /* clean up */ group = result->groups; while(group) { element = group->elements; while(element) { CompletionElement* ne = element->next; free(element); element = ne; } CompletionGroup *ng = group->next; free(group); group = ng; } } /* create list based on commands */ else { int i = 0; command_mode = TRUE; rows = malloc(LENGTH(commands) * sizeof(CompletionRow)); for(i = 0; i < LENGTH(commands); i++) { int abbr_length = commands[i].abbr ? strlen(commands[i].abbr) : 0; int cmd_length = commands[i].command ? strlen(commands[i].command) : 0; /* add command to list iff * the current command would match the command * the current command would match the abbreviation */ if( ((current_command_length <= cmd_length) && !strncmp(current_command, commands[i].command, current_command_length)) || ((current_command_length <= abbr_length) && !strncmp(current_command, commands[i].abbr, current_command_length)) ) { rows[n_items].command = commands[i].command; rows[n_items].description = commands[i].description; rows[n_items].command_id = i; rows[n_items].is_group = FALSE; rows[n_items++].row = GTK_WIDGET(createCompletionRow(results, commands[i].command, commands[i].description, FALSE)); } } rows = realloc(rows, n_items * sizeof(CompletionRow)); } gtk_box_pack_start(Zathura.UI.box, GTK_WIDGET(results), FALSE, FALSE, 0); gtk_widget_show_all(GTK_WIDGET(Zathura.UI.window)); current_item = (argument->n == NEXT) ? -1 : 0; } /* update coloring iff * there is a list with items */ if( (results) && (n_items > 0) ) { setCompletionRowColor(results, NORMAL, current_item); char* temp; int i = 0, next_group = 0; for(i = 0; i < n_items; i++) { if(argument->n == NEXT || argument->n == NEXT_GROUP) current_item = (current_item + n_items + 1) % n_items; else if(argument->n == PREVIOUS || argument->n == PREVIOUS_GROUP) current_item = (current_item + n_items - 1) % n_items; if(rows[current_item].is_group) { if(!command_mode && (argument->n == NEXT_GROUP || argument->n == PREVIOUS_GROUP)) next_group = 1; continue; } else { if(!command_mode && (next_group == 0) && (argument->n == NEXT_GROUP || argument->n == PREVIOUS_GROUP)) continue; break; } } setCompletionRowColor(results, HIGHLIGHT, current_item); if(command_mode) { char* cp = (current_parameter) ? g_strconcat(" ", current_parameter, NULL) : 0; temp = g_strconcat(":", rows[current_item].command, cp, NULL); } else { temp = g_strconcat(":", previous_command, " ", rows[current_item].command, NULL); } gtk_entry_set_text(Zathura.UI.inputbar, temp); gtk_editable_set_position(GTK_EDITABLE(Zathura.UI.inputbar), -1); g_free(temp); previous_command = (command_mode) ? rows[current_item].command : current_command; previous_parameter = (command_mode) ? current_parameter : rows[current_item].command; previous_length = strlen(previous_command) + ((command_mode) ? (length - current_command_length) : (strlen(previous_parameter) + 1)); previous_id = rows[current_item].command_id; } } void isc_string_manipulation(Argument* argument) { if(argument->n == DELETE_LAST_WORD) { gchar *input = gtk_editable_get_chars(GTK_EDITABLE(Zathura.UI.inputbar), 0, -1); int length = strlen(input); int i = 0; for(i = length; i > 0; i--) { if( (input[i] == ' ') || (input[i] == '/') ) { if(i == (length - 1)) continue; i = (input[i] == ' ') ? (i - 1) : i; break; } } notify(DEFAULT, g_strndup(input, i + 1)); gtk_editable_set_position(GTK_EDITABLE(Zathura.UI.inputbar), -1); } } /* command implementation */ gboolean cmd_bookmark(int argc, char** argv) { if(!Zathura.PDF.document || argc < 1) return TRUE; /* get id */ int i; GString *id = g_string_new(""); for(i = 0; i < argc; i++) { if(i != 0) id = g_string_append_c(id, ' '); id = g_string_append(id, argv[i]); } /* check for existing bookmark to overwrite */ for(i = 0; i < Zathura.Bookmarks.number_of_bookmarks; i++) { if(!strcmp(id->str, Zathura.Bookmarks.bookmarks[i].id)) { Zathura.Bookmarks.bookmarks[i].page = Zathura.PDF.page_number; return TRUE; } } /* add new bookmark */ Zathura.Bookmarks.bookmarks = realloc(Zathura.Bookmarks.bookmarks, (Zathura.Bookmarks.number_of_bookmarks + 1) * sizeof(Bookmark)); Zathura.Bookmarks.bookmarks[Zathura.Bookmarks.number_of_bookmarks].id = id->str; Zathura.Bookmarks.bookmarks[Zathura.Bookmarks.number_of_bookmarks].page = Zathura.PDF.page_number; Zathura.Bookmarks.number_of_bookmarks++; return TRUE; } gboolean cmd_open_bookmark(int argc, char** argv) { if(!Zathura.PDF.document || argc < 1) return TRUE; /* get id */ int i; GString *id = g_string_new(""); for(i = 0; i < argc; i++) { if(i != 0) id = g_string_append_c(id, ' '); id = g_string_append(id, argv[i]); } /* find bookmark */ for(i = 0; i < Zathura.Bookmarks.number_of_bookmarks; i++) { if(!strcmp(id->str, Zathura.Bookmarks.bookmarks[i].id)) { set_page(Zathura.Bookmarks.bookmarks[i].page); return TRUE; } } notify(WARNING, "No matching bookmark found"); return FALSE; } gboolean cmd_close(int argc, char** argv) { if(!Zathura.PDF.document) { if(argc != -1) notify(ERROR, "No file has been opened"); return FALSE; } /* clean up pages */ int i; for(i = 0; i < Zathura.PDF.number_of_pages; i++) { Page* current_page = Zathura.PDF.pages[i]; g_object_unref(current_page->page); } /* save bookmarks */ if(Zathura.Bookmarks.data) { /* set current page */ g_key_file_set_integer(Zathura.Bookmarks.data, Zathura.PDF.file, BM_PAGE_ENTRY, Zathura.PDF.page_number); /* save bookmarks */ int i; for(i = 0; i < Zathura.Bookmarks.number_of_bookmarks; i++) g_key_file_set_integer(Zathura.Bookmarks.data, Zathura.PDF.file, Zathura.Bookmarks.bookmarks[i].id, Zathura.Bookmarks.bookmarks[i].page); /* convert file and save it */ gchar* bookmarks = g_key_file_to_data(Zathura.Bookmarks.data, NULL, NULL); g_file_set_contents(Zathura.Bookmarks.file, bookmarks, -1, NULL); g_free(bookmarks); } /* inotify */ if(Zathura.Inotify.fd != -1 && Zathura.Inotify.wd != -1) inotify_rm_watch(Zathura.Inotify.fd, Zathura.Inotify.wd); Zathura.Inotify.wd = -1; /*pthread_cancel(Zathura.Thread.inotify_thread);*/ /* reset values */ free(Zathura.PDF.pages); g_object_unref(Zathura.PDF.document); Zathura.State.pages = ""; Zathura.State.filename = (char*) DEFAULT_TEXT; g_static_mutex_lock(&(Zathura.Lock.document_lock)); Zathura.PDF.document = NULL; g_static_mutex_unlock(&(Zathura.Lock.document_lock)); Zathura.PDF.file = ""; Zathura.PDF.password = ""; Zathura.PDF.page_number = 0; Zathura.PDF.number_of_pages = 0; Zathura.PDF.scale = 0; Zathura.PDF.rotate = 0; /* destroy index */ if(Zathura.UI.index) { gtk_widget_destroy(Zathura.UI.index); Zathura.UI.index = NULL; } /* destroy information */ if(Zathura.UI.information) { gtk_widget_destroy(Zathura.UI.information); Zathura.UI.information = NULL; } /* free markers */ if(Zathura.Marker.markers) free(Zathura.Marker.markers); Zathura.Marker.number_of_markers = 0; Zathura.Marker.last = -1; update_status(); return TRUE; } gboolean cmd_delete_bookmark(int argc, char** argv) { if(!Zathura.PDF.document || argc < 1) return TRUE; /* get id */ int i; GString *id = g_string_new(""); for(i = 0; i < argc; i++) { if(i != 0) id = g_string_append_c(id, ' '); id = g_string_append(id, argv[i]); } /* check for bookmark to delete */ for(i = 0; i < Zathura.Bookmarks.number_of_bookmarks; i++) { if(!strcmp(id->str, Zathura.Bookmarks.bookmarks[i].id)) { /* update key file */ g_key_file_remove_key(Zathura.Bookmarks.data, Zathura.PDF.file, Zathura.Bookmarks.bookmarks[i].id, NULL); /* update bookmarks */ Zathura.Bookmarks.bookmarks[i].id = Zathura.Bookmarks.bookmarks[Zathura.Bookmarks.number_of_bookmarks - 1].id; Zathura.Bookmarks.bookmarks[i].page = Zathura.Bookmarks.bookmarks[Zathura.Bookmarks.number_of_bookmarks - 1].page; Zathura.Bookmarks.bookmarks = realloc(Zathura.Bookmarks.bookmarks, Zathura.Bookmarks.number_of_bookmarks * sizeof(Bookmark)); Zathura.Bookmarks.number_of_bookmarks--; return TRUE; } } return TRUE; } gboolean cmd_export(int argc, char** argv) { if(argc == 0 || !Zathura.PDF.document) return TRUE; if(argc < 2) { notify(WARNING, "No export path specified"); return FALSE; } /* export images */ if(!strcmp(argv[0], "images")) { int page_number; for(page_number = 0; page_number < Zathura.PDF.number_of_pages; page_number++) { GList *image_list; GList *images; cairo_surface_t *image; g_static_mutex_lock(&(Zathura.Lock.pdflib_lock)); image_list = poppler_page_get_image_mapping(Zathura.PDF.pages[page_number]->page); g_static_mutex_unlock(&(Zathura.Lock.pdflib_lock)); if(!g_list_length(image_list)) { notify(WARNING, "This document does not contain any images"); return FALSE; } for(images = image_list; images; images = g_list_next(images)) { PopplerImageMapping *image_mapping; PopplerRectangle image_field; gint image_id; char* file; char* filename; image_mapping = (PopplerImageMapping*) images->data; image_field = image_mapping->area; image_id = image_mapping->image_id; g_static_mutex_lock(&(Zathura.Lock.pdflib_lock)); image = poppler_page_get_image(Zathura.PDF.pages[page_number]->page, image_id); g_static_mutex_unlock(&(Zathura.Lock.pdflib_lock)); if(!image) continue; filename = g_strdup_printf("%s_p%i_i%i.png", Zathura.PDF.file, page_number + 1, image_id); if(argv[1][0] == '~') { file = malloc(((int) strlen(filename) + (int) strlen(argv[1]) + (int) strlen(getenv("HOME")) - 1) * sizeof(char)); file = g_strdup_printf("%s%s%s", getenv("HOME"), argv[1] + 1, filename); } else file = g_strdup_printf("%s%s", argv[1], filename); cairo_surface_write_to_png(image, file); g_free(file); } } } else if(!strcmp(argv[0], "attachments")) { g_static_mutex_lock(&(Zathura.Lock.pdflib_lock)); if(!poppler_document_has_attachments(Zathura.PDF.document)) { notify(WARNING, "PDF file has no attachments"); g_static_mutex_unlock(&(Zathura.Lock.pdflib_lock)); return FALSE; } GList *attachment_list = poppler_document_get_attachments(Zathura.PDF.document); g_static_mutex_unlock(&(Zathura.Lock.pdflib_lock)); GList *attachments; char *file; for(attachments = attachment_list; attachments; attachments = g_list_next(attachments)) { PopplerAttachment *attachment = (PopplerAttachment*) attachments->data; if(argv[1][0] == '~') { file = malloc(((int) strlen(attachment->name) + (int) strlen(argv[1]) + (int) strlen(getenv("HOME")) - 1) * sizeof(char)); file = g_strdup_printf("%s%s%s", getenv("HOME"), argv[1] + 1, attachment->name); } else file = g_strdup_printf("%s%s", argv[1], attachment->name); g_static_mutex_lock(&(Zathura.Lock.pdflib_lock)); poppler_attachment_save(attachment, file, NULL); g_static_mutex_unlock(&(Zathura.Lock.pdflib_lock)); g_free(file); } } return TRUE; } gboolean cmd_info(int argc, char** argv) { if(!Zathura.PDF.document) return TRUE; static gboolean visible = FALSE; if(!Zathura.UI.information) { GtkListStore *list; GtkTreeIter iter; GtkCellRenderer *renderer; GtkTreeSelection *selection; list = gtk_list_store_new(2, G_TYPE_STRING, G_TYPE_STRING); /* read document information */ gchar *title, *author; gchar *subject, *keywords; gchar *creator, *producer; GTime creation_date, modification_date; g_object_get(Zathura.PDF.document, "title", &title, "author", &author, "subject", &subject, "keywords", &keywords, "creator", &creator, "producer", &producer, "creation-date", &creation_date, "mod-date", &modification_date, NULL); /* append information to list */ gtk_list_store_append(list, &iter); gtk_list_store_set(list, &iter, 0, "Author", 1, author ? author : "", -1); gtk_list_store_append(list, &iter); gtk_list_store_set(list, &iter, 0, "Title", 1, title ? title : "", -1); gtk_list_store_append(list, &iter); gtk_list_store_set(list, &iter, 0, "Subject", 1, subject ? subject : "", -1); gtk_list_store_append(list, &iter); gtk_list_store_set(list, &iter, 0, "Keywords", 1, keywords ? keywords : "", -1); gtk_list_store_append(list, &iter); gtk_list_store_set(list, &iter, 0, "Creator", 1, creator ? creator : "", -1); gtk_list_store_append(list, &iter); gtk_list_store_set(list, &iter, 0, "Producer", 1, producer ? producer : "", -1); Zathura.UI.information = gtk_tree_view_new_with_model(GTK_TREE_MODEL(list)); renderer = gtk_cell_renderer_text_new(); gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(Zathura.UI.information), -1, "Name", renderer, "text", 0, NULL); gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(Zathura.UI.information), -1, "Value", renderer, "text", 1, NULL); selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(Zathura.UI.information)); gtk_tree_selection_set_mode(selection, GTK_SELECTION_SINGLE); gtk_widget_show_all(Zathura.UI.information); } if(!visible) switch_view(Zathura.UI.information); else switch_view(Zathura.UI.drawing_area); visible = !visible; return FALSE; } gboolean cmd_open(int argc, char** argv) { if(argc == 0 || strlen(argv[0]) == 0) return FALSE; /* assembly the arguments back to one string */ int i = 0; GString *filepath = g_string_new(""); for(i = 0; i < argc; i++) { if(i != 0) filepath = g_string_append_c(filepath, ' '); filepath = g_string_append(filepath, argv[i]); } return open_file(filepath->str, Zathura.PDF.password); } gboolean cmd_print(int argc, char** argv) { if(!Zathura.PDF.document) return TRUE; if(argc == 0) { notify(WARNING, "No printer specified"); return FALSE; } char* printer = argv[0]; char* sites = (argc == 2) ? argv[1] : g_strdup_printf("%i", Zathura.PDF.number_of_pages); char* print_command = g_strdup_printf(PRINT_COMMAND, printer, sites, Zathura.PDF.file); system(print_command); return TRUE; } gboolean cmd_rotate(int argc, char** argv) { return TRUE; } gboolean cmd_set(int argc, char** argv) { if(argc <= 0 || argc >= 3) return FALSE; int i; for(i = 0; i < LENGTH(settings); i++) { if(!strcmp(argv[0], settings[i].name)) { /* check var type */ if(settings[i].type == 'b') { gboolean *x = (gboolean*) (settings[i].variable); *x = !(*x); if(argv[1]) { if(!strcmp(argv[1], "false") || !strcmp(argv[1], "0")) *x = TRUE; else *x = FALSE; } } else if(settings[i].type == 'i') { if(argc != 2) return FALSE; int *x = (int*) (settings[i].variable); if(argv[1]) *x = atoi(argv[1]); } else if(settings[i].type == 's') { if(argc != 2) return FALSE; char **x = (char**) settings[i].variable; if(argv[1]) *x = argv[1]; } else if(settings[i].type == 'c') { if(argc != 2) return FALSE; char *x = (char*) (settings[i].variable); if(argv[1]) *x = argv[1][0]; } /* render */ if(settings[i].render) { if(!Zathura.PDF.document) return FALSE; draw(Zathura.PDF.page_number); } } } return TRUE; } gboolean cmd_quit(int argc, char** argv) { cb_destroy(NULL, NULL); return TRUE; } gboolean cmd_save(int argc, char** argv) { return TRUE; } /* completion command implementation */ Completion* cc_bookmark(char* input) { /* init completion group */ Completion *completion = malloc(sizeof(Completion)); CompletionGroup* group = malloc(sizeof(CompletionGroup)); group->value = NULL; group->next = NULL; group->elements = NULL; completion->groups = group; CompletionElement *last_element = NULL; int element_counter = 0; int i = 0; int input_length = input ? strlen(input) : 0; for(i = 0; i < Zathura.Bookmarks.number_of_bookmarks; i++) { if( (input_length <= strlen(Zathura.Bookmarks.bookmarks[i].id)) && !strncmp(input, Zathura.Bookmarks.bookmarks[i].id, input_length) ) { CompletionElement* el = malloc(sizeof(CompletionElement)); el->value = Zathura.Bookmarks.bookmarks[i].id; el->description = g_strdup_printf("Page: %d", Zathura.Bookmarks.bookmarks[i].page); el->next = NULL; if(element_counter++ != 0) last_element->next = el; else group->elements = el; last_element = el; } } return completion; } Completion* cc_export(char* input) { /* init completion group */ Completion *completion = malloc(sizeof(Completion)); CompletionGroup* group = malloc(sizeof(CompletionGroup)); group->value = NULL; group->next = NULL; group->elements = NULL; completion->groups = group; /* export images */ CompletionElement *export_images = malloc(sizeof(CompletionElement)); export_images->value = "images"; export_images->description = "Export images"; export_images->next = NULL; /* export attachmants */ CompletionElement *export_attachments = malloc(sizeof(CompletionElement)); export_attachments->value = "attachments"; export_attachments->description = "Export attachments"; export_attachments->next = NULL; /* connect */ group->elements = export_attachments; export_attachments->next = export_images; return completion; } Completion* cc_open(char* input) { /* init completion group */ Completion *completion = malloc(sizeof(Completion)); CompletionGroup* group = malloc(sizeof(CompletionGroup)); group->value = NULL; group->next = NULL; group->elements = NULL; completion->groups = group; /* read dir */ char* path = "/"; char* file = ""; int file_length = 0; /* ~ */ if(input[0] == '~') { char *file = g_strdup_printf(":open %s/%s", getenv("HOME"), input + 1); gtk_entry_set_text(Zathura.UI.inputbar, file); gtk_editable_set_position(GTK_EDITABLE(Zathura.UI.inputbar), -1); return NULL; } /* parse input string */ if(input && strlen(input) > 0) { char* path_temp = dirname(strdup(input)); char* file_temp = basename(strdup(input)); char last_char = input[strlen(input) - 1]; if( !strcmp(path_temp, "/") && !strcmp(file_temp, "/") ) file = ""; else if( !strcmp(path_temp, "/") && strcmp(file_temp, "/") && last_char != '/') file = file_temp; else if( !strcmp(path_temp, "/") && strcmp(file_temp, "/") && last_char == '/') path = g_strdup_printf("/%s/", file_temp); else if(last_char == '/') path = input; else { path = g_strdup_printf("%s/", path_temp); file = file_temp; } } file_length = strlen(file); /* open directory */ GDir* dir = g_dir_open(path, 0, NULL); if(!dir) return NULL; /* create element list */ CompletionElement *last_element = NULL; char* name = NULL; int element_counter = 0; while((name = (char*) g_dir_read_name(dir)) != NULL) { char* d_name = g_filename_display_name(name); int d_length = strlen(d_name); if( ((file_length <= d_length) && !strncmp(file, d_name, file_length)) || (file_length == 0) ) { CompletionElement* el = malloc(sizeof(CompletionElement)); el->value = g_strdup_printf("%s%s", path, d_name); el->description = NULL; el->next = NULL; if(element_counter++ != 0) last_element->next = el; else group->elements = el; last_element = el; } } g_dir_close(dir); return completion; } Completion* cc_print(char* input) { /* init completion group */ Completion *completion = malloc(sizeof(Completion)); CompletionGroup* group = malloc(sizeof(CompletionGroup)); group->value = NULL; group->next = NULL; group->elements = NULL; completion->groups = group; CompletionElement *last_element = NULL; int element_counter = 0; int input_length = input ? strlen(input) : 0; /* read printers */ char *current_line = NULL, current_char; int count = 0; FILE *fp; fp = popen(LIST_PRINTER_COMMAND, "r"); if(!fp) { free(completion); free(group); return NULL; } while((current_char = fgetc(fp)) != EOF) { if(!current_line) current_line = malloc(sizeof(char)); current_line = realloc(current_line, (count + 1) * sizeof(char)); if(current_char != '\n') current_line[count++] = current_char; else { current_line[count] = '\0'; int line_length = strlen(current_line); if( (input_length <= line_length) || (!strncmp(input, current_line, input_length)) ) { CompletionElement* el = malloc(sizeof(CompletionElement)); el->value = g_strdup(current_line); el->description = NULL; el->next = NULL; if(element_counter++ != 0) last_element->next = el; else group->elements = el; last_element = el; } free(current_line); current_line = NULL; count = 0; } } pclose(fp); return completion; } Completion* cc_set(char* input) { /* init completion group */ Completion *completion = malloc(sizeof(Completion)); CompletionGroup* group = malloc(sizeof(CompletionGroup)); group->value = NULL; group->next = NULL; group->elements = NULL; completion->groups = group; CompletionElement *last_element = NULL; int element_counter = 0; int i = 0; int input_length = input ? strlen(input) : 0; for(i = 0; i < LENGTH(settings); i++) { if( (input_length <= strlen(settings[i].name)) && !strncmp(input, settings[i].name, input_length) ) { CompletionElement* el = malloc(sizeof(CompletionElement)); el->value = settings[i].name; el->description = settings[i].description; el->next = NULL; if(element_counter++ != 0) last_element->next = el; else group->elements = el; last_element = el; } } return completion; } /* buffer command implementation */ void bcmd_goto(char* buffer, Argument* argument) { int b_length = strlen(buffer); if(b_length < 1) return; if(!strcmp(buffer, "gg")) set_page(0); else if(!strcmp(buffer, "G")) set_page(Zathura.PDF.number_of_pages - 1); else set_page(atoi(g_strndup(buffer, b_length - 1)) - 1); update_status(); } void bcmd_scroll(char* buffer, Argument* argument) { int b_length = strlen(buffer); if(b_length < 1) return; int percentage = atoi(g_strndup(buffer, b_length - 1)); percentage = (percentage < 0) ? 0 : ((percentage > 100) ? 100 : percentage); GtkAdjustment *adjustment = gtk_scrolled_window_get_vadjustment(Zathura.UI.view); gdouble view_size = gtk_adjustment_get_page_size(adjustment); gdouble max = gtk_adjustment_get_upper(adjustment) - view_size; gdouble nvalue = (percentage * max) / 100; Zathura.State.scroll_percentage = percentage; gtk_adjustment_set_value(adjustment, nvalue); update_status(); } void bcmd_zoom(char* buffer, Argument* argument) { if(argument->n == ZOOM_IN) { if((Zathura.PDF.scale + ZOOM_STEP) <= ZOOM_MAX) Zathura.PDF.scale += ZOOM_STEP; else Zathura.PDF.scale = ZOOM_MAX; } else if(argument->n == ZOOM_OUT) { if((Zathura.PDF.scale - ZOOM_STEP) >= ZOOM_MIN) Zathura.PDF.scale -= ZOOM_STEP; else Zathura.PDF.scale = ZOOM_MIN; } else if(argument->n == ZOOM_SPECIFIC) { int b_length = strlen(buffer); if(b_length < 1) return; int value = atoi(g_strndup(buffer, b_length - 1)); if(value <= ZOOM_MIN) Zathura.PDF.scale = ZOOM_MIN; else if(value >= ZOOM_MAX) Zathura.PDF.scale = ZOOM_MAX; else Zathura.PDF.scale = value; } else Zathura.PDF.scale = 100; draw(Zathura.PDF.page_number); update_status(); } /* special command implementation */ gboolean scmd_search(char* input, Argument* argument) { if(!strlen(input)) return TRUE; argument->data = input; sc_search(argument); return TRUE; } /* callback implementation */ gboolean cb_destroy(GtkWidget* widget, gpointer data) { pango_font_description_free(Zathura.Style.font); if(Zathura.PDF.document) cmd_close(0, NULL); /* clean up other variables */ g_free(Zathura.Bookmarks.file); /* inotify */ if(Zathura.Inotify.fd != -1) close(Zathura.Inotify.fd); gtk_main_quit(); return TRUE; } gboolean cb_draw(GtkWidget* widget, GdkEventExpose* expose, gpointer data) { int page_id = Zathura.PDF.page_number; if(page_id < 0 || page_id > Zathura.PDF.number_of_pages) return FALSE; gdk_window_clear(widget->window); cairo_t *cairo = gdk_cairo_create(widget->window); double page_width, page_height, width, height; double scale = ((double) Zathura.PDF.scale / 100.0); g_static_mutex_lock(&(Zathura.Lock.pdflib_lock)); poppler_page_get_size(Zathura.PDF.pages[page_id]->page, &page_width, &page_height); g_static_mutex_unlock(&(Zathura.Lock.pdflib_lock)); if(Zathura.PDF.rotate == 0 || Zathura.PDF.rotate == 180) { width = page_width * scale; height = page_height * scale; } else { width = page_height * scale; height = page_width * scale; } int window_x, window_y; gdk_drawable_get_size(widget->window, &window_x, &window_y); int offset_x, offset_y; if (window_x > width) offset_x = (window_x - width) / 2; else offset_x = 0; if (window_y > height) offset_y = (window_y - height) / 2; else offset_y = 0; cairo_set_source_surface(cairo, Zathura.PDF.surface, offset_x, offset_y); cairo_paint(cairo); cairo_destroy(cairo); return TRUE; } gboolean cb_view_kb_pressed(GtkWidget *widget, GdkEventKey *event, gpointer data) { int i; for(i = 0; i < LENGTH(shortcuts); i++) { if (event->keyval == shortcuts[i].key && (((event->state & shortcuts[i].mask) == shortcuts[i].mask) || shortcuts[i].mask == 0) && (Zathura.Global.mode == shortcuts[i].mode || shortcuts[i].mode == -1)) { shortcuts[i].function(&(shortcuts[i].argument)); return TRUE; } } if(Zathura.Global.mode == ADD_MARKER) { add_marker(event->keyval); change_mode(NORMAL); return TRUE; } else if(Zathura.Global.mode == EVAL_MARKER) { eval_marker(event->keyval); change_mode(NORMAL); return TRUE; } /* append only numbers and characters to buffer */ if( (event->keyval >= 0x21) && (event->keyval <= 0x7A)) { if(!Zathura.Global.buffer) Zathura.Global.buffer = g_string_new(""); Zathura.Global.buffer = g_string_append_c(Zathura.Global.buffer, event->keyval); gtk_label_set_markup((GtkLabel*) Zathura.Global.status_buffer, Zathura.Global.buffer->str); } /* search buffer commands */ if(Zathura.Global.buffer) { for(i = 0; i < LENGTH(buffer_commands); i++) { regex_t regex; int status; regcomp(®ex, buffer_commands[i].regex, REG_EXTENDED); status = regexec(®ex, Zathura.Global.buffer->str, (size_t) 0, NULL, 0); regfree(®ex); if(status == 0) { buffer_commands[i].function(Zathura.Global.buffer->str, &(buffer_commands[i].argument)); g_string_free(Zathura.Global.buffer, TRUE); Zathura.Global.buffer = NULL; gtk_label_set_markup((GtkLabel*) Zathura.Global.status_buffer, ""); return TRUE; } } } return FALSE; } gboolean cb_index_selection_changed(GtkTreeSelection* treeselection, GtkWidget* action_view) { GtkTreeModel *model; GtkTreeIter iter; if(gtk_tree_selection_get_selected(treeselection, &model, &iter)) { PopplerAction* action; PopplerDest* destination; gtk_tree_model_get(model, &iter, 1, &action, -1); if(action->type == POPPLER_ACTION_GOTO_DEST) { destination = action->goto_dest.dest; int page_number = destination->page_num; if(page_number >= 0 && page_number <= Zathura.PDF.number_of_pages) { set_page(page_number - 1); update_status(); } } } return TRUE; } gboolean cb_inputbar_kb_pressed(GtkWidget *widget, GdkEventKey *event, gpointer data) { int i; /* inputbar shortcuts */ for(i = 0; i < LENGTH(inputbar_shortcuts); i++) { if(event->keyval == inputbar_shortcuts[i].key && (((event->state & inputbar_shortcuts[i].mask) == inputbar_shortcuts[i].mask) || inputbar_shortcuts[i].mask == 0)) { inputbar_shortcuts[i].function(&(inputbar_shortcuts[i].argument)); return TRUE; } } /* special commands */ char identifier = gtk_editable_get_chars(GTK_EDITABLE(Zathura.UI.inputbar), 0, 1)[0]; for(i = 0; i < LENGTH(special_commands); i++) { if((identifier == special_commands[i].identifier) && (special_commands[i].always == 1)) { gchar *input = gtk_editable_get_chars(GTK_EDITABLE(Zathura.UI.inputbar), 1, -1); special_commands[i].function(input, &(special_commands[i].argument)); return FALSE; } } return FALSE; } gboolean cb_inputbar_activate(GtkEntry* entry, gpointer data) { gchar *input = gtk_editable_get_chars(GTK_EDITABLE(entry), 1, -1); gchar **tokens = g_strsplit(input, " ", -1); gchar *command = tokens[0]; int length = g_strv_length(tokens); int i = 0; gboolean retv = FALSE; gboolean succ = FALSE; /* no input */ if(length < 1) { isc_abort(NULL); return FALSE; } /* append input to the command history */ Zathura.Global.history = g_list_append(Zathura.Global.history, g_strdup(gtk_entry_get_text(entry))); /* special commands */ char identifier = gtk_editable_get_chars(GTK_EDITABLE(entry), 0, 1)[0]; for(i = 0; i < LENGTH(special_commands); i++) { if(identifier == special_commands[i].identifier) { retv = special_commands[i].function(input, &(special_commands[i].argument)); if(retv) isc_abort(NULL); gtk_widget_grab_focus(GTK_WIDGET(Zathura.UI.view)); return TRUE; } } /* search commands */ for(i = 0; i < LENGTH(commands); i++) { if((g_strcmp0(command, commands[i].command) == 0) || (g_strcmp0(command, commands[i].abbr) == 0)) { retv = commands[i].function(length - 1, tokens + 1); succ = TRUE; break; } } if(retv) isc_abort(NULL); if(!succ) notify(ERROR, "Unknown command."); Argument arg = { HIDE }; isc_completion(&arg); gtk_widget_grab_focus(GTK_WIDGET(Zathura.UI.view)); return TRUE; } /* main function */ int main(int argc, char* argv[]) { g_thread_init(NULL); gdk_threads_init(); gtk_init(&argc, &argv); init_zathura(); init_directories(); update_status(); if(argc >= 2) open_file(argv[1], (argc == 3) ? argv[2] : NULL); gtk_widget_show_all(GTK_WIDGET(Zathura.UI.window)); Argument arg; arg.n = ADJUST_OPEN; sc_adjust_window(&arg); gdk_threads_enter(); gtk_main(); gdk_threads_leave(); return 0; }