[retro-gtk/wip/aplazas/gl-display: 4/4] Add RetroGLDisplay
- From: Adrien Plazas <aplazas src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [retro-gtk/wip/aplazas/gl-display: 4/4] Add RetroGLDisplay
- Date: Fri, 10 Nov 2017 09:34:30 +0000 (UTC)
commit bbb3da54997dd2eaa8720243c14ff3d979c28085
Author: Adrien Plazas <kekun plazas laposte net>
Date: Sun Oct 15 09:36:05 2017 +0200
Add RetroGLDisplay
meson.build | 3 +
retro-gtk/meson.build | 11 +
retro-gtk/retro-core-view.c | 31 ++-
retro-gtk/retro-gl-display-fragment.glsl | 6 +
retro-gtk/retro-gl-display-vertex.glsl | 6 +
retro-gtk/retro-gl-display.c | 433 ++++++++++++++++++++++++++++++
retro-gtk/retro-gl-display.h | 36 +++
retro-gtk/retro-gtk-0.14.deps | 1 +
retro-gtk/retro-gtk.gresource.xml | 7 +
9 files changed, 519 insertions(+), 15 deletions(-)
---
diff --git a/meson.build b/meson.build
index 5f560f7..d0f936d 100644
--- a/meson.build
+++ b/meson.build
@@ -3,6 +3,7 @@ project('retro-gtk','c',
meson_version: '>= 0.43.0',
)
+cc = meson.get_compiler('c')
gnome = import('gnome')
prefix = get_option('prefix')
@@ -15,12 +16,14 @@ srcinc = include_directories('retro-gtk')
glib_version = '>= 2.50'
gtk_version = '>= 3.22'
+epoxy = dependency ('epoxy')
gio = dependency ('gio-2.0', version: glib_version)
glib = dependency ('glib-2.0', version: glib_version)
gmodule = dependency ('gmodule-2.0', version: glib_version)
gobject = dependency ('gobject-2.0', version: glib_version)
gtk = dependency ('gtk+-3.0', version: gtk_version)
libpulse_simple = dependency ('libpulse-simple')
+m = cc.find_library('m', required : false)
config_h = configuration_data()
config_h.set_quoted ('RETRO_PLUGIN_PATH', ':'.join ([libretrodir, libdir]))
diff --git a/retro-gtk/meson.build b/retro-gtk/meson.build
index 7b86739..1a2f733 100644
--- a/retro-gtk/meson.build
+++ b/retro-gtk/meson.build
@@ -2,7 +2,15 @@ api_version = '0.14'
retro_gtk_module = 'retro-gtk-' + api_version
+retro_gtk_resources = gnome.compile_resources(
+ 'retro_gtk_resources',
+ 'retro-gtk.gresource.xml',
+ c_name: 'retro_gtk',
+ source_dir: '.',
+)
+
retro_gtk_sources = [
+ retro_gtk_resources[0],
'retro-cairo-display.c',
'retro-controller.c',
'retro-controller-codes.c',
@@ -14,6 +22,7 @@ retro_gtk_sources = [
'retro-core-view-controller.c',
'retro-environment.c',
'retro-game-info.c',
+ 'retro-gl-display.c',
'retro-input.c',
'retro-input-descriptor.c',
'retro-keyboard-key.c',
@@ -80,12 +89,14 @@ retro_gtk_c_args = [
]
retro_gtk_deps = [
+ epoxy,
gio,
glib,
gmodule,
gobject,
gtk,
libpulse_simple,
+ m,
]
retro_gtk_lib = shared_library(
diff --git a/retro-gtk/retro-core-view.c b/retro-gtk/retro-core-view.c
index 3f3f9c2..6aa2cf3 100644
--- a/retro-gtk/retro-core-view.c
+++ b/retro-gtk/retro-core-view.c
@@ -4,6 +4,7 @@
#include <linux/input-event-codes.h>
#include "retro-cairo-display.h"
+#include "retro-gl-display.h"
#include "retro-controller-codes.h"
#include "retro-core-view-controller.h"
#include "retro-pa-player.h"
@@ -31,7 +32,7 @@ struct _RetroCoreView
{
GtkEventBox parent_instance;
RetroCore *core;
- RetroCairoDisplay *display;
+ RetroGLDisplay *display;
GBinding *pixbuf_binding;
GBinding *sensitive_binding;
GdkPixbuf *pixbuf;
@@ -247,11 +248,11 @@ retro_core_view_on_button_press_event (GtkWidget *source,
else {
set_input_pressed (self->mouse_button_state, event->button);
self->pointer_is_on_display =
- retro_cairo_display_get_coordinates_on_display (self->display,
- event->x,
- event->y,
- &self->pointer_x,
- &self->pointer_y);
+ retro_gl_display_get_coordinates_on_display (self->display,
+ event->x,
+ event->y,
+ &self->pointer_x,
+ &self->pointer_y);
}
return FALSE;
@@ -312,11 +313,11 @@ retro_core_view_on_motion_notify_event (GtkWidget *source,
}
else {
self->pointer_is_on_display =
- retro_cairo_display_get_coordinates_on_display (self->display,
- event->x,
- event->y,
- &self->pointer_x,
- &self->pointer_y);
+ retro_gl_display_get_coordinates_on_display (self->display,
+ event->x,
+ event->y,
+ &self->pointer_x,
+ &self->pointer_y);
}
@@ -488,7 +489,7 @@ retro_core_view_init (RetroCoreView *self)
{
g_object_set ((GtkWidget*) self, "can-focus", TRUE, NULL);
- self->display = g_object_ref_sink (retro_cairo_display_new ());
+ self->display = g_object_ref_sink (retro_gl_display_new ());
gtk_widget_set_visible (GTK_WIDGET (self->display), TRUE);
g_object_set (GTK_WIDGET (self->display), "can-focus", FALSE, NULL);
gtk_container_add (GTK_CONTAINER (self), GTK_WIDGET (self->display));
@@ -537,13 +538,13 @@ retro_core_view_set_core (RetroCoreView *self,
if (self->core != NULL) {
g_clear_object (&self->core);
- retro_cairo_display_set_core (self->display, NULL);
+ retro_gl_display_set_core (self->display, NULL);
retro_pa_player_set_core (self->audio_player, NULL);
}
if (core != NULL) {
self->core = g_object_ref (core);
- retro_cairo_display_set_core (self->display, core);
+ retro_gl_display_set_core (self->display, core);
retro_pa_player_set_core (self->audio_player, core);
}
}
@@ -595,7 +596,7 @@ retro_core_view_set_filter (RetroCoreView *self,
{
g_return_if_fail (RETRO_IS_CORE_VIEW (self));
- retro_cairo_display_set_filter (self->display, filter);
+ retro_gl_display_set_filter (self->display, filter);
}
/**
diff --git a/retro-gtk/retro-gl-display-fragment.glsl b/retro-gtk/retro-gl-display-fragment.glsl
new file mode 100644
index 0000000..f70065a
--- /dev/null
+++ b/retro-gtk/retro-gl-display-fragment.glsl
@@ -0,0 +1,6 @@
+#version 400
+
+out vec4 frag_colour;
+void main() {
+ frag_colour = vec4(0.5, 0.0, 0.5, 1.0);
+}
diff --git a/retro-gtk/retro-gl-display-vertex.glsl b/retro-gtk/retro-gl-display-vertex.glsl
new file mode 100644
index 0000000..d2107ad
--- /dev/null
+++ b/retro-gtk/retro-gl-display-vertex.glsl
@@ -0,0 +1,6 @@
+#version 400
+
+in vec3 vp;
+void main() {
+ gl_Position = vec4(vp, 1.0);
+}
diff --git a/retro-gtk/retro-gl-display.c b/retro-gtk/retro-gl-display.c
new file mode 100644
index 0000000..b5ca8b2
--- /dev/null
+++ b/retro-gtk/retro-gl-display.c
@@ -0,0 +1,433 @@
+// This file is part of retro-gtk. License: GPL-3.0+.
+
+#include "retro-gl-display.h"
+
+#include <epoxy/gl.h>
+#include <math.h>
+#include "retro-pixdata.h"
+
+struct _RetroGLDisplay
+{
+ GtkGLArea parent_instance;
+ RetroCore *core;
+ GdkPixbuf *pixbuf;
+ RetroVideoFilter filter;
+ gfloat aspect_ratio;
+ gulong on_video_output_id;
+
+ GLuint vertex_array_object;
+ GLuint shader_program;
+};
+
+G_DEFINE_TYPE (RetroGLDisplay, retro_gl_display, GTK_TYPE_GL_AREA)
+
+enum {
+ PROP_PIXBUF = 1,
+ N_PROPS,
+};
+
+static GParamSpec *properties [N_PROPS];
+
+#define GLAREA_ERROR (glarea_error_quark ())
+
+typedef enum {
+ GLAREA_ERROR_SHADER_COMPILATION,
+ GLAREA_ERROR_SHADER_LINK
+} GlareaError;
+
+G_DEFINE_QUARK (glarea-error, glarea_error)
+
+/* Private */
+
+static void
+retro_gl_display_get_video_box (RetroGLDisplay *self,
+ gdouble *width,
+ gdouble *height,
+ gdouble *x,
+ gdouble *y)
+{
+ gdouble w;
+ gdouble h;
+ gdouble display_ratio;
+ gdouble allocated_ratio;
+
+ g_return_if_fail (self != NULL);
+ g_return_if_fail (width != NULL);
+ g_return_if_fail (height != NULL);
+ g_return_if_fail (x != NULL);
+ g_return_if_fail (y != NULL);
+
+ w = (gdouble) gtk_widget_get_allocated_width (GTK_WIDGET (self));
+ h = (gdouble) gtk_widget_get_allocated_height (GTK_WIDGET (self));
+
+ // Set the size of the display.
+ display_ratio = (gdouble) self->aspect_ratio;
+ allocated_ratio = w / h;
+
+ // If the screen is wider than the video…
+ if (allocated_ratio > display_ratio) {
+ *height = h;
+ *width = (gdouble) (h * display_ratio);
+ }
+ else {
+ *width = w;
+ *height = (gdouble) (w / display_ratio);
+ }
+
+ // Set the position of the display.
+ *x = (w - *width) / 2;
+ *y = (h - *height) / 2;
+}
+
+// From Antons's OpenGL 4 Tutorial
+float points[] = {
+ 0.0f, 0.5f, 0.0f,
+ 0.5f, -0.5f, 0.0f,
+ -0.5f, -0.5f, 0.0f
+};
+
+static void
+retro_gl_display_realize (RetroGLDisplay *self)
+{
+ GLuint vertex_buffer_object = 0;
+ GLuint vertex_array_object = 0;
+ GLuint vertex_shader;
+ GLuint fragment_shader;
+ GLuint shader_program;
+ GBytes *shader_source_bytes;
+ const gchar *shader_source;
+
+ gtk_gl_area_make_current (GTK_GL_AREA (self));
+
+ // Prepare the vertex buffer
+ glGenBuffers (1, &vertex_buffer_object);
+ glBindBuffer (GL_ARRAY_BUFFER, vertex_buffer_object);
+ glBufferData (GL_ARRAY_BUFFER, 9 * sizeof(float), points, GL_STATIC_DRAW);
+
+ // Prepare the vertex array
+ glGenVertexArrays (1, &vertex_array_object);
+ glBindVertexArray (vertex_array_object);
+ glEnableVertexAttribArray (0);
+ glBindBuffer (GL_ARRAY_BUFFER, vertex_buffer_object);
+ glVertexAttribPointer (0, 3, GL_FLOAT, GL_FALSE, 0, NULL);
+
+ // Prepare the vertex shader
+ shader_source_bytes = g_resources_lookup_data ("/org/gnome/Retro/retro-gl-display-vertex.glsl", 0, NULL);
+ shader_source = g_bytes_get_data (shader_source_bytes, NULL);
+ vertex_shader = glCreateShader (GL_VERTEX_SHADER);
+ glShaderSource (vertex_shader, 1, &shader_source, NULL);
+ glCompileShader (vertex_shader);
+ g_bytes_unref (shader_source_bytes);
+
+ // Prepare the fragment shader
+ shader_source_bytes = g_resources_lookup_data ("/org/gnome/Retro/retro-gl-display-fragment.glsl", 0, NULL);
+ shader_source = g_bytes_get_data (shader_source_bytes, NULL);
+ fragment_shader = glCreateShader (GL_FRAGMENT_SHADER);
+ glShaderSource (fragment_shader, 1, &shader_source, NULL);
+ glCompileShader (fragment_shader);
+ g_bytes_unref (shader_source_bytes);
+
+ // Prepare the shader program
+ shader_program = glCreateProgram();
+ glAttachShader (shader_program, vertex_shader);
+ glAttachShader (shader_program, fragment_shader);
+ glLinkProgram (shader_program);
+
+ self->vertex_array_object = vertex_array_object;
+ self->shader_program = shader_program;
+}
+
+static void
+retro_gl_display_unrealize (RetroGLDisplay *self)
+{
+ gtk_gl_area_make_current (GTK_GL_AREA (self));
+
+ glDeleteVertexArrays (1, &self->vertex_array_object);
+ glDeleteProgram (self->shader_program);
+}
+
+static gboolean
+retro_gl_display_render (RetroGLDisplay *self)
+{
+ glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+ glUseProgram(self->shader_program);
+ glBindVertexArray(self->vertex_array_object);
+ glDrawArrays(GL_TRIANGLES, 0, 3);
+
+ return FALSE;
+}
+
+static void
+retro_gl_display_finalize (GObject *object)
+{
+ RetroGLDisplay *self = (RetroGLDisplay *) object;
+
+ if (self->core != NULL)
+ g_object_unref (self->core);
+ if (self->pixbuf != NULL)
+ g_object_unref (self->pixbuf);
+
+ G_OBJECT_CLASS (retro_gl_display_parent_class)->finalize (object);
+}
+
+static void
+retro_gl_display_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ RetroGLDisplay *self = RETRO_GL_DISPLAY (object);
+
+ switch (prop_id) {
+ case PROP_PIXBUF:
+ g_value_set_object (value, retro_gl_display_get_pixbuf (self));
+
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+
+ break;
+ }
+}
+
+static void
+retro_gl_display_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ RetroGLDisplay *self = RETRO_GL_DISPLAY (object);
+
+ switch (prop_id) {
+ case PROP_PIXBUF:
+ retro_gl_display_set_pixbuf (self, g_value_get_object (value));
+
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+
+ break;
+ }
+}
+
+static void
+retro_gl_display_class_init (RetroGLDisplayClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->finalize = retro_gl_display_finalize;
+ object_class->get_property = retro_gl_display_get_property;
+ object_class->set_property = retro_gl_display_set_property;
+
+ properties[PROP_PIXBUF] =
+ g_param_spec_object ("pixbuf",
+ "Pixbuf",
+ "The displayed pixbuf",
+ gdk_pixbuf_get_type (),
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_NAME |
+ G_PARAM_STATIC_NICK |
+ G_PARAM_STATIC_BLURB);
+ g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_PIXBUF, properties[PROP_PIXBUF]);
+}
+
+static void
+queue_draw (GObject *sender,
+ GParamSpec *pspec,
+ gpointer self)
+{
+ gtk_widget_queue_draw (GTK_WIDGET (self));
+}
+
+static void
+retro_gl_display_init (RetroGLDisplay *self)
+{
+ g_signal_connect_object (G_OBJECT (self),
+ "realize",
+ (GCallback) retro_gl_display_realize,
+ GTK_WIDGET (self),
+ 0);
+
+ g_signal_connect_object (G_OBJECT (self),
+ "unrealize",
+ (GCallback) retro_gl_display_unrealize,
+ GTK_WIDGET (self),
+ 0);
+
+ g_signal_connect_object (G_OBJECT (self),
+ "render",
+ (GCallback) retro_gl_display_render,
+ GTK_WIDGET (self),
+ 0);
+
+ self->filter = RETRO_VIDEO_FILTER_SMOOTH;
+
+ g_signal_connect_object (G_OBJECT (self),
+ "notify::sensitive",
+ (GCallback) queue_draw,
+ GTK_WIDGET (self),
+ 0);
+
+ g_signal_connect_object (G_OBJECT (self),
+ "notify::pixbuf",
+ (GCallback) queue_draw,
+ GTK_WIDGET (self),
+ 0);
+}
+
+static void
+retro_gl_display_on_video_output (RetroCore *sender,
+ RetroPixdata *pixdata,
+ gpointer user_data)
+{
+ RetroGLDisplay *self = RETRO_GL_DISPLAY (user_data);
+
+ GdkPixbuf *pixbuf;
+
+ g_return_if_fail (self != NULL);
+
+ self->aspect_ratio = retro_pixdata_get_aspect_ratio (pixdata);
+ pixbuf = retro_pixdata_to_pixbuf (pixdata);
+ retro_gl_display_set_pixbuf (self, pixbuf);
+
+ if (pixbuf != NULL)
+ g_object_unref (pixbuf);
+}
+
+/* Public */
+
+/**
+ * retro_gl_display_set_core:
+ * @self: a #RetroGLDisplay
+ * @core: (nullable): a #RetroCore, or %NULL
+ *
+ * Sets @core as the #RetroCore displayed by @self.
+ */
+void
+retro_gl_display_set_core (RetroGLDisplay *self,
+ RetroCore *core)
+{
+ g_return_if_fail (self != NULL);
+
+ if (self->core == core)
+ return;
+
+ if (self->core != NULL) {
+ g_signal_handler_disconnect (G_OBJECT (self->core), self->on_video_output_id);
+ g_clear_object (&self->core);
+ }
+
+ if (core != NULL) {
+ self->core = g_object_ref (core);
+ self->on_video_output_id = g_signal_connect_object (core, "video-output", (GCallback)
retro_gl_display_on_video_output, self, 0);
+ }
+}
+
+/**
+ * retro_gl_display_get_pixbuf:
+ * @self: a #RetroGLDisplay
+ *
+ * Gets the currently displayed video frame.
+ *
+ * Returns: (transfer none): a #GdkPixbuf
+ */
+GdkPixbuf *
+retro_gl_display_get_pixbuf (RetroGLDisplay *self)
+{
+ g_return_val_if_fail (self != NULL, NULL);
+
+ return self->pixbuf;
+}
+
+/**
+ * retro_gl_display_set_pixbuf:
+ * @self: a #RetroGLDisplay
+ * @pixbuf: a #GdkPixbuf
+ *
+ * Sets @pixbuf as the currently displayed video frame.
+ */
+void
+retro_gl_display_set_pixbuf (RetroGLDisplay *self,
+ GdkPixbuf *pixbuf)
+{
+ g_return_if_fail (self != NULL);
+
+ if (self->pixbuf == pixbuf)
+ return;
+
+ g_clear_object (&self->pixbuf);
+
+ if (pixbuf != NULL)
+ self->pixbuf = g_object_ref (pixbuf);
+
+ g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_PIXBUF]);
+}
+
+/**
+ * retro_gl_display_set_filter:
+ * @self: a #RetroGLDisplay
+ * @filter: a #RetroVideoFilter
+ *
+ * Sets the video filter to use to render the core's video on @self.
+ */
+void
+retro_gl_display_set_filter (RetroGLDisplay *self,
+ RetroVideoFilter filter)
+{
+ g_return_if_fail (self != NULL);
+
+ self->filter = filter;
+ gtk_widget_queue_draw (GTK_WIDGET (self));
+}
+
+/**
+ * retro_gl_display_get_coordinates_on_display:
+ * @self: a #RetroGLDisplay
+ * @widget_x: the abscissa on @self
+ * @widget_y: the ordinate on @self
+ * @display_x: return location for a the abscissa on the core's video display
+ * @display_y: return location for a the ordinate on the core's video display
+ *
+ * Gets coordinates on the core's video output from coordinates on @self, and
+ * whether the point is inside the core's video display.
+ *
+ * Returns: whether the coordinates are on the core's video display
+ */
+gboolean
+retro_gl_display_get_coordinates_on_display (RetroGLDisplay *self,
+ gdouble widget_x,
+ gdouble widget_y,
+ gdouble *display_x,
+ gdouble *display_y)
+{
+ gdouble w = 0.0;
+ gdouble h = 0.0;
+ gdouble x = 0.0;
+ gdouble y = 0.0;
+
+ g_return_val_if_fail (self != NULL, FALSE);
+ g_return_val_if_fail (display_x != NULL, FALSE);
+ g_return_val_if_fail (display_y != NULL, FALSE);
+
+ retro_gl_display_get_video_box (self, &w, &h, &x, &y);
+
+ // Return coordinates as a [-1.0, 1.0] scale, (0.0, 0.0) is the center.
+ *display_x = ((widget_x - x) * 2.0 - w) / w;
+ *display_y = ((widget_y - y) * 2.0 - h) / h;
+
+ return (-1.0 <= *display_x) && (*display_x <= 1.0) &&
+ (-1.0 <= *display_y) && (*display_y <= 1.0);
+}
+
+/**
+ * retro_gl_display_new:
+ *
+ * Creates a new #RetroGLDisplay.
+ *
+ * Returns: (transfer full): a new #RetroGLDisplay
+ */
+RetroGLDisplay *
+retro_gl_display_new (void)
+{
+ return g_object_new (RETRO_TYPE_GL_DISPLAY, NULL);
+}
diff --git a/retro-gtk/retro-gl-display.h b/retro-gtk/retro-gl-display.h
new file mode 100644
index 0000000..2a0b1c7
--- /dev/null
+++ b/retro-gtk/retro-gl-display.h
@@ -0,0 +1,36 @@
+// This file is part of retro-gtk. License: GPL-3.0+.
+
+#ifndef RETRO_GL_DISPLAY_H
+#define RETRO_GL_DISPLAY_H
+
+#if !defined(__RETRO_GTK_INSIDE__) && !defined(RETRO_GTK_COMPILATION)
+# error "Only <retro-gtk.h> can be included directly."
+#endif
+
+#include <gtk/gtk.h>
+#include "retro-core.h"
+#include "retro-video-filter.h"
+
+G_BEGIN_DECLS
+
+#define RETRO_TYPE_GL_DISPLAY (retro_gl_display_get_type())
+
+G_DECLARE_FINAL_TYPE (RetroGLDisplay, retro_gl_display, RETRO, GL_DISPLAY, GtkGLArea)
+
+RetroGLDisplay *retro_gl_display_new (void);
+GdkPixbuf *retro_gl_display_get_pixbuf (RetroGLDisplay *self);
+void retro_gl_display_set_pixbuf (RetroGLDisplay *self,
+ GdkPixbuf *pixbuf);
+void retro_gl_display_set_core (RetroGLDisplay *self,
+ RetroCore *core);
+void retro_gl_display_set_filter (RetroGLDisplay *self,
+ RetroVideoFilter filter);
+gboolean retro_gl_display_get_coordinates_on_display (RetroGLDisplay *self,
+ gdouble widget_x,
+ gdouble widget_y,
+ gdouble *display_x,
+ gdouble *display_y);
+
+G_END_DECLS
+
+#endif /* RETRO_GL_DISPLAY_H */
diff --git a/retro-gtk/retro-gtk-0.14.deps b/retro-gtk/retro-gtk-0.14.deps
index ec2cb16..2fc8f4c 100644
--- a/retro-gtk/retro-gtk-0.14.deps
+++ b/retro-gtk/retro-gtk-0.14.deps
@@ -1,5 +1,6 @@
cairo
+epoxy
gio-2.0
glib-2.0
gmodule-2.0
diff --git a/retro-gtk/retro-gtk.gresource.xml b/retro-gtk/retro-gtk.gresource.xml
new file mode 100644
index 0000000..e9a9c5f
--- /dev/null
+++ b/retro-gtk/retro-gtk.gresource.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<gresources>
+ <gresource prefix="/org/gnome/Retro">
+ <file>retro-gl-display-fragment.glsl</file>
+ <file>retro-gl-display-vertex.glsl</file>
+ </gresource>
+</gresources>
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]