Compare commits

..

2 Commits

Author SHA1 Message Date
c39ac1a9da
Enable viewing all X11 cursor sizes 2021-11-27 18:59:05 +01:00
085f2d7eef
Use GFile a bit more 2021-11-27 02:34:24 +01:00
5 changed files with 131 additions and 73 deletions

View File

@ -64,7 +64,7 @@ static const double g_permitted_width_multiplier = 2;
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
struct entry { struct entry {
char *filename; ///< Absolute path char *uri; ///< GIO URI
cairo_surface_t *thumbnail; ///< Prescaled thumbnail cairo_surface_t *thumbnail; ///< Prescaled thumbnail
GIcon *icon; ///< If no thumbnail, use this icon GIcon *icon; ///< If no thumbnail, use this icon
}; };
@ -72,7 +72,7 @@ struct entry {
static void static void
entry_free(Entry *self) entry_free(Entry *self)
{ {
g_free(self->filename); g_free(self->uri);
g_clear_pointer(&self->thumbnail, cairo_surface_destroy); g_clear_pointer(&self->thumbnail, cairo_surface_destroy);
g_clear_object(&self->icon); g_clear_object(&self->icon);
} }
@ -351,15 +351,15 @@ entry_add_thumbnail(gpointer data, gpointer user_data)
g_clear_pointer(&self->thumbnail, cairo_surface_destroy); g_clear_pointer(&self->thumbnail, cairo_surface_destroy);
FastivBrowser *browser = FASTIV_BROWSER(user_data); FastivBrowser *browser = FASTIV_BROWSER(user_data);
GFile *file = g_file_new_for_uri(self->uri);
self->thumbnail = rescale_thumbnail( self->thumbnail = rescale_thumbnail(
fastiv_io_lookup_thumbnail(self->filename, browser->item_size), fastiv_io_lookup_thumbnail(file, browser->item_size),
browser->item_height); browser->item_height);
if (self->thumbnail) if (self->thumbnail)
return; goto out;
// Fall back to symbolic icons, though there's only so much we can do // Fall back to symbolic icons, though there's only so much we can do
// in parallel--GTK+ isn't thread-safe. // in parallel--GTK+ isn't thread-safe.
GFile *file = g_file_new_for_path(self->filename);
GFileInfo *info = g_file_query_info(file, GFileInfo *info = g_file_query_info(file,
G_FILE_ATTRIBUTE_STANDARD_NAME G_FILE_ATTRIBUTE_STANDARD_NAME
"," G_FILE_ATTRIBUTE_STANDARD_SYMBOLIC_ICON, "," G_FILE_ATTRIBUTE_STANDARD_SYMBOLIC_ICON,
@ -371,6 +371,8 @@ entry_add_thumbnail(gpointer data, gpointer user_data)
self->icon = g_object_ref(icon); self->icon = g_object_ref(icon);
g_object_unref(info); g_object_unref(info);
} }
out:
g_object_unref(file);
} }
static void static void
@ -521,9 +523,9 @@ destroy_widget_idle_source_func(GtkWidget *widget)
} }
static void static void
show_context_menu(GtkWidget *widget, const char *filename) show_context_menu(GtkWidget *widget, const char *uri)
{ {
GFile *file = g_file_new_for_path(filename); GFile *file = g_file_new_for_uri(uri);
GFileInfo *info = g_file_query_info(file, GFileInfo *info = g_file_query_info(file,
G_FILE_ATTRIBUTE_STANDARD_NAME G_FILE_ATTRIBUTE_STANDARD_NAME
"," G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE, "," G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE,
@ -778,8 +780,10 @@ fastiv_browser_draw(GtkWidget *widget, cairo_t *cr)
static gboolean static gboolean
open_entry(GtkWidget *self, const Entry *entry, gboolean new_window) open_entry(GtkWidget *self, const Entry *entry, gboolean new_window)
{ {
g_signal_emit(self, browser_signals[ITEM_ACTIVATED], 0, entry->filename, GFile *location = g_file_new_for_uri(entry->uri);
g_signal_emit(self, browser_signals[ITEM_ACTIVATED], 0, location,
new_window ? GTK_PLACES_OPEN_NEW_WINDOW : GTK_PLACES_OPEN_NORMAL); new_window ? GTK_PLACES_OPEN_NEW_WINDOW : GTK_PLACES_OPEN_NORMAL);
g_object_unref(location);
return TRUE; return TRUE;
} }
@ -817,7 +821,7 @@ fastiv_browser_button_press_event(GtkWidget *widget, GdkEventButton *event)
// On X11, after closing the menu, the pointer otherwise remains, // On X11, after closing the menu, the pointer otherwise remains,
// no matter what its new location is. // no matter what its new location is.
gdk_window_set_cursor(gtk_widget_get_window(widget), NULL); gdk_window_set_cursor(gtk_widget_get_window(widget), NULL);
show_context_menu(widget, entry->filename); show_context_menu(widget, entry->uri);
return TRUE; return TRUE;
default: default:
return FALSE; return FALSE;
@ -871,7 +875,7 @@ fastiv_browser_query_tooltip(GtkWidget *widget, gint x, gint y,
if (!entry) if (!entry)
return FALSE; return FALSE;
GFile *file = g_file_new_for_path(entry->filename); GFile *file = g_file_new_for_uri(entry->uri);
GFileInfo *info = g_file_query_info(file, GFileInfo *info = g_file_query_info(file,
G_FILE_ATTRIBUTE_STANDARD_NAME G_FILE_ATTRIBUTE_STANDARD_NAME
"," G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME, "," G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME,
@ -967,7 +971,7 @@ fastiv_browser_class_init(FastivBrowserClass *klass)
browser_signals[ITEM_ACTIVATED] = g_signal_new("item-activated", browser_signals[ITEM_ACTIVATED] = g_signal_new("item-activated",
G_TYPE_FROM_CLASS(klass), 0, 0, NULL, NULL, NULL, G_TYPE_FROM_CLASS(klass), 0, 0, NULL, NULL, NULL,
G_TYPE_NONE, 2, G_TYPE_STRING, GTK_TYPE_PLACES_OPEN_FLAGS); G_TYPE_NONE, 2, G_TYPE_FILE, GTK_TYPE_PLACES_OPEN_FLAGS);
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass); GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass);
widget_class->get_request_mode = fastiv_browser_get_request_mode; widget_class->get_request_mode = fastiv_browser_get_request_mode;
@ -1020,8 +1024,8 @@ entry_compare(gconstpointer a, gconstpointer b)
{ {
const Entry *entry1 = a; const Entry *entry1 = a;
const Entry *entry2 = b; const Entry *entry2 = b;
GFile *location1 = g_file_new_for_path(entry1->filename); GFile *location1 = g_file_new_for_uri(entry1->uri);
GFile *location2 = g_file_new_for_path(entry2->filename); GFile *location2 = g_file_new_for_uri(entry2->uri);
gint result = fastiv_io_filecmp(location1, location2); gint result = fastiv_io_filecmp(location1, location2);
g_object_unref(location1); g_object_unref(location1);
g_object_unref(location2); g_object_unref(location2);
@ -1055,7 +1059,7 @@ fastiv_browser_load(
continue; continue;
g_array_append_val(self->entries, g_array_append_val(self->entries,
((Entry) {.thumbnail = NULL, .filename = g_file_get_path(child)})); ((Entry) {.thumbnail = NULL, .uri = g_file_get_uri(child)}));
} }
g_object_unref(enumerator); g_object_unref(enumerator);

View File

@ -955,39 +955,58 @@ open_xcursor(const gchar *data, gsize len, GError **error)
return NULL; return NULL;
} }
// Let's just arrange the pixel data in a recording surface. // Interpret cursors as animated pages.
static cairo_user_data_key_t key = {}; cairo_surface_t *pages = NULL, *frames_head = NULL, *frames_tail = NULL;
cairo_surface_t *recording =
cairo_recording_surface_create(CAIRO_CONTENT_COLOR_ALPHA, NULL);
cairo_surface_set_user_data(
recording, &key, images, (cairo_destroy_func_t) XcursorImagesDestroy);
cairo_t *cr = cairo_create(recording);
// Unpack horizontally by animation, vertically by size. // XXX: Assuming that all "nominal sizes" have the same dimensions.
XcursorDim last_nominal = -1; XcursorDim last_nominal = -1;
int x = 0, y = 0, row_height = 0;
for (int i = 0; i < images->nimage; i++) { for (int i = 0; i < images->nimage; i++) {
XcursorImage *image = images->images[i]; XcursorImage *image = images->images[i];
if (image->size != last_nominal) {
x = 0;
y += row_height;
row_height = 0;
last_nominal = image->size;
}
// The library automatically byte swaps in _XcursorReadImage(). // The library automatically byte swaps in _XcursorReadImage().
cairo_surface_t *source = cairo_image_surface_create_for_data( cairo_surface_t *surface = cairo_image_surface_create_for_data(
(unsigned char *) image->pixels, CAIRO_FORMAT_ARGB32, (unsigned char *) image->pixels, CAIRO_FORMAT_ARGB32,
image->width, image->height, image->width * sizeof *image->pixels); image->width, image->height, image->width * sizeof *image->pixels);
cairo_set_source_surface(cr, source, x, y); cairo_surface_set_user_data(surface, &fastiv_io_key_frame_duration,
cairo_surface_destroy(source); (void *) (intptr_t) image->delay, NULL);
cairo_paint(cr);
x += image->width; if (pages && image->size == last_nominal) {
row_height = MAX(row_height, (int) image->height); cairo_surface_set_user_data(surface,
&fastiv_io_key_frame_previous, frames_tail, NULL);
cairo_surface_set_user_data(frames_tail,
&fastiv_io_key_frame_next, surface,
(cairo_destroy_func_t) cairo_surface_destroy);
} else if (frames_head) {
cairo_surface_set_user_data(frames_head,
&fastiv_io_key_frame_previous, frames_tail, NULL);
cairo_surface_set_user_data(frames_head,
&fastiv_io_key_page_next, surface,
(cairo_destroy_func_t) cairo_surface_destroy);
cairo_surface_set_user_data(surface,
&fastiv_io_key_page_previous, frames_head, NULL);
frames_head = surface;
} else {
pages = frames_head = surface;
}
frames_tail = surface;
last_nominal = image->size;
} }
cairo_destroy(cr); if (!pages) {
return recording; XcursorImagesDestroy(images);
return NULL;
}
// Wrap around animations in the last page.
cairo_surface_set_user_data(
frames_head, &fastiv_io_key_frame_previous, frames_tail, NULL);
// There is no need to copy data, assign it to the surface.
static cairo_user_data_key_t key = {};
cairo_surface_set_user_data(
pages, &key, images, (cairo_destroy_func_t) XcursorImagesDestroy);
return pages;
} }
#endif // HAVE_XCURSOR -------------------------------------------------------- #endif // HAVE_XCURSOR --------------------------------------------------------
@ -1041,6 +1060,9 @@ cairo_user_data_key_t fastiv_io_key_frame_previous;
cairo_user_data_key_t fastiv_io_key_frame_duration; cairo_user_data_key_t fastiv_io_key_frame_duration;
cairo_user_data_key_t fastiv_io_key_loops; cairo_user_data_key_t fastiv_io_key_loops;
cairo_user_data_key_t fastiv_io_key_page_next;
cairo_user_data_key_t fastiv_io_key_page_previous;
cairo_surface_t * cairo_surface_t *
fastiv_io_open(const gchar *path, GError **error) fastiv_io_open(const gchar *path, GError **error)
{ {
@ -1365,21 +1387,23 @@ fail_init:
} }
cairo_surface_t * cairo_surface_t *
fastiv_io_lookup_thumbnail(const gchar *target, FastivIoThumbnailSize size) fastiv_io_lookup_thumbnail(GFile *target, FastivIoThumbnailSize size)
{ {
g_return_val_if_fail(size >= FASTIV_IO_THUMBNAIL_SIZE_MIN && g_return_val_if_fail(size >= FASTIV_IO_THUMBNAIL_SIZE_MIN &&
size <= FASTIV_IO_THUMBNAIL_SIZE_MAX, NULL); size <= FASTIV_IO_THUMBNAIL_SIZE_MAX, NULL);
GStatBuf st; // Local files only, at least for now.
if (g_stat(target, &st)) gchar *path = g_file_get_path(target);
if (!path)
return NULL; return NULL;
// TODO(p): Consider making the `target` an absolute path, if it isn't. GStatBuf st = {};
// Or maybe let it fail, and document the requirement. int err = g_stat(path, &st);
gchar *uri = g_filename_to_uri(target, NULL, NULL); g_free(path);
if (!uri) if (err)
return NULL; return NULL;
gchar *uri = g_file_get_uri(target);
gchar *sum = g_compute_checksum_for_string(G_CHECKSUM_MD5, uri, -1); gchar *sum = g_compute_checksum_for_string(G_CHECKSUM_MD5, uri, -1);
gchar *cache_dir = get_xdg_home_dir("XDG_CACHE_HOME", ".cache"); gchar *cache_dir = get_xdg_home_dir("XDG_CACHE_HOME", ".cache");

View File

@ -46,6 +46,13 @@ extern cairo_user_data_key_t fastiv_io_key_frame_duration;
/// How many times to repeat the animation, or zero for +inf, as a uintptr_t. /// How many times to repeat the animation, or zero for +inf, as a uintptr_t.
extern cairo_user_data_key_t fastiv_io_key_loops; extern cairo_user_data_key_t fastiv_io_key_loops;
/// The first frame of the next page, as a surface, in a chain.
/// There is no wrap-around.
extern cairo_user_data_key_t fastiv_io_key_page_next;
/// The first frame of the previous page, as a surface, in a chain.
/// There is no wrap-around. This is a weak pointer.
extern cairo_user_data_key_t fastiv_io_key_page_previous;
cairo_surface_t *fastiv_io_open(const gchar *path, GError **error); cairo_surface_t *fastiv_io_open(const gchar *path, GError **error);
cairo_surface_t *fastiv_io_open_from_data( cairo_surface_t *fastiv_io_open_from_data(
const char *data, size_t len, const gchar *path, GError **error); const char *data, size_t len, const gchar *path, GError **error);
@ -99,4 +106,4 @@ extern FastivIoThumbnailSizeInfo
fastiv_io_thumbnail_sizes[FASTIV_IO_THUMBNAIL_SIZE_COUNT]; fastiv_io_thumbnail_sizes[FASTIV_IO_THUMBNAIL_SIZE_COUNT];
cairo_surface_t *fastiv_io_lookup_thumbnail( cairo_surface_t *fastiv_io_lookup_thumbnail(
const gchar *target, FastivIoThumbnailSize size); GFile *target, FastivIoThumbnailSize size);

View File

@ -31,8 +31,9 @@
struct _FastivView { struct _FastivView {
GtkWidget parent_instance; GtkWidget parent_instance;
cairo_surface_t *surface; ///< The loaded image (sequence) cairo_surface_t *image; ///< The loaded image (sequence)
cairo_surface_t *frame; ///< Current frame within, unreferenced cairo_surface_t *page; ///< Current page within image, weak
cairo_surface_t *frame; ///< Current frame within page, weak
FastivIoOrientation orientation; ///< Current orientation FastivIoOrientation orientation; ///< Current orientation
bool filter; bool filter;
bool scale_to_fit; bool scale_to_fit;
@ -89,7 +90,7 @@ static void
fastiv_view_finalize(GObject *gobject) fastiv_view_finalize(GObject *gobject)
{ {
FastivView *self = FASTIV_VIEW(gobject); FastivView *self = FASTIV_VIEW(gobject);
cairo_surface_destroy(self->surface); cairo_surface_destroy(self->image);
G_OBJECT_CLASS(fastiv_view_parent_class)->finalize(gobject); G_OBJECT_CLASS(fastiv_view_parent_class)->finalize(gobject);
} }
@ -115,28 +116,28 @@ static void
get_surface_dimensions(FastivView *self, double *width, double *height) get_surface_dimensions(FastivView *self, double *width, double *height)
{ {
*width = *height = 0; *width = *height = 0;
if (!self->surface) if (!self->image)
return; return;
cairo_rectangle_t extents = {}; cairo_rectangle_t extents = {};
switch (cairo_surface_get_type(self->surface)) { switch (cairo_surface_get_type(self->page)) {
case CAIRO_SURFACE_TYPE_IMAGE: case CAIRO_SURFACE_TYPE_IMAGE:
switch (self->orientation) { switch (self->orientation) {
case FastivIoOrientation90: case FastivIoOrientation90:
case FastivIoOrientationMirror90: case FastivIoOrientationMirror90:
case FastivIoOrientation270: case FastivIoOrientation270:
case FastivIoOrientationMirror270: case FastivIoOrientationMirror270:
*width = cairo_image_surface_get_height(self->surface); *width = cairo_image_surface_get_height(self->page);
*height = cairo_image_surface_get_width(self->surface); *height = cairo_image_surface_get_width(self->page);
break; break;
default: default:
*width = cairo_image_surface_get_width(self->surface); *width = cairo_image_surface_get_width(self->page);
*height = cairo_image_surface_get_height(self->surface); *height = cairo_image_surface_get_height(self->page);
} }
return; return;
case CAIRO_SURFACE_TYPE_RECORDING: case CAIRO_SURFACE_TYPE_RECORDING:
if (!cairo_recording_surface_get_extents(self->surface, &extents)) { if (!cairo_recording_surface_get_extents(self->page, &extents)) {
cairo_recording_surface_ink_extents(self->surface, cairo_recording_surface_ink_extents(self->page,
&extents.x, &extents.y, &extents.width, &extents.height); &extents.x, &extents.y, &extents.width, &extents.height);
} }
@ -198,7 +199,7 @@ fastiv_view_size_allocate(GtkWidget *widget, GtkAllocation *allocation)
->size_allocate(widget, allocation); ->size_allocate(widget, allocation);
FastivView *self = FASTIV_VIEW(widget); FastivView *self = FASTIV_VIEW(widget);
if (!self->surface || !self->scale_to_fit) if (!self->image || !self->scale_to_fit)
return; return;
double w, h; double w, h;
@ -271,7 +272,7 @@ fastiv_view_draw(GtkWidget *widget, cairo_t *cr)
allocation.width, allocation.height); allocation.width, allocation.height);
FastivView *self = FASTIV_VIEW(widget); FastivView *self = FASTIV_VIEW(widget);
if (!self->surface || if (!self->image ||
!gtk_cairo_should_draw_window(cr, gtk_widget_get_window(widget))) !gtk_cairo_should_draw_window(cr, gtk_widget_get_window(widget)))
return TRUE; return TRUE;
@ -408,7 +409,7 @@ static gboolean
fastiv_view_scroll_event(GtkWidget *widget, GdkEventScroll *event) fastiv_view_scroll_event(GtkWidget *widget, GdkEventScroll *event)
{ {
FastivView *self = FASTIV_VIEW(widget); FastivView *self = FASTIV_VIEW(widget);
if (!self->surface) if (!self->image)
return FALSE; return FALSE;
if (event->state & gtk_accelerator_get_default_mod_mask()) if (event->state & gtk_accelerator_get_default_mod_mask())
return FALSE; return FALSE;
@ -431,7 +432,7 @@ fastiv_view_key_press_event(GtkWidget *widget, GdkEventKey *event)
FastivView *self = FASTIV_VIEW(widget); FastivView *self = FASTIV_VIEW(widget);
if (event->state & ~GDK_SHIFT_MASK & gtk_accelerator_get_default_mod_mask()) if (event->state & ~GDK_SHIFT_MASK & gtk_accelerator_get_default_mod_mask())
return FALSE; return FALSE;
if (!self->surface) if (!self->image)
return FALSE; return FALSE;
switch (event->keyval) { switch (event->keyval) {
@ -462,16 +463,33 @@ fastiv_view_key_press_event(GtkWidget *widget, GdkEventKey *event)
gtk_widget_queue_resize(widget); gtk_widget_queue_resize(widget);
return TRUE; return TRUE;
case GDK_KEY_bracketleft: case GDK_KEY_bracketleft: {
cairo_surface_t *page = cairo_surface_get_user_data(
self->page, &fastiv_io_key_page_previous);
if (page)
self->frame = self->page = page;
gtk_widget_queue_resize(widget);
return TRUE;
}
case GDK_KEY_bracketright: {
cairo_surface_t *page = cairo_surface_get_user_data(
self->page, &fastiv_io_key_page_next);
if (page)
self->frame = self->page = page;
gtk_widget_queue_resize(widget);
return TRUE;
}
case GDK_KEY_braceleft:
if (!(self->frame = cairo_surface_get_user_data( if (!(self->frame = cairo_surface_get_user_data(
self->frame, &fastiv_io_key_frame_previous))) self->frame, &fastiv_io_key_frame_previous)))
self->frame = self->surface; self->frame = self->page;
gtk_widget_queue_draw(widget); gtk_widget_queue_draw(widget);
return TRUE; return TRUE;
case GDK_KEY_bracketright: case GDK_KEY_braceright:
if (!(self->frame = cairo_surface_get_user_data( if (!(self->frame = cairo_surface_get_user_data(
self->frame, &fastiv_io_key_frame_next))) self->frame, &fastiv_io_key_frame_next)))
self->frame = self->surface; self->frame = self->page;
gtk_widget_queue_draw(widget); gtk_widget_queue_draw(widget);
return TRUE; return TRUE;
} }
@ -527,14 +545,15 @@ fastiv_view_open(FastivView *self, const gchar *path, GError **error)
cairo_surface_t *surface = fastiv_io_open(path, error); cairo_surface_t *surface = fastiv_io_open(path, error);
if (!surface) if (!surface)
return FALSE; return FALSE;
if (self->surface) if (self->image)
cairo_surface_destroy(self->surface); cairo_surface_destroy(self->image);
self->frame = self->surface = surface; self->frame = self->page = self->image = surface;
set_scale_to_fit(self, true); set_scale_to_fit(self, true);
// TODO(p): This is actually per-page.
if ((self->orientation = (uintptr_t) cairo_surface_get_user_data( if ((self->orientation = (uintptr_t) cairo_surface_get_user_data(
self->surface, &fastiv_io_key_orientation)) == self->image, &fastiv_io_key_orientation)) ==
FastivIoOrientationUnknown) FastivIoOrientationUnknown)
self->orientation = FastivIoOrientation0; self->orientation = FastivIoOrientation0;
return TRUE; return TRUE;

View File

@ -311,13 +311,17 @@ spawn_path(const char *path)
} }
static void static void
on_item_activated(G_GNUC_UNUSED FastivBrowser *browser, const char *path, on_item_activated(G_GNUC_UNUSED FastivBrowser *browser, GFile *location,
GtkPlacesOpenFlags flags, G_GNUC_UNUSED gpointer data) GtkPlacesOpenFlags flags, G_GNUC_UNUSED gpointer data)
{ {
if (flags == GTK_PLACES_OPEN_NEW_WINDOW) gchar *path = g_file_get_path(location);
spawn_path(path); if (path) {
else if (flags == GTK_PLACES_OPEN_NEW_WINDOW)
open(path); spawn_path(path);
else
open(path);
g_free(path);
}
} }
static gboolean static gboolean