diff --git a/Makefile.am b/Makefile.am index f32591f5..4d4a9097 100644 --- a/Makefile.am +++ b/Makefile.am @@ -75,6 +75,7 @@ obrender_libobrender_la_CPPFLAGS = \ $(XML_CFLAGS) \ $(PANGO_CFLAGS) \ $(IMLIB2_CFLAGS) \ + $(LIBRSVG_CFLAGS) \ -DG_LOG_DOMAIN=\"ObRender\" \ -DDEFAULT_THEME=\"$(theme)\" obrender_libobrender_la_LDFLAGS = \ @@ -85,6 +86,7 @@ obrender_libobrender_la_LIBADD = \ $(PANGO_LIBS) \ $(GLIB_LIBS) \ $(IMLIB2_LIBS) \ + $(LIBRSVG_LIBS) \ $(XML_LIBS) obrender_libobrender_la_SOURCES = \ gettext.h \ diff --git a/configure.ac b/configure.ac index a1833090..49cfd938 100644 --- a/configure.ac +++ b/configure.ac @@ -195,6 +195,36 @@ fi AM_CONDITIONAL(USE_IMLIB2, [test $imlib2_found = yes]) +AC_ARG_ENABLE(librsvg, + AC_HELP_STRING( + [--disable-librsvg], + [disable use of SVG image files for loading icons. [default=enabled]] + ), + [enable_librsvg=$enableval], + [enable_librsvg=yes] +) + +if test "$enable_librsvg" = yes; then +PKG_CHECK_MODULES(LIBRSVG, [librsvg-2.0], + [ + AC_DEFINE(USE_LIBRSVG, [1], [Use SVG image files]) + AC_SUBST(LIBRSVG_CFLAGS) + AC_SUBST(LIBRSVG_LIBS) + # export it for the pkg-config file + PKG_CONFIG_LIBRSVG=librsvg-2.0 + AC_SUBST(PKG_CONFIG_LIBRSVG) + librsvg_found=yes + ], + [ + librsvg_found=no + ] +) +else + librsvg_found=no +fi + +AM_CONDITIONAL(USE_LIBRSVG, [test $librsvg_found = yes]) + dnl Check for session management X11_SM @@ -233,6 +263,7 @@ AC_MSG_RESULT([Compiling with these options: Startup Notification... $sn_found X Cursor Library... $xcursor_found Session Management... $SM - Imlib2 library... $imlib2_found + Imlib2 Library... $imlib2_found + SVG Support (librsvg)... $librsvg_found ]) AC_MSG_RESULT([configure complete, now type "make"]) diff --git a/obrender/image.c b/obrender/image.c index 196d9d1e..0164a8c8 100644 --- a/obrender/image.c +++ b/obrender/image.c @@ -24,6 +24,9 @@ #ifdef USE_IMLIB2 #include #endif +#ifdef USE_LIBRSVG +#include +#endif #include @@ -463,17 +466,164 @@ RrImage* RrImageNewFromData(RrImageCache *cache, RrPixel32 *data, return self; } +#if defined(USE_IMLIB2) +typedef struct _ImlibLoader ImlibLoader; + +struct _ImlibLoader +{ + Imlib_Image img; +}; + +void DestroyImlibLoader(ImlibLoader *loader) +{ + if (!loader) + return; + + imlib_free_image(); + g_slice_free(ImlibLoader, loader); +} + +ImlibLoader* LoadWithImlib(gchar *path, + RrPixel32 **pixel_data, + gint *width, + gint *height) +{ + ImlibLoader *loader = g_slice_new0(ImlibLoader); + if (!(loader->img = imlib_load_image(path))) { + DestroyImlibLoader(loader); + return NULL; + } + + /* Get data and dimensions of the image. + + WARNING: This stuff is NOT threadsafe !! + */ + imlib_context_set_image(loader->img); + *pixel_data = imlib_image_get_data_for_reading_only(); + *width = imlib_image_get_width(); + *height = imlib_image_get_height(); + + return loader; +} +#endif /* USE_IMLIB2 */ + +#if defined(USE_LIBRSVG) +typedef struct _RsvgLoader RsvgLoader; + +struct _RsvgLoader +{ + RsvgHandle *handle; + cairo_surface_t *surface; + RrPixel32 *pixel_data; +}; + +void DestroyRsvgLoader(RsvgLoader *loader) +{ + if (!loader) + return; + + if (loader->pixel_data) + g_free(loader->pixel_data); + if (loader->surface) + cairo_surface_destroy(loader->surface); + if (loader->handle) + g_object_unref(loader->handle); + g_slice_free(RsvgLoader, loader); +} + +RsvgLoader* LoadWithRsvg(gchar *path, + RrPixel32 **pixel_data, + gint *width, + gint *height) +{ + RsvgLoader *loader = g_slice_new0(RsvgLoader); + + if (!(loader->handle = rsvg_handle_new_from_file(path, NULL))) { + DestroyRsvgLoader(loader); + return NULL; + } + + if (!rsvg_handle_close(loader->handle, NULL)) { + DestroyRsvgLoader(loader); + return NULL; + } + + RsvgDimensionData dimension_data; + rsvg_handle_get_dimensions(loader->handle, &dimension_data); + *width = dimension_data.width; + *height = dimension_data.height; + + loader->surface = cairo_image_surface_create( + CAIRO_FORMAT_ARGB32, *width, *height); + + cairo_t* context = cairo_create(loader->surface); + gboolean success = rsvg_handle_render_cairo(loader->handle, context); + cairo_destroy(context); + + if (!success) { + DestroyRsvgLoader(loader); + return NULL; + } + + loader->pixel_data = g_new(guint32, *width * *height); + + /* + Cairo has its data in ARGB with premultiplied alpha, but RrPixel32 + non-premultipled, so convert that. Also, RrPixel32 doesn't allow + strides not equal to the width of the image. + */ + + /* Verify that RrPixel32 has the same ordering as cairo. */ + g_assert(RrDefaultAlphaOffset == 24); + g_assert(RrDefaultRedOffset == 16); + g_assert(RrDefaultGreenOffset == 8); + g_assert(RrDefaultBlueOffset == 0); + + guint32 *out_row = loader->pixel_data; + + guint32 *in_row = + (guint32*)cairo_image_surface_get_data(loader->surface); + gint in_stride = cairo_image_surface_get_stride(loader->surface); + + gint y; + for (y = 0; y < *height; ++y) { + gint x; + for (x = 0; x < *width; ++x) { + guchar a = in_row[x] >> 24; + guchar r = (in_row[x] >> 16) & 0xff; + guchar g = (in_row[x] >> 8) & 0xff; + guchar b = in_row[x] & 0xff; + out_row[x] = + ((r * 256 / (a + 1)) << RrDefaultRedOffset) + + ((g * 256 / (a + 1)) << RrDefaultGreenOffset) + + ((b * 256 / (a + 1)) << RrDefaultBlueOffset) + + (a << RrDefaultAlphaOffset); + } + in_row += in_stride / 4; + out_row += *width; + } + + *pixel_data = loader->pixel_data; + + return loader; +} +#endif /* USE_LIBRSVG */ + RrImage* RrImageNewFromName(RrImageCache *cache, const gchar *name) { -#ifndef USE_IMLIB2 - return NULL; -#else RrImage *self; RrImageSet *set; - Imlib_Image img; gint w, h; RrPixel32 *data; gchar *path; + gboolean loaded; + +#if defined(USE_IMLIB2) + ImlibLoader *imlib_loader = NULL; +#endif +#if defined(USE_LIBRSVG) + RsvgLoader *rsvg_loader = NULL; +#endif g_return_val_if_fail(cache != NULL, NULL); g_return_val_if_fail(name != NULL, NULL); @@ -488,21 +638,32 @@ RrImage* RrImageNewFromName(RrImageCache *cache, const gchar *name) /* XXX find the path via freedesktop icon spec (use obt) ! */ path = g_strdup(name); - if (!(img = imlib_load_image(path))) - g_message("Cannot load image \"%s\" from file \"%s\"", name, path); + loaded = FALSE; +#if defined(USE_LIBRSVG) + if (!loaded) { + rsvg_loader = LoadWithRsvg(path, &data, &w, &h); + loaded = !!rsvg_loader; + } +#endif +#if defined(USE_IMLIB2) + if (!loaded) { + imlib_loader = LoadWithImlib(path, &data, &w, &h); + loaded = !!imlib_loader; + } +#endif + g_free(path); - if (!img) + if (!loaded) { + g_message("Cannot load image \"%s\" from file \"%s\"", name, path); +#if defined(USE_LIBRSVG) + DestroyRsvgLoader(rsvg_loader); +#endif +#if defined(USE_IMLIB2) + DestroyImlibLoader(imlib_loader); +#endif return NULL; - - /* Get data and dimensions of the image. - - WARNING: This stuff is NOT threadsafe !! - */ - imlib_context_set_image(img); - data = imlib_image_get_data_for_reading_only(); - w = imlib_image_get_width(); - h = imlib_image_get_height(); + } /* get an RrImage that contains an RrImageSet with this picture in it. the RrImage might be new, or reused if the picture was already in the @@ -517,9 +678,14 @@ RrImage* RrImageNewFromName(RrImageCache *cache, const gchar *name) self = RrImageNewFromData(cache, data, w, h); RrImageSetAddName(self->set, name); - imlib_free_image(); - return self; +#if defined(USE_LIBRSVG) + DestroyRsvgLoader(rsvg_loader); #endif +#if defined(USE_IMLIB2) + DestroyImlibLoader(imlib_loader); +#endif + + return self; } /************************************************************************ diff --git a/obrender/obrender-3.5.pc.in b/obrender/obrender-3.5.pc.in index 2c8a4357..8057a017 100644 --- a/obrender/obrender-3.5.pc.in +++ b/obrender/obrender-3.5.pc.in @@ -9,6 +9,6 @@ xlibs=@X_LIBS@ Name: ObRender Description: Openbox Render Library Version: @RR_VERSION@ -Requires: obt-3.5 glib-2.0 xft pangoxft @PKG_CONFIG_IMLIB@ +Requires: obt-3.5 glib-2.0 xft pangoxft @PKG_CONFIG_IMLIB@ @PKG_CONFIG_LIBRSVG@ Libs: -L${libdir} -lobrender ${xlibs} Cflags: -I${includedir}/openbox/@RR_VERSION@ ${xcflags} diff --git a/obrender/render.h b/obrender/render.h index a5d6500c..59e77660 100644 --- a/obrender/render.h +++ b/obrender/render.h @@ -49,7 +49,7 @@ typedef struct _RrImagePic RrImagePic; typedef struct _RrImageCache RrImageCache; typedef struct _RrButton RrButton; -typedef guint32 RrPixel32; /* RGBA format */ +typedef guint32 RrPixel32; /* ARGB format, not premultiplied alpha */ typedef guint16 RrPixel16; typedef guchar RrPixel8; diff --git a/obt/xml.c b/obt/xml.c index 5b7e77b5..223ad02b 100644 --- a/obt/xml.c +++ b/obt/xml.c @@ -48,8 +48,13 @@ struct _ObtXmlInst { xmlDocPtr doc; xmlNodePtr root; gchar *path; + gchar *last_error_file; + gint last_error_line; + gchar *last_error_message; }; +static void obt_xml_save_last_error(ObtXmlInst* inst); + static void destfunc(struct Callback *c) { g_free(c->tag); @@ -66,6 +71,9 @@ ObtXmlInst* obt_xml_instance_new(void) i->doc = NULL; i->root = NULL; i->path = NULL; + i->last_error_file = NULL; + i->last_error_line = -1; + i->last_error_message = NULL; return i; } @@ -79,6 +87,8 @@ void obt_xml_instance_unref(ObtXmlInst *i) if (i && --i->ref == 0) { obt_paths_unref(i->xdg_paths); g_hash_table_destroy(i->callbacks); + g_free(i->last_error_file); + g_free(i->last_error_message); g_slice_free(ObtXmlInst, i); } } @@ -128,6 +138,8 @@ static gboolean load_file(ObtXmlInst *i, g_assert(i->doc == NULL); /* another doc isn't open already? */ + xmlResetLastError(); + for (it = paths; !r && it; it = g_slist_next(it)) { gchar *path; struct stat s; @@ -169,6 +181,8 @@ static gboolean load_file(ObtXmlInst *i, g_free(path); } + obt_xml_save_last_error(i); + return r; } @@ -264,6 +278,8 @@ gboolean obt_xml_load_mem(ObtXmlInst *i, g_assert(i->doc == NULL); /* another doc isn't open already? */ + xmlResetLastError(); + i->doc = xmlParseMemory(data, len); if (i) { i->root = xmlDocGetRootElement(i->doc); @@ -282,9 +298,51 @@ gboolean obt_xml_load_mem(ObtXmlInst *i, else r = TRUE; /* ok ! */ } + + obt_xml_save_last_error(i); + return r; } +static void obt_xml_save_last_error(ObtXmlInst* inst) +{ + xmlErrorPtr error = xmlGetLastError(); + if (error) { + inst->last_error_file = g_strdup(error->file); + inst->last_error_line = error->line; + inst->last_error_message = g_strdup(error->message); + xmlResetError(error); + } +} + +gboolean obt_xml_last_error(ObtXmlInst *inst) +{ + return inst->last_error_file && + inst->last_error_line >= 0 && + inst->last_error_message; +} + +gchar* obt_xml_last_error_file(ObtXmlInst *inst) +{ + if (!obt_xml_last_error(inst)) + return NULL; + return inst->last_error_file; +} + +gint obt_xml_last_error_line(ObtXmlInst *inst) +{ + if (!obt_xml_last_error(inst)) + return -1; + return inst->last_error_line; +} + +gchar* obt_xml_last_error_message(ObtXmlInst *inst) +{ + if (!obt_xml_last_error(inst)) + return NULL; + return inst->last_error_message; +} + gboolean obt_xml_save_file(ObtXmlInst *inst, const gchar *path, gboolean pretty) diff --git a/obt/xml.h b/obt/xml.h index 831aba63..f6b5dc23 100644 --- a/obt/xml.h +++ b/obt/xml.h @@ -51,6 +51,12 @@ gboolean obt_xml_load_theme_file(ObtXmlInst *inst, gboolean obt_xml_load_mem(ObtXmlInst *inst, gpointer data, guint len, const gchar *root_node); +/* Returns true if an error is present. */ +gboolean obt_xml_last_error(ObtXmlInst *inst); +gchar* obt_xml_last_error_file(ObtXmlInst *inst); +gint obt_xml_last_error_line(ObtXmlInst *inst); +gchar* obt_xml_last_error_message(ObtXmlInst *inst); + gboolean obt_xml_save_file(ObtXmlInst *inst, const gchar *path, gboolean pretty); diff --git a/openbox/openbox.c b/openbox/openbox.c index 4ac09cd6..cba04995 100644 --- a/openbox/openbox.c +++ b/openbox/openbox.c @@ -224,6 +224,7 @@ gint main(gint argc, gchar **argv) event_reset_time(); do { + gchar *xml_error_string = NULL; ObPrompt *xmlprompt = NULL; if (reconfigure) obt_keyboard_reload(); @@ -264,6 +265,14 @@ gint main(gint argc, gchar **argv) else OBT_PROP_ERASE(obt_root(ob_screen), OB_CONFIG_FILE); + if (obt_xml_last_error(i)) { + xml_error_string = g_strdup_printf( + _("One or more XML syntax errors were found while parsing the Openbox configuration files. See stdout for more information. The last error seen was in file \"%s\" line %d, with message: %s"), + obt_xml_last_error_file(i), + obt_xml_last_error_line(i), + obt_xml_last_error_message(i)); + } + /* we're done with parsing now, kill it */ obt_xml_instance_unref(i); } @@ -362,17 +371,12 @@ gint main(gint argc, gchar **argv) reconfigure = FALSE; /* look for parsing errors */ - { - xmlErrorPtr e = xmlGetLastError(); - if (e) { - gchar *m; - - m = g_strdup_printf(_("One or more XML syntax errors were found while parsing the Openbox configuration files. See stdout for more information. The last error seen was in file \"%s\" line %d, with message: %s"), e->file, e->line, e->message); - xmlprompt = - prompt_show_message(m, _("Openbox Syntax Error"), _("Close")); - g_free(m); - xmlResetError(e); - } + if (xml_error_string) { + xmlprompt = prompt_show_message(xml_error_string, + _("Openbox Syntax Error"), + _("Close")); + g_free(xml_error_string); + xml_error_string = NULL; } g_main_loop_run(ob_main_loop); diff --git a/release/go b/release/go index 3158ac21..f54e8014 100755 --- a/release/go +++ b/release/go @@ -78,6 +78,13 @@ make >/dev/null 2>/dev/null || \ error "make (with --disable-imlib2) failed" make clean >/dev/null || error "make clean failed" +echo Check compile with librsvg disabled +./configure -C --disable-imlib2 >/dev/null || \ + error "configure failed" +make >/dev/null 2>/dev/null || \ + error "make (with --disable-librsvg) failed" +make clean >/dev/null || error "make clean failed" + echo Check compile with session management disabled ./configure -C --disable-session-management >/dev/null || \ error "configure failed"