UI trickery around creating new video
[opo.git] / opo.c
blob942f3c17d798cf322e7a2e97aaae99a274232e92
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 "opo.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;
33 static void
34 post_tee_pipeline(GstBin *bin, GstElement *tee, GstElement *sink,
35 int crop_left, int crop_right){
36 GstElement *queue = gst_element_factory_make("queue", NULL);
37 GstElement *crop = gst_element_factory_make("videocrop", NULL);
39 g_object_set(G_OBJECT(crop),
40 "top", 0,
41 "bottom", 0,
42 "left", crop_left,
43 "right", crop_right,
44 NULL);
46 gst_bin_add_many(bin,
47 queue,
48 crop,
49 sink,
50 NULL);
52 gst_element_link_many(tee,
53 queue,
54 crop,
55 sink,
56 NULL);
60 static void
61 about_to_finish_cb(GstElement *pipeline, char *uri)
63 g_object_set(G_OBJECT(pipeline),
64 "uri", uri,
65 NULL);
66 g_print("starting again with %s\n", uri);
71 static void hide_mouse(GtkWidget *widget){
72 GdkWindow *w = GDK_WINDOW(widget->window);
73 GdkDisplay *display = gdk_display_get_default();
74 GdkCursor *cursor = gdk_cursor_new_for_display(display, GDK_BLANK_CURSOR);
75 gdk_window_set_cursor(w, cursor);
76 gdk_cursor_unref (cursor);
81 static GstBusSyncReply
82 sync_bus_call(GstBus *bus, GstMessage *msg, gpointer data)
84 // ignore anything but 'prepare-xwindow-id' element messages
85 if ((GST_MESSAGE_TYPE(msg) != GST_MESSAGE_ELEMENT) ||
86 (! gst_structure_has_name(msg->structure, "prepare-xwindow-id"))){
87 return GST_BUS_PASS;
89 window_t *windows = (window_t *)data;
90 g_print("Got prepare-xwindow-id msg. \n");
91 //connect this one up with the right window.
92 GstElement *sink = GST_ELEMENT(GST_MESSAGE_SRC(msg));
93 int done = 0;
95 g_print("found sink %p\n", sink);
96 for (int i = 0; i < option_screens; i++){
97 const window_t *w = windows + i;
98 if (w->sink == sink){
99 gst_x_overlay_set_xwindow_id(GST_X_OVERLAY(sink), w->xid);
100 g_print("connected sink %d to window %lu\n", i, w->xid);
101 hide_mouse(w->widget);
102 done = 1;
103 break;
107 if (! done){
108 g_print("couldn't find a window for this sink!\n");
111 gst_message_unref(msg);
112 return GST_BUS_DROP;
115 static void
116 toggle_fullscreen(GtkWidget *widget){
117 GdkWindowState state = gdk_window_get_state(GDK_WINDOW(widget->window));
118 if (state == GDK_WINDOW_STATE_FULLSCREEN){
119 gtk_window_unfullscreen(GTK_WINDOW(widget));
121 else{
122 gtk_window_fullscreen(GTK_WINDOW(widget));
126 static gboolean
127 key_press_event_cb(GtkWidget *widget, GdkEventKey *event, gpointer data)
129 g_print("got key %c\n", event->keyval);
130 switch (event->keyval){
131 case 'f':
132 toggle_fullscreen(widget);
133 break;
134 case 'q':
135 g_signal_emit_by_name(widget, "destroy");
136 break;
137 default:
138 break;
140 return TRUE;
143 static void
144 destroy_cb(GtkWidget *widget, gpointer data)
146 GMainLoop *loop = (GMainLoop*) data;
147 g_print("Window destroyed\n");
148 g_main_loop_quit(loop);
149 gtk_widget_destroy(widget);
152 static void
153 video_widget_realize_cb(GtkWidget *widget, gpointer data)
155 window_t *w = (window_t *)data;
156 w->xid = GDK_WINDOW_XID(GDK_WINDOW(widget->window));
157 g_print("realised window %d with XID %lu\n", w->id, w->xid);
158 hide_mouse(widget);
162 static void
163 set_up_window(GMainLoop *loop, window_t *w, int screen_no){
164 GtkWidget *window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
165 w->widget = window;
166 w->sink = gst_element_factory_make("xvimagesink", NULL);
167 w->id = screen_no;
168 g_signal_connect(w->widget, "realize", G_CALLBACK(video_widget_realize_cb), w);
170 static const GdkColor black = {0, 0, 0, 0};
171 //XXX need to resize once video size is known
172 gtk_window_set_default_size(GTK_WINDOW(window), option_width, option_height);
174 if (option_fullscreen){
175 gtk_window_fullscreen(GTK_WINDOW(window));
177 int xscreen_no;
178 GdkScreen * xscreen;
179 if (option_x_screens <= 1){
180 xscreen = gdk_screen_get_default();
181 /*Xscreen number might not be 0, but 0 is right assumption for
182 calculations below.*/
183 xscreen_no = 0;
185 else{
186 xscreen_no = screen_no * option_x_screens / option_screens;
187 char display[sizeof(":0.00")];
188 g_snprintf(display, sizeof(display), ":0.%d", xscreen_no);
189 xscreen = gdk_display_get_screen(gdk_display_get_default(), xscreen_no);
190 gtk_window_set_screen(GTK_WINDOW(window), xscreen);
191 g_object_set(G_OBJECT(w->sink),
192 "display", display,
193 NULL);
195 int x, y;
196 int windows_per_xscreen = option_screens / option_x_screens;
197 int window_no = screen_no % windows_per_xscreen;
198 int monitors = gdk_screen_get_n_monitors(xscreen);
199 if (option_force_multiscreen){
200 /*Ask gtk to find the appropriate monitor (assuming each Xscreen has the
201 same number of monitors). */
202 if (window_no >= monitors){
203 g_print("asking for monitor %d which does not exist! (monitors %d)\n",
204 window_no, monitors);
206 GdkRectangle monitor_shape;
207 gdk_screen_get_monitor_geometry(xscreen, window_no, &monitor_shape);
208 x = monitor_shape.x + 1;
209 y = monitor_shape.y + 1;
211 else {
212 /*simple placement heuristic, places windows evenly across display.
213 This should work with equally sized monitors/projectors, and allows
214 testing on a single monitor.
216 int full_screen_width = gdk_screen_get_width(xscreen);
217 x = window_no * (full_screen_width / windows_per_xscreen) + 1;
218 y = 1;
221 gtk_window_move(GTK_WINDOW(window), x, y);
222 g_print("putting window %d on screen :0.%d at %d,%d\n",
223 screen_no, xscreen_no, x, y);
225 // attach key press signal to key press callback
226 gtk_widget_set_events(window, GDK_KEY_PRESS_MASK);
227 g_signal_connect(G_OBJECT(window), "key-press-event", G_CALLBACK(key_press_event_cb), NULL);
228 g_signal_connect(G_OBJECT(window), "destroy", G_CALLBACK(destroy_cb), loop);
230 gtk_widget_modify_bg(window, GTK_STATE_NORMAL, &black);
231 gtk_widget_show_all(window);
232 hide_mouse(window);
235 static inline char *
236 attempt_filename_to_uri(char *filename){
237 char *uri;
238 if (g_str_has_prefix(filename, "/")){
239 uri = g_strconcat("file://",
240 option_content,
241 NULL);
243 else {
244 char *cwd = g_get_current_dir();
245 uri = g_strconcat("file://",
246 cwd,
247 "/",
248 option_content,
249 NULL);
250 g_free(cwd);
252 return uri;
255 static GstPipeline *
256 pre_tee_pipeline(){
257 GstPipeline *pipeline;
258 if (option_content) {
259 char *uri;
260 if( g_str_has_prefix(option_content, "file://") ||
261 g_str_has_prefix(option_content, "http://") /* || others */
263 uri = option_content;
265 else{
266 uri = attempt_filename_to_uri(option_content);
268 g_print("uri is '%s'\n", uri);
269 pipeline = GST_PIPELINE(gst_element_factory_make("playbin2", NULL));
270 g_object_set(G_OBJECT(pipeline),
271 "uri", uri,
272 NULL);
273 g_signal_connect(pipeline, "about-to-finish",
274 G_CALLBACK(about_to_finish_cb), uri);
275 //g_free(uri);
277 else {
278 pipeline = GST_PIPELINE(gst_pipeline_new("test_pipeline"));
279 char * src_name = (option_fake) ? "videotestsrc" : "v4l2src";
280 GstElement *src = gst_element_factory_make(src_name, "videosource");
281 if (option_fake == 2){//set some properties for an interesting picture
282 g_object_set(G_OBJECT(src),
283 "pattern", 14, //"zone-plate"
284 "kt2", 0,
285 "kx2", 3,
286 "ky2", 3,
287 "kt", 3,
288 "kxy", 2,
289 NULL);
291 gst_bin_add(GST_BIN(pipeline), src);
293 return pipeline;
296 static GstBin *
297 tee_bin(GMainLoop *loop, window_t *windows){
298 GstBin *bin = GST_BIN(gst_bin_new("teebin"));
299 GstElement *tee = gst_element_factory_make("tee", NULL);
300 gst_bin_add(bin, tee);
301 GstPad *teesink = gst_element_get_pad(tee, "sink");
302 GstPad *ghost = gst_ghost_pad_new("sink", teesink);
303 gst_element_add_pad(GST_ELEMENT(bin), ghost);
304 //XXX unref pad?
306 /* construct the various arms
307 crop _left/_right are amount to cut, not coordinate of cut
309 int input_width = option_screens * option_width;
310 int crop_left = 0;
311 int crop_right = input_width - option_width;
313 int i;
314 for (i = 0; i < option_screens; i++){
315 window_t *w = windows + i;
316 set_up_window(loop, w, i);
317 post_tee_pipeline(bin, tee, w->sink, crop_left, crop_right);
318 crop_left += option_width;
319 crop_right -= option_width;
321 return bin;
326 static GstPipeline *
327 gstreamer_start(GMainLoop *loop, window_t windows[MAX_SCREENS])
329 GstPipeline *pipeline = pre_tee_pipeline();
330 GstBin *teebin = tee_bin(loop, windows);
333 if (option_content) {
334 g_object_set(G_OBJECT(pipeline),
335 "video-sink", GST_ELEMENT(teebin),
336 NULL);
338 else {
339 gst_bin_add(GST_BIN(pipeline), GST_ELEMENT(teebin));
340 GstElement *videosrc = gst_bin_get_by_name(GST_BIN(pipeline), "videosource");
341 GstCaps *caps = make_good_caps();
342 gst_element_link_filtered(videosrc, GST_ELEMENT(teebin), caps);
343 //XXX unref caps?
346 //xxx
348 GstBus *bus = gst_pipeline_get_bus(pipeline);
349 gst_bus_set_sync_handler(bus, (GstBusSyncHandler)sync_bus_call, windows);
350 gst_object_unref(bus);
352 gst_element_set_state(GST_ELEMENT(pipeline), GST_STATE_PLAYING);
353 return pipeline;
357 static void
358 gstreamer_stop(GstElement *pipeline)
360 gst_element_set_state(pipeline, GST_STATE_NULL);
361 gst_object_unref(pipeline);
364 gint main (gint argc, gchar *argv[])
366 //initialise threads before any gtk stuff (because not using gtk_init)
367 static window_t windows[MAX_SCREENS];
368 g_type_init();
369 g_thread_init(NULL);
370 /*this is more complicated than plain gtk_init/gst_init, so that options from
371 all over can be gathered and presented together.
373 GOptionGroup *gst_opts = gst_init_get_option_group();
374 GOptionGroup *gtk_opts = gtk_get_option_group(TRUE);
375 GOptionContext *ctx = g_option_context_new("...!");
376 g_option_context_add_main_entries(ctx, entries, NULL);
377 g_option_context_add_group(ctx, gst_opts);
378 g_option_context_add_group(ctx, gtk_opts);
379 GError *error = NULL;
380 if (!g_option_context_parse(ctx, &argc, &argv, &error)){
381 g_print ("Error initializing: %s\n", GST_STR_NULL(error->message));
382 exit (1);
384 g_option_context_free(ctx);
385 /*sanitise options*/
386 if (option_x_screens > MAX_X_SCREENS)
387 option_x_screens = MAX_X_SCREENS;
388 if (option_x_screens < MIN_X_SCREENS)
389 option_x_screens = MIN_X_SCREENS;
390 if (option_x_screens > MAX_X_SCREENS)
391 option_screens = MAX_SCREENS;
392 if (option_screens < MIN_SCREENS)
393 option_screens = MIN_SCREENS;
394 if (option_width > MAX_PIXELS)
395 option_width = MAX_PIXELS;
396 if (option_height > MAX_PIXELS)
397 option_height = MAX_PIXELS;
398 /*setting width, height <= 0 makes sizing automatic (default) */
399 if (option_width <= 0){
400 option_width = DEFAULT_WIDTH;
401 option_autosize = 1;
403 if (option_height <= 0){
404 option_height = DEFAULT_HEIGHT;
405 option_autosize = 1;
408 GMainLoop *loop = g_main_loop_new(NULL, FALSE);
410 GstPipeline *pipeline = gstreamer_start(loop, windows);
412 g_main_loop_run(loop);
414 gstreamer_stop(GST_ELEMENT(pipeline));
415 return 0;