move options to gtk-app.h, so new implementations can share them
[sparrow.git] / gtk-app.c
blob6e0bf8b71bce4a5a8f46144b4e880be25f5eaeb7
1 /*
2 initially based on an example by Tristan Matthews
3 http://tristanswork.blogspot.com/2008/09/fullscreen-video-in-gstreamer-with-gtk.html
4 */
5 #include <gst/gst.h>
6 #include <gtk/gtk.h>
7 #include <gst/interfaces/xoverlay.h>
8 #include <gdk/gdk.h>
9 #include <gdk/gdkx.h>
10 #include "gtk-app.h"
13 static void hide_mouse(GtkWidget *widget){
14 GdkWindow *w = GDK_WINDOW(widget->window);
15 GdkDisplay *display = gdk_display_get_default();
16 GdkCursor *cursor = gdk_cursor_new_for_display(display, GDK_BLANK_CURSOR);
17 gdk_window_set_cursor(w, cursor);
18 gdk_cursor_unref (cursor);
21 static void
22 mjpeg_branch(GstPipeline *pipeline, GstElement *tee)
24 /* ! jpegenc ! avimux ! filesink location=mjpeg.avi */
25 GstElement *queue = gst_element_factory_make("queue", NULL);
26 GstElement *jpegenc = gst_element_factory_make("jpegenc", NULL);
27 GstElement *avimux = gst_element_factory_make("avimux", NULL);
28 GstElement *filesink = gst_element_factory_make("filesink", NULL);
29 GstElement *cs = gst_element_factory_make("ffmpegcolorspace", NULL);
30 g_object_set(G_OBJECT(filesink),
31 "location", option_avi,
32 NULL);
33 gst_bin_add_many(GST_BIN(pipeline),
34 queue,
35 cs,
36 jpegenc,
37 avimux,
38 filesink,
39 NULL);
40 gst_element_link_many(tee,
41 queue,
42 cs,
43 jpegenc,
44 avimux,
45 filesink,
46 NULL);
49 static void
50 post_tee_pipeline(GstPipeline *pipeline, GstElement *tee, GstElement *sink,
51 int rngseed, int colour, int timer, int debug, char *save, char *reload){
52 GstElement *queue = gst_element_factory_make("queue", NULL);
53 GstElement *sparrow = gst_element_factory_make("sparrow", NULL);
54 GstElement *caps_posteriori = gst_element_factory_make("capsfilter", NULL);
55 GstElement *cs_posteriori = gst_element_factory_make("ffmpegcolorspace", NULL);
57 g_object_set(G_OBJECT(caps_posteriori), "caps",
58 gst_caps_new_simple ("video/x-raw-rgb",
59 COMMON_CAPS), NULL);
61 g_object_set(G_OBJECT(sparrow),
62 "timer", timer,
63 "debug", debug,
64 "rngseed", rngseed,
65 "colour", colour,
66 "serial", option_serial,
67 NULL);
68 if (reload){
69 g_object_set(G_OBJECT(sparrow),
70 "reload", reload,
71 NULL);
73 if (save){
74 g_object_set(G_OBJECT(sparrow),
75 "save", save,
76 NULL);
79 gst_bin_add_many (GST_BIN(pipeline),
80 queue,
81 sparrow,
82 caps_posteriori,
83 cs_posteriori,
84 sink,
85 NULL);
87 gst_element_link_many(tee,
88 queue,
89 sparrow,
90 caps_posteriori,
91 cs_posteriori,
92 sink,
93 NULL);
96 static GstElement *
97 pre_tee_pipeline(GstPipeline *pipeline){
98 if (pipeline == NULL){
99 pipeline = GST_PIPELINE(gst_pipeline_new("sparrow_pipeline"));
101 char * src_name = (option_fake) ? "videotestsrc" : "v4l2src";
102 GstElement *src = gst_element_factory_make(src_name, NULL);
103 GstElement *caps_priori = gst_element_factory_make("capsfilter", NULL);
104 GstElement *cs_priori = gst_element_factory_make("ffmpegcolorspace", NULL);
105 GstElement *caps_interiori = gst_element_factory_make("capsfilter", NULL);
106 GstElement *tee = gst_element_factory_make ("tee", NULL);
108 g_object_set(G_OBJECT(caps_priori), "caps",
109 gst_caps_new_simple ("video/x-raw-yuv",
110 "format", GST_TYPE_FOURCC, GST_MAKE_FOURCC('Y', 'U', 'Y', '2'),
111 COMMON_CAPS), NULL);
113 g_object_set(G_OBJECT(caps_interiori), "caps",
114 gst_caps_new_simple ("video/x-raw-rgb",
115 COMMON_CAPS), NULL);
117 gst_bin_add_many(GST_BIN(pipeline),
118 src,
119 caps_priori,
120 cs_priori,
121 //caps_interiori,
122 tee,
123 NULL);
125 gst_element_link_many(src,
126 caps_priori,
127 cs_priori,
128 //caps_interiori,
129 tee,
130 NULL);
131 return tee;
135 static GstPipeline *
136 make_multi_pipeline(windows_t *windows, int count)
138 GstPipeline *pipeline = GST_PIPELINE(gst_pipeline_new("sparrow_pipeline"));
139 GstElement *tee = pre_tee_pipeline(pipeline);
140 char *reload = NULL;
141 char *save = NULL;
142 int i;
143 for (i = 0; i < count; i++){
144 GstElement *sink = windows->sinks[i];
145 //args are:
146 //(pipeline, tee, sink, int rngseed, int colour, timer flag, int debug flag)
147 /* timer should only run on one of them. colour >= 3 is undefined */
148 int debug = option_debug == i;
149 int timer = option_timer == i;
150 if (option_reload != NULL){
151 if (option_reload[i] == NULL){
152 g_critical("You can't reload some screens and not others!");
153 exit(1);
155 reload = option_reload[i];
157 if (option_save && option_save[i]){
158 save = option_save[i];
160 post_tee_pipeline(pipeline, tee, sink, i, i + 1, timer, debug, save, reload);
162 if (option_avi){
163 /*add a branch saving the video to a file */
164 mjpeg_branch(pipeline, tee);
167 return pipeline;
171 static void
172 bus_call(GstBus * bus, GstMessage *msg, gpointer data)
174 windows_t *windows = (windows_t *)data;
175 if (GST_MESSAGE_TYPE(msg) == GST_MESSAGE_ELEMENT &&
176 gst_structure_has_name(msg->structure, "prepare-xwindow-id")){
177 g_print("Got prepare-xwindow-id msg. option screens: %d\n", option_screens);
178 for (int i = 0; i < option_screens; i++){
179 gst_x_overlay_set_xwindow_id(GST_X_OVERLAY(windows->sinks[i]),
180 windows->xwindows[i]);
181 g_print("connected sink %d to window %lu\n", i, windows->xwindows[i]);
182 hide_mouse(windows->gtk_windows[i]);
187 static void
188 toggle_fullscreen(GtkWidget *widget){
189 GdkWindowState state = gdk_window_get_state(GDK_WINDOW(widget->window));
190 if (state == GDK_WINDOW_STATE_FULLSCREEN){
191 gtk_window_unfullscreen(GTK_WINDOW(widget));
193 else{
194 gtk_window_fullscreen(GTK_WINDOW(widget));
198 static gboolean
199 key_press_event_cb(GtkWidget *widget, GdkEventKey *event, gpointer data)
201 g_print("got key %c\n", event->keyval);
202 switch (event->keyval){
203 case 'f':
204 toggle_fullscreen(widget);
205 break;
206 case 'q':
207 g_signal_emit_by_name(widget, "destroy");
208 break;
209 default:
210 break;
212 return TRUE;
215 void destroy_cb(GtkWidget * widget, gpointer data)
217 GMainLoop *loop = (GMainLoop*) data;
218 g_print("Window destroyed\n");
219 g_main_loop_quit(loop);
222 static void
223 video_widget_realize_cb(GtkWidget *widget, gpointer data)
225 windows_t *windows = (windows_t *)data;
226 int r = windows->realised;
227 if (r < MAX_SCREENS){
228 windows->xwindows[r] = GDK_WINDOW_XID(GDK_WINDOW(widget->window));
229 g_print("realised window %d with XID %lu\n", r, windows->xwindows[r]);
231 else {
232 g_print("wtf, there seem to be %d windows!\n", r);
234 windows->realised++;
235 hide_mouse(widget);
239 static void
240 set_up_window(GMainLoop *loop, GtkWidget *window, int screen_no){
241 static const GdkColor black = {0, 0, 0, 0};
242 gtk_window_set_default_size(GTK_WINDOW(window), WIDTH, HEIGHT);
244 if (option_fullscreen){
245 gtk_window_fullscreen(GTK_WINDOW(window));
248 /*if more than one screen is requested, set the screen number.
249 otherwise let it fall were it falls */
250 if (option_screens > 1 || option_first_screen){
251 /* "screen" is not the same thing as "monitor" */
252 GdkScreen * screen = gdk_screen_get_default();
253 int width = gdk_screen_get_width(screen);
254 /* XXX window selection is specific to equally sized windows arranged
255 horizontally. This could be generalised, perhaps using trial and
256 error */
257 gtk_window_move(GTK_WINDOW(window),
258 (width / 2 * screen_no + 200) % width, 50);
261 // attach key press signal to key press callback
262 gtk_widget_set_events(window, GDK_KEY_PRESS_MASK);
263 g_signal_connect(G_OBJECT(window), "key-press-event", G_CALLBACK(key_press_event_cb), NULL);
264 g_signal_connect(G_OBJECT(window), "destroy", G_CALLBACK(destroy_cb), loop);
266 gtk_widget_modify_bg(window, GTK_STATE_NORMAL, &black);
267 gtk_widget_show_all(window);
268 hide_mouse(window);
272 gint main (gint argc, gchar *argv[])
274 //initialise threads before any gtk stuff (because not using gtk_init)
275 g_thread_init(NULL);
276 /*this is more complicated than plain gtk_init/gst_init, so that options from
277 all over can be gathered and presented together.
279 GOptionGroup *gst_opts = gst_init_get_option_group();
280 GOptionGroup *gtk_opts = gtk_get_option_group(TRUE);
281 GOptionContext *ctx = g_option_context_new("...!");
282 g_option_context_add_main_entries(ctx, entries, NULL);
283 g_option_context_add_group(ctx, gst_opts);
284 g_option_context_add_group(ctx, gtk_opts);
285 GError *error = NULL;
286 if (!g_option_context_parse(ctx, &argc, &argv, &error)){
287 g_print ("Error initializing: %s\n", GST_STR_NULL(error->message));
288 exit (1);
290 g_option_context_free(ctx);
292 GMainLoop *loop = g_main_loop_new(NULL, FALSE);
294 windows_t windows;
295 windows.realised = 0;
297 int i;
298 for (i = 0; i < option_screens; i++){
299 GtkWidget *window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
300 g_signal_connect(window, "realize",
301 G_CALLBACK(video_widget_realize_cb), &windows);
302 /* set up sink here */
303 GstElement *sink = gst_element_factory_make("ximagesink", NULL);
304 set_up_window(loop, window, i + option_first_screen);
305 windows.gtk_windows[i] = window;
306 windows.sinks[i] = sink;
309 GstElement *pipeline = (GstElement *)make_multi_pipeline(&windows, option_screens);
311 GstBus *bus = gst_pipeline_get_bus(GST_PIPELINE(pipeline));
312 gst_bus_add_watch(bus, (GstBusFunc)bus_call, &windows);
313 gst_object_unref(bus);
315 gst_element_set_state(pipeline, GST_STATE_PLAYING);
317 g_main_loop_run(loop);
319 gst_element_set_state (pipeline, GST_STATE_NULL);
320 gst_object_unref (pipeline);
321 return 0;