mirror of
https://github.com/bsnes-emu/bsnes.git
synced 2025-09-02 19:32:46 +02:00
Implement image compositing and rendering
Not saving to the correct place yet, but almost there!
This commit is contained in:
17
Makefile
17
Makefile
@@ -228,9 +228,6 @@ endif
|
||||
|
||||
GDK_PIXBUF_CFLAGS := $(shell $(PKG_CONFIG) --cflags gdk-pixbuf-2.0)
|
||||
GDK_PIXBUF_LDFLAGS := $(shell $(PKG_CONFIG) --libs gdk-pixbuf-2.0)
|
||||
|
||||
LIBPNG_CFLAGS := $(shell $(PKG_CONFIG) --cflags libpng)
|
||||
LIBPNG_LDFLAGS := $(shell $(PKG_CONFIG) --libs libpng)
|
||||
endif
|
||||
|
||||
ifeq (,$(PKG_CONFIG))
|
||||
@@ -429,7 +426,7 @@ $(OBJ)/SDL/%.c.o: SDL/%.c
|
||||
|
||||
$(OBJ)/XdgThumbnailer/%.c.o: XdgThumbnailer/%.c
|
||||
-@$(MKDIR) -p $(dir $@)
|
||||
$(CC) $(CFLAGS) $(GIO_CFLAGS) $(GDK_PIXBUF_CFLAGS) $(LIBPNG_CFLAGS) -DG_LOG_DOMAIN='"sameboy-thumbnailer"' -c $< -o $@
|
||||
$(CC) $(CFLAGS) $(GIO_CFLAGS) $(GDK_PIXBUF_CFLAGS) -DG_LOG_DOMAIN='"sameboy-thumbnailer"' -c $< -o $@
|
||||
# Make sure not to attempt compiling this before generating the interface code.
|
||||
$(OBJ)/XdgThumbnailer/main.c.o: $(OBJ)/XdgThumbnailer/interface.h
|
||||
# Make sure not to attempt compiling this before generating the resource code.
|
||||
@@ -438,7 +435,7 @@ $(OBJ)/XdgThumbnailer/emulate.c.o: $(OBJ)/XdgThumbnailer/resources.h
|
||||
# compilation with some version of the generator and/or compiler.
|
||||
$(OBJ)/XdgThumbnailer/%.c.o: $(OBJ)/XdgThumbnailer/%.c
|
||||
-@$(MKDIR) -p $(dir $@)
|
||||
$(CC) $(CFLAGS) $(GIO_CFLAGS) $(GDK_PIXBUF_CFLAGS) $(LIBPNG_CFLAGS) -DG_LOG_DOMAIN='"sameboy-thumbnailer"' -w -c $< -o $@
|
||||
$(CC) $(CFLAGS) $(GIO_CFLAGS) $(GDK_PIXBUF_CFLAGS) -DG_LOG_DOMAIN='"sameboy-thumbnailer"' -w -c $< -o $@
|
||||
|
||||
$(OBJ)/XdgThumbnailer/interface.c $(OBJ)/XdgThumbnailer/interface.h: XdgThumbnailer/interface.xml
|
||||
-@$(MKDIR) -p $(dir $@)
|
||||
@@ -574,7 +571,7 @@ $(BIN)/SameBoy.qlgenerator/Contents/Resources/cgb_boot_fast.bin: $(BIN)/BootROMs
|
||||
|
||||
$(BIN)/XdgThumbnailer/sameboy-thumbnailer: $(CORE_OBJECTS) $(XDG_THUMBNAILER_OBJECTS)
|
||||
-@$(MKDIR) -p $(dir $@)
|
||||
$(CC) $^ -o $@ $(LDFLAGS) $(GIO_LDFLAGS) $(GDK_PIXBUF_LDFLAGS) $(LIBPNG_LDFLAGS)
|
||||
$(CC) $^ -o $@ $(LDFLAGS) $(GIO_LDFLAGS) $(GDK_PIXBUF_LDFLAGS)
|
||||
|
||||
# SDL Port
|
||||
|
||||
@@ -657,14 +654,14 @@ $(BIN)/SDL/background.bmp: SDL/background.bmp
|
||||
-@$(MKDIR) -p $(dir $@)
|
||||
cp -f $< $@
|
||||
|
||||
$(BIN)/SDL/Shaders: $(wildcard Shaders/*.fsh)
|
||||
$(BIN)/SDL/Shaders: Shaders
|
||||
-@$(MKDIR) -p $@
|
||||
cp -rf $^ $@
|
||||
cp -rf $< $@
|
||||
touch $@
|
||||
|
||||
$(BIN)/SDL/Palettes: $(wildcard Misc/Palettes/*.sbp)
|
||||
$(BIN)/SDL/Palettes: Misc/Palettes
|
||||
-@$(MKDIR) -p $@
|
||||
cp -rf $^ $@
|
||||
cp -rf $< $@
|
||||
touch $@
|
||||
|
||||
# Boot ROMs
|
||||
|
@@ -1,7 +1,7 @@
|
||||
#include "emulate.h"
|
||||
|
||||
#include <glib.h>
|
||||
#include <gio/gio.h>
|
||||
#include <glib.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "Core/gb.h"
|
||||
@@ -27,8 +27,8 @@ void load_boot_rom(void)
|
||||
// NOTREACHED
|
||||
}
|
||||
else if (length != BOOT_ROM_SIZE) {
|
||||
g_error("Error loading boot ROM from \"%s\": expected to read %d bytes, got %zu",
|
||||
boot_rom_path, BOOT_ROM_SIZE, length);
|
||||
g_error("Error loading boot ROM from \"%s\": expected to read %d bytes, got %zu", boot_rom_path, BOOT_ROM_SIZE,
|
||||
length);
|
||||
// NOTREACHED
|
||||
}
|
||||
}
|
||||
@@ -37,10 +37,7 @@ void unload_boot_rom(void) { g_free(boot_rom); }
|
||||
|
||||
/* --- */
|
||||
|
||||
static char *async_input_callback(GB_gameboy_t *gb)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
static char *async_input_callback(GB_gameboy_t *gb) { return NULL; }
|
||||
|
||||
static void log_callback(GB_gameboy_t *gb, const char *string, GB_log_attributes attributes)
|
||||
{
|
||||
@@ -54,14 +51,26 @@ static void vblank_callback(GB_gameboy_t *gb, GB_vblank_type_t type)
|
||||
GB_set_user_data(gb, GUINT_TO_POINTER(nb_frames_left));
|
||||
|
||||
// *Do* render the very last frame.
|
||||
if (nb_frames_left == 0) {
|
||||
if (nb_frames_left == 1) {
|
||||
GB_set_rendering_disabled(gb, false);
|
||||
}
|
||||
}
|
||||
|
||||
static uint32_t rgb_encode(GB_gameboy_t *gb, uint8_t r, uint8_t g, uint8_t b)
|
||||
{
|
||||
return r | g << 8 | b << 16 | 0xFF << 24;
|
||||
uint32_t rgba;
|
||||
// The GdkPixbuf that will be created from the screen buffer later, expects components in the
|
||||
// order [red, green, blue, alpha], from a uint8_t[] buffer.
|
||||
// But SameBoy requires a uint32_t[] buffer, and don't know the endianness of `uint32_t`.
|
||||
// So we treat each uint32_t as a 4-byte buffer, and write the bytes accordingly.
|
||||
// This is guaranteed to not be UB, because casting a `T*` to any flavour of `char*` accesses
|
||||
// and modifies the `T`'s "object representation".
|
||||
unsigned char *bytes = (uint8_t *)&rgba;
|
||||
bytes[0] = r;
|
||||
bytes[1] = g;
|
||||
bytes[2] = b;
|
||||
bytes[3] = 0xFF;
|
||||
return rgba;
|
||||
}
|
||||
|
||||
unsigned emulate(enum FileKind kind, unsigned char const *rom, size_t rom_size, uint32_t screen[static 160 * 144])
|
||||
@@ -70,7 +79,8 @@ unsigned emulate(enum FileKind kind, unsigned char const *rom, size_t rom_size,
|
||||
GB_init(&gb, GB_MODEL_CGB_E);
|
||||
|
||||
GError *error = NULL;
|
||||
GBytes *boot_rom = g_resource_lookup_data(resources_get_resource(), "/thumbnailer/cgb_boot_fast.bin", G_RESOURCE_LOOKUP_FLAGS_NONE, &error);
|
||||
GBytes *boot_rom = g_resource_lookup_data(resources_get_resource(), "/thumbnailer/cgb_boot_fast.bin",
|
||||
G_RESOURCE_LOOKUP_FLAGS_NONE, &error);
|
||||
g_assert_no_error(error);
|
||||
size_t boot_rom_size;
|
||||
unsigned char const *boot_rom_data = g_bytes_get_data(boot_rom, &boot_rom_size);
|
||||
|
@@ -5,6 +5,7 @@
|
||||
#include <glib-unix.h>
|
||||
#include <glib.h>
|
||||
#include <signal.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
@@ -21,13 +22,12 @@ static char const *const object_path = "/com/github/liji32/sameboy/XdgThumbnaile
|
||||
ThumbnailerSpecializedThumbnailer1 *thumbnailer_interface = NULL;
|
||||
static unsigned max_nb_worker_threads;
|
||||
|
||||
static gboolean handle_queue(void *instance, GDBusMethodInvocation *invocation, char const *uri,
|
||||
char const *mime_type, char const *flavor, gboolean urgent,
|
||||
void *user_data)
|
||||
static gboolean handle_queue(void *instance, GDBusMethodInvocation *invocation, char const *uri, char const *mime_type,
|
||||
char const *flavor, gboolean urgent, void *user_data)
|
||||
{
|
||||
ThumbnailerSpecializedThumbnailer1 *skeleton = instance;
|
||||
g_info("Received Queue(uri=\"%s\", mime_type=\"%s\", flavor=\"%s\", urgent=%s) request", uri,
|
||||
mime_type, flavor, urgent ? "true" : "false");
|
||||
g_info("Received Queue(uri=\"%s\", mime_type=\"%s\", flavor=\"%s\", urgent=%s) request", uri, mime_type, flavor,
|
||||
urgent ? "true" : "false");
|
||||
g_assert(skeleton == thumbnailer_interface);
|
||||
|
||||
struct NewTaskInfo task_info = new_task(urgent);
|
||||
@@ -37,8 +37,7 @@ static gboolean handle_queue(void *instance, GDBusMethodInvocation *invocation,
|
||||
return G_DBUS_METHOD_INVOCATION_HANDLED;
|
||||
}
|
||||
|
||||
static gboolean handle_dequeue(void *instance, GDBusMethodInvocation *invocation, unsigned handle,
|
||||
void *user_data)
|
||||
static gboolean handle_dequeue(void *instance, GDBusMethodInvocation *invocation, unsigned handle, void *user_data)
|
||||
{
|
||||
ThumbnailerSpecializedThumbnailer1 *skeleton = instance;
|
||||
g_info("Received Dequeue(handle=%u) request", handle);
|
||||
@@ -69,8 +68,8 @@ static void on_bus_acquired(GDBusConnection *connection, const char *name, void
|
||||
g_assert(res);
|
||||
g_assert_no_error(error);
|
||||
if (error) {
|
||||
g_error("Error exporting interface \"%s\" at \"%s\": %s",
|
||||
g_dbus_interface_skeleton_get_info(interface)->name, object_path, error->message);
|
||||
g_error("Error exporting interface \"%s\" at \"%s\": %s", g_dbus_interface_skeleton_get_info(interface)->name,
|
||||
object_path, error->message);
|
||||
// NOTREACHED
|
||||
}
|
||||
}
|
||||
@@ -112,13 +111,12 @@ int main(int argc, char const *argv[])
|
||||
// Create the task queue *before* starting to accept tasks from D-Bus.
|
||||
init_tasks();
|
||||
// Likewise, create the main loop before then, so it can be aborted even before entering it.
|
||||
main_loop = g_main_loop_new(NULL, FALSE);
|
||||
main_loop = g_main_loop_new(NULL, false);
|
||||
|
||||
// Refuse to replace the name or be replaced; there should only be one instance of the
|
||||
// thumbnailer on the bus at all times. To replace this program, kill it.
|
||||
unsigned owner_id =
|
||||
g_bus_own_name(G_BUS_TYPE_SESSION, name_on_bus, G_BUS_NAME_OWNER_FLAGS_DO_NOT_QUEUE,
|
||||
on_bus_acquired, on_name_acquired, on_name_lost, NULL, NULL);
|
||||
unsigned owner_id = g_bus_own_name(G_BUS_TYPE_SESSION, name_on_bus, G_BUS_NAME_OWNER_FLAGS_DO_NOT_QUEUE,
|
||||
on_bus_acquired, on_name_acquired, on_name_lost, NULL, NULL);
|
||||
|
||||
unsigned sigterm_source_id = g_unix_signal_add(SIGTERM, handle_sigterm, NULL);
|
||||
g_main_loop_run(main_loop);
|
||||
|
@@ -2,12 +2,12 @@
|
||||
|
||||
// As defined in the thumbnailer spec.
|
||||
enum ErrorCode {
|
||||
ERROR_UNKNOWN_SCHEME_OR_MIME,
|
||||
ERROR_SPECIALIZED_THUMBNAILER_CONNECTION_FAILED, // Not applicable.
|
||||
ERROR_INVALID_DATA, // Any file can be decoded as a GB ROM, apparently!
|
||||
ERROR_THUMBNAIILING_THUMBNAIL, // We defer checking this to the generic thumbnailer.
|
||||
ERROR_COULD_NOT_WRITE,
|
||||
ERROR_UNSUPPORTED_FLAVOR,
|
||||
ERROR_UNKNOWN_SCHEME_OR_MIME,
|
||||
ERROR_SPECIALIZED_THUMBNAILER_CONNECTION_FAILED, // Not applicable.
|
||||
ERROR_INVALID_DATA, // Any file can be decoded as a GB ROM, apparently!
|
||||
ERROR_THUMBNAIILING_THUMBNAIL, // We defer checking this to the generic thumbnailer.
|
||||
ERROR_COULD_NOT_WRITE,
|
||||
ERROR_UNSUPPORTED_FLAVOR,
|
||||
};
|
||||
|
||||
struct _ThumbnailerSpecializedThumbnailer1;
|
||||
|
@@ -2,6 +2,7 @@
|
||||
|
||||
#include <gio/gio.h>
|
||||
#include <glib.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#define URGENT_FLAG (1u << (sizeof(unsigned) * CHAR_BIT - 1)) // The compiler should warn if this shift is out of range.
|
||||
|
||||
@@ -16,7 +17,7 @@ static struct Tasks urgent_tasks, tasks;
|
||||
static void init_task_list(struct Tasks *task_list)
|
||||
{
|
||||
g_rw_lock_init(&task_list->lock);
|
||||
task_list->tasks = g_array_new(FALSE, FALSE, sizeof(GCancellable *));
|
||||
task_list->tasks = g_array_new(false, false, sizeof(GCancellable *));
|
||||
}
|
||||
void init_tasks(void)
|
||||
{
|
||||
@@ -24,7 +25,8 @@ void init_tasks(void)
|
||||
init_task_list(&tasks);
|
||||
}
|
||||
|
||||
static void cleanup_task_list(struct Tasks *task_list) {
|
||||
static void cleanup_task_list(struct Tasks *task_list)
|
||||
{
|
||||
// TODO: wait for the remaining tasks to end?
|
||||
g_rw_lock_clear(&task_list->lock);
|
||||
g_array_unref(task_list->tasks);
|
||||
@@ -70,8 +72,7 @@ got_slot:
|
||||
g_assert_cmpuint(index, !=, 0);
|
||||
g_assert_cmpuint(index, <, URGENT_FLAG);
|
||||
|
||||
return (struct NewTaskInfo){.handle = is_urgent ? (index | URGENT_FLAG) : index,
|
||||
.cancellable = cancellable};
|
||||
return (struct NewTaskInfo){.handle = is_urgent ? (index | URGENT_FLAG) : index, .cancellable = cancellable};
|
||||
}
|
||||
|
||||
void cancel_task(unsigned handle)
|
||||
|
@@ -1,7 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <glib.h>
|
||||
#include <gio/gio.h>
|
||||
#include <glib.h>
|
||||
|
||||
void init_tasks(void);
|
||||
void cleanup_tasks(void);
|
||||
|
@@ -1,30 +1,25 @@
|
||||
#include "thumbnail.h"
|
||||
|
||||
#include <gdk-pixbuf/gdk-pixbuf.h>
|
||||
#include <gio/gio.h>
|
||||
#include <glib.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "emulate.h"
|
||||
#include "main.h"
|
||||
#include "tasks.h"
|
||||
|
||||
#define DMG_ONLY_RESOURCE_PATH "/thumbnailer/CartridgeTemplate.png"
|
||||
#define DUAL_RESOURCE_PATH "/thumbnailer/UniversalCartridgeTemplate.png"
|
||||
#define CGB_ONLY_RESOURCE_PATH "/thumbnailer/ColorCartridgeTemplate.png"
|
||||
|
||||
#define THUMBNAILING_ERROR_DOMAIN (g_quark_from_static_string("thumbnailing"))
|
||||
|
||||
/* --- */
|
||||
|
||||
enum CartridgeType {
|
||||
CART_DMG_ONLY,
|
||||
CART_DUAL,
|
||||
CART_CGB_ONLY,
|
||||
};
|
||||
|
||||
void load_cartridge_images(void) {
|
||||
// TODO
|
||||
}
|
||||
|
||||
/* --- */
|
||||
|
||||
struct TaskData {
|
||||
char *contents;
|
||||
size_t length;
|
||||
@@ -39,33 +34,88 @@ static void destroy_task_data(void *data)
|
||||
g_slice_free(struct TaskData, task_data);
|
||||
}
|
||||
|
||||
char const *mime_type(enum FileKind kind)
|
||||
{
|
||||
switch (kind) {
|
||||
case KIND_GB:
|
||||
return "application/x-gameboy-rom";
|
||||
case KIND_GBC:
|
||||
return "application/x-gameboy-color-rom";
|
||||
case KIND_ISX:
|
||||
return "application/x-gameboy-isx";
|
||||
}
|
||||
}
|
||||
|
||||
/* --- */
|
||||
|
||||
static void generate_thumbnail(GTask *task, void *source_object, void *data,
|
||||
GCancellable *cancellable)
|
||||
#define GB_SCREEN_WIDTH 160
|
||||
#define GB_SCREEN_HEIGHT 144
|
||||
|
||||
static void generate_thumbnail(GTask *task, void *source_object, void *data, GCancellable *cancellable)
|
||||
{
|
||||
struct TaskData *task_data = data;
|
||||
|
||||
uint32_t screen[160 * 144];
|
||||
unsigned cgb_flag = emulate(task_data->kind, (unsigned char const *)task_data->contents,
|
||||
task_data->length, screen);
|
||||
uint32_t screen_raw[GB_SCREEN_WIDTH * GB_SCREEN_HEIGHT];
|
||||
unsigned cgb_flag =
|
||||
emulate(task_data->kind, (unsigned char const *)task_data->contents, task_data->length, screen_raw);
|
||||
|
||||
// Generate the thumbnail from `screen` and `cgb_flag`.
|
||||
enum CartridgeType type;
|
||||
// Generate the thumbnail from `screen_raw` and `cgb_flag`.
|
||||
|
||||
// `screen_raw` is properly formatted for this operation; see the comment in `rgb_encode` for a
|
||||
// discussion of why and how.
|
||||
GdkPixbuf *screen = gdk_pixbuf_new_from_data((uint8_t *)screen_raw, GDK_COLORSPACE_RGB,
|
||||
true, // Yes, we have alpha!
|
||||
8, // bpp
|
||||
GB_SCREEN_WIDTH, GB_SCREEN_HEIGHT, // Size.
|
||||
GB_SCREEN_WIDTH * sizeof(*screen_raw), // Row stride.
|
||||
NULL, NULL); // Do not free the buffer.
|
||||
// Scale the screen and position it in the appropriate place for compositing the cartridge templates.
|
||||
GdkPixbuf *scaled_screen = gdk_pixbuf_new(GDK_COLORSPACE_RGB, true, 8, 1024, 1024);
|
||||
gdk_pixbuf_scale(screen, scaled_screen, 192, 298, // Match the displacement below.
|
||||
GB_SCREEN_WIDTH * 4, GB_SCREEN_HEIGHT * 4, // Cropping the scaled rectangle...
|
||||
192, 298, // Displace the scaled screen so it lines up with the template.
|
||||
4, 4, // Scaling factors.
|
||||
GDK_INTERP_NEAREST);
|
||||
g_object_unref(screen);
|
||||
|
||||
GError *error = NULL;
|
||||
GdkPixbuf *template;
|
||||
switch (cgb_flag) {
|
||||
case 0xC0:
|
||||
type = CART_CGB_ONLY;
|
||||
break;
|
||||
case 0x80:
|
||||
type = CART_DUAL;
|
||||
break;
|
||||
default:
|
||||
type = CART_DMG_ONLY;
|
||||
break;
|
||||
case 0xC0:
|
||||
template = gdk_pixbuf_new_from_resource(CGB_ONLY_RESOURCE_PATH, &error);
|
||||
break;
|
||||
case 0x80:
|
||||
template = gdk_pixbuf_new_from_resource(DUAL_RESOURCE_PATH, &error);
|
||||
break;
|
||||
default:
|
||||
template = gdk_pixbuf_new_from_resource(DMG_ONLY_RESOURCE_PATH, &error);
|
||||
break;
|
||||
}
|
||||
(void)type;
|
||||
g_assert_no_error(error);
|
||||
g_assert_cmpint(gdk_pixbuf_get_width(template), ==, 1024);
|
||||
g_assert_cmpint(gdk_pixbuf_get_height(template), ==, 1024);
|
||||
gdk_pixbuf_composite(template, // Source.
|
||||
scaled_screen, // Destination.
|
||||
0, 0, // Match the displacement below.
|
||||
1024, 1024, // Crop of the scaled rectangle.
|
||||
0, 0, // Displacement of the scaled rectangle.
|
||||
1, 1, // Scaling factors.
|
||||
GDK_INTERP_NEAREST, // Doesn't really matter, but should be a little faster.
|
||||
255); // Blending factor of the source.
|
||||
g_object_unref(template);
|
||||
|
||||
g_task_return_boolean(task, TRUE);
|
||||
char file_size[sizeof(G_STRINGIFY(SIZE_MAX))];
|
||||
sprintf(file_size, "%zu", task_data->length);
|
||||
// TODO: proper file name
|
||||
gdk_pixbuf_save(scaled_screen, "/tmp/output.png", "png", &error, // "Base" parameters.
|
||||
"tEXt::Thumb::URI", g_task_get_name(task), // URI of the file being thumbnailed.
|
||||
"tEXt::Thumb::MTime", "", // TODO
|
||||
"tEXt::Thumb::Size", file_size, // Size (in bytes) of the file being thumbnailed.
|
||||
"tEXt::Thumb::Mimetype", mime_type(task_data->kind), // MIME type of the file being thumbnailed.
|
||||
NULL);
|
||||
g_object_unref(scaled_screen);
|
||||
|
||||
g_task_return_boolean(task, true);
|
||||
g_object_unref(task);
|
||||
}
|
||||
|
||||
@@ -116,16 +166,15 @@ static void on_thumbnailing_end(GObject *source_object, GAsyncResult *res, void
|
||||
}
|
||||
else if (!g_cancellable_is_cancelled(g_task_get_cancellable(task))) {
|
||||
// If the task was cancelled, do not emit an error response.
|
||||
g_signal_emit_by_name(thumbnailer_interface, "error", handle, uri, error->code,
|
||||
error->message);
|
||||
g_signal_emit_by_name(thumbnailer_interface, "error", handle, uri, error->code, error->message);
|
||||
}
|
||||
g_signal_emit_by_name(thumbnailer_interface, "finished", handle);
|
||||
|
||||
finished_task(handle);
|
||||
}
|
||||
|
||||
void start_thumbnailing(unsigned handle, GCancellable *cancellable, gboolean is_urgent,
|
||||
char const *uri, char const *mime_type)
|
||||
void start_thumbnailing(unsigned handle, GCancellable *cancellable, gboolean is_urgent, char const *uri,
|
||||
char const *mime_type)
|
||||
{
|
||||
g_signal_emit_by_name(thumbnailer_interface, "started", handle);
|
||||
|
||||
|
@@ -1,7 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <glib.h>
|
||||
#include <gio/gio.h>
|
||||
#include <glib.h>
|
||||
|
||||
void start_thumbnailing(unsigned handle, GCancellable *cancellable, gboolean is_urgent,
|
||||
char const *uri, char const *mime_type);
|
||||
void start_thumbnailing(unsigned handle, GCancellable *cancellable, gboolean is_urgent, char const *uri,
|
||||
char const *mime_type);
|
||||
|
Reference in New Issue
Block a user