try not setting caps in pad_added callback: in complains
[opo.git] / gtk-app.c
blobf5ae2a1949ed171b1479693831b838478fe0d10c
1 /*N way video splitter */
2 #include <gst/gst.h>
3 #include <gtk/gtk.h>
4 #include <gst/interfaces/xoverlay.h>
5 #include <gdk/gdk.h>
6 #include <gdk/gdkx.h>
7 #include "gtk-app.h"
9 static GstCaps *
10 make_good_caps(){
11 GstCaps *caps;
12 if (option_autosize && option_content){
13 //automatically find size
14 caps = gst_caps_new_simple("video/x-raw-yuv",
15 NULL);
16 gst_caps_merge(caps, gst_caps_new_simple("video/x-raw-rgb",
17 NULL));
19 else {
20 caps = gst_caps_new_simple("video/x-raw-yuv",
21 "width", G_TYPE_INT, option_screens * option_width,
22 "height", G_TYPE_INT, option_height,
23 NULL);
24 gst_caps_merge(caps, gst_caps_new_simple("video/x-raw-rgb",
25 "width", G_TYPE_INT, option_screens * option_width,
26 "height", G_TYPE_INT, option_height,
27 NULL));
29 return caps;
32 static void
33 post_tee_pipeline(GstPipeline *pipeline, GstElement *tee, GstElement *sink,
34 int crop_left, int crop_right){
35 GstElement *queue = gst_element_factory_make("queue", NULL);
36 GstElement *crop = gst_element_factory_make("videocrop", NULL);
38 g_object_set(G_OBJECT(crop),
39 "top", 0,
40 "bottom", 0,
41 "left", crop_left,
42 "right", crop_right,
43 NULL);
45 gst_bin_add_many(GST_BIN(pipeline),
46 queue,
47 crop,
48 sink,
49 NULL);
51 gst_element_link_many(tee,
52 queue,
53 crop,
54 sink,
55 NULL);
59 static void
60 pad_added_cb (GstElement *decodebin, GstPad *pad, GstElement *tee)
62 //GstCaps *caps = make_good_caps();
63 GstPad *tee_pad = gst_element_get_static_pad(tee, "sink");
64 //gst_pad_set_caps(tee_pad, caps);
65 gst_pad_link(pad, tee_pad);
66 gst_object_unref(tee_pad);
67 //gtk_window_set_default_size(GTK_WINDOW(window), option_width, option_height);
71 static GstElement *
72 pre_tee_pipeline(GstPipeline *pipeline){
73 if (pipeline == NULL){
74 pipeline = GST_PIPELINE(gst_pipeline_new("wha_pipeline"));
76 GstElement *src;
77 if (option_content) {
78 char *uri;
79 if (g_str_has_prefix(option_content, "/")){
80 uri = g_strconcat("file://",
81 option_content,
82 NULL);
84 else {
85 char *cwd = g_get_current_dir();
86 uri = g_strconcat("file://",
87 cwd,
88 "/",
89 option_content,
90 NULL);
91 g_free(cwd);
93 g_print("uri is '%s'\n", uri);
95 src = gst_element_factory_make("uridecodebin", NULL);
96 g_object_set(G_OBJECT(src),
97 "uri", uri,
98 NULL);
99 g_free(uri);
101 else {
102 char * src_name = (option_fake) ? "videotestsrc" : "v4l2src";
103 src = gst_element_factory_make(src_name, NULL);
104 if (option_fake == 2){//set some properties for an interesting picture
105 g_object_set(G_OBJECT(src),
106 "pattern", 14, //"zone-plate"
107 "kt2", 0,
108 "kx2", 3,
109 "ky2", 3,
110 "kt", 3,
111 "kxy", 2,
112 NULL);
116 GstElement *tee = gst_element_factory_make("tee", NULL);
118 gst_bin_add(GST_BIN(pipeline), src);
119 gst_bin_add(GST_BIN(pipeline), tee);
121 if (option_content) {
122 //Can't link it yet!
123 g_signal_connect(src, "pad-added",
124 G_CALLBACK(pad_added_cb), tee);
126 else{
127 GstCaps *caps = make_good_caps();
128 gst_element_link_filtered(src,
129 tee,
130 caps);
132 return tee;
136 static void hide_mouse(GtkWidget *widget){
137 GdkWindow *w = GDK_WINDOW(widget->window);
138 GdkDisplay *display = gdk_display_get_default();
139 GdkCursor *cursor = gdk_cursor_new_for_display(display, GDK_BLANK_CURSOR);
140 gdk_window_set_cursor(w, cursor);
141 gdk_cursor_unref (cursor);
146 static GstBusSyncReply
147 sync_bus_call(GstBus *bus, GstMessage *msg, gpointer data)
149 // ignore anything but 'prepare-xwindow-id' element messages
150 if ((GST_MESSAGE_TYPE(msg) != GST_MESSAGE_ELEMENT) ||
151 (! gst_structure_has_name(msg->structure, "prepare-xwindow-id"))){
152 return GST_BUS_PASS;
154 window_t *windows = (window_t *)data;
155 g_print("Got prepare-xwindow-id msg. \n");
156 //connect this one up with the right window.
157 GstElement *sink = GST_ELEMENT(GST_MESSAGE_SRC(msg));
158 int done = 0;
160 g_print("found sink %p\n", sink);
161 for (int i = 0; i < option_screens; i++){
162 const window_t *w = windows + i;
163 if (w->sink == sink){
164 gst_x_overlay_set_xwindow_id(GST_X_OVERLAY(sink), w->xid);
165 g_print("connected sink %d to window %lu\n", i, w->xid);
166 hide_mouse(w->widget);
167 done = 1;
168 break;
172 if (! done){
173 g_print("couldn't find a window for this sink!\n");
176 gst_message_unref(msg);
177 return GST_BUS_DROP;
180 static void
181 toggle_fullscreen(GtkWidget *widget){
182 GdkWindowState state = gdk_window_get_state(GDK_WINDOW(widget->window));
183 if (state == GDK_WINDOW_STATE_FULLSCREEN){
184 gtk_window_unfullscreen(GTK_WINDOW(widget));
186 else{
187 gtk_window_fullscreen(GTK_WINDOW(widget));
191 static gboolean
192 key_press_event_cb(GtkWidget *widget, GdkEventKey *event, gpointer data)
194 g_print("got key %c\n", event->keyval);
195 switch (event->keyval){
196 case 'f':
197 toggle_fullscreen(widget);
198 break;
199 case 'q':
200 g_signal_emit_by_name(widget, "destroy");
201 break;
202 default:
203 break;
205 return TRUE;
208 static void
209 destroy_cb(GtkWidget *widget, gpointer data)
211 GMainLoop *loop = (GMainLoop*) data;
212 g_print("Window destroyed\n");
213 g_main_loop_quit(loop);
214 gtk_widget_destroy(widget);
217 static void
218 video_widget_realize_cb(GtkWidget *widget, gpointer data)
220 window_t *w = (window_t *)data;
221 w->xid = GDK_WINDOW_XID(GDK_WINDOW(widget->window));
222 g_print("realised window %d with XID %lu\n", w->id, w->xid);
223 hide_mouse(widget);
227 static void
228 set_up_window(GMainLoop *loop, window_t *w, int screen_no){
229 GtkWidget *window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
230 w->widget = window;
231 w->sink = gst_element_factory_make("xvimagesink", NULL);
232 w->id = screen_no;
233 g_signal_connect(w->widget, "realize", G_CALLBACK(video_widget_realize_cb), w);
235 static const GdkColor black = {0, 0, 0, 0};
236 //XXX need to resize once video size is known
237 gtk_window_set_default_size(GTK_WINDOW(window), option_width, option_height);
239 if (option_fullscreen){
240 gtk_window_fullscreen(GTK_WINDOW(window));
242 int xscreen_no;
243 GdkScreen * screen;
244 if (option_x_screens <= 1){
245 screen = gdk_screen_get_default();
246 /*Xscreen number might not be 0, but 0 is right assumption for
247 calculations below.*/
248 xscreen_no = 0;
250 else{
251 xscreen_no = screen_no * option_x_screens / option_screens;
252 char display[sizeof(":0.00")];
253 g_snprintf(display, sizeof(display), ":0.%d", xscreen_no);
254 screen = gdk_display_get_screen(gdk_display_get_default(), xscreen_no);
255 g_print("putting window %d on screen %s (%p)\n",
256 screen_no, display, screen);
257 gtk_window_set_screen(GTK_WINDOW(window), screen);
258 g_object_set(G_OBJECT(w->sink),
259 "display", display,
260 NULL);
262 int x, y;
263 int monitors = gdk_screen_get_n_monitors(screen);
264 int monitor_no = screen_no % monitors;
265 if (option_force_multiscreen){
266 /*Ask gtk to find the appropriate monitor (assuming each Xscreen has the
267 same number of monitors).
269 GdkRectangle monitor_shape;
270 gdk_screen_get_monitor_geometry(screen, screen_no, &monitor_shape);
271 x = monitor_shape.x + 1;
272 y = monitor_shape.y + 1;
274 else {
275 /*simple placement heuristic, places windows evenly across display.
276 This should work with equally sized monitors/projectors, and allows
277 testing on a single monitor. */
278 int width = gdk_screen_get_width(screen);
279 x = (width / (option_screens/ option_x_screens)) * monitor_no + 1;
280 y = 50;
283 gtk_window_move(GTK_WINDOW(window), x, y);
284 g_print("putting window %d at %d\n", screen_no, x);
286 // attach key press signal to key press callback
287 gtk_widget_set_events(window, GDK_KEY_PRESS_MASK);
288 g_signal_connect(G_OBJECT(window), "key-press-event", G_CALLBACK(key_press_event_cb), NULL);
289 g_signal_connect(G_OBJECT(window), "destroy", G_CALLBACK(destroy_cb), loop);
291 gtk_widget_modify_bg(window, GTK_STATE_NORMAL, &black);
292 gtk_widget_show_all(window);
293 hide_mouse(window);
296 static GstElement *
297 gstreamer_start(GMainLoop *loop, window_t windows[MAX_SCREENS])
299 int input_width = option_screens * option_width;
300 //crop _left/_right are amount to cut, not coordinate of cut
301 int crop_left = 0;
302 int crop_right = input_width - option_width;
304 GstElement *pipeline = gst_pipeline_new("e_wha");
305 GstElement *tee = pre_tee_pipeline(GST_PIPELINE(pipeline));
307 int i;
308 for (i = 0; i < option_screens; i++){
309 window_t *w = windows + i;
310 set_up_window(loop, w, i);
311 post_tee_pipeline(GST_PIPELINE(pipeline), tee, w->sink, crop_left, crop_right);
312 crop_left += option_width;
313 crop_right -= option_width;
316 GstBus *bus = gst_pipeline_get_bus(GST_PIPELINE(pipeline));
317 gst_bus_set_sync_handler(bus, (GstBusSyncHandler)sync_bus_call, windows);
318 gst_object_unref(bus);
320 gst_element_set_state(pipeline, GST_STATE_PLAYING);
321 return pipeline;
324 static void
325 gstreamer_stop(GstElement *pipeline)
327 gst_element_set_state(pipeline, GST_STATE_NULL);
328 gst_object_unref(pipeline);
331 gint main (gint argc, gchar *argv[])
333 //initialise threads before any gtk stuff (because not using gtk_init)
334 static window_t windows[MAX_SCREENS];
335 g_type_init();
336 g_thread_init(NULL);
337 /*this is more complicated than plain gtk_init/gst_init, so that options from
338 all over can be gathered and presented together.
340 GOptionGroup *gst_opts = gst_init_get_option_group();
341 GOptionGroup *gtk_opts = gtk_get_option_group(TRUE);
342 GOptionContext *ctx = g_option_context_new("...!");
343 g_option_context_add_main_entries(ctx, entries, NULL);
344 g_option_context_add_group(ctx, gst_opts);
345 g_option_context_add_group(ctx, gtk_opts);
346 GError *error = NULL;
347 if (!g_option_context_parse(ctx, &argc, &argv, &error)){
348 g_print ("Error initializing: %s\n", GST_STR_NULL(error->message));
349 exit (1);
351 g_option_context_free(ctx);
352 /*sanitise options*/
353 if (option_x_screens > MAX_X_SCREENS)
354 option_x_screens = MAX_X_SCREENS;
355 if (option_x_screens < MIN_X_SCREENS)
356 option_x_screens = MIN_X_SCREENS;
357 if (option_x_screens > MAX_X_SCREENS)
358 option_screens = MAX_SCREENS;
359 if (option_screens < MIN_SCREENS)
360 option_screens = MIN_SCREENS;
361 if (option_width > MAX_PIXELS)
362 option_width = MAX_PIXELS;
363 if (option_height > MAX_PIXELS)
364 option_height = MAX_PIXELS;
365 /*setting width, height <= 0 makes sizing automatic (default) */
366 if (option_width <= 0){
367 option_width = DEFAULT_WIDTH;
368 option_autosize = 1;
370 if (option_height <= 0){
371 option_height = DEFAULT_HEIGHT;
372 option_autosize = 1;
375 GMainLoop *loop = g_main_loop_new(NULL, FALSE);
377 GstElement *pipeline = gstreamer_start(loop, windows);
379 g_main_loop_run(loop);
381 gstreamer_stop(pipeline);
382 return 0;