added README
[opo.git] / gtk-app.c
blob22a142c3cde4e2aa317871a4a8d76c3714ff3b37
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"
11 static void
12 post_tee_pipeline(GstPipeline *pipeline, GstElement *tee, GstElement *sink,
13 int crop_left, int crop_right){
14 GstElement *queue = gst_element_factory_make("queue", NULL);
15 GstElement *crop = gst_element_factory_make("videocrop", NULL);
17 g_object_set(G_OBJECT(crop),
18 "top", 0,
19 "bottom", 0,
20 "left", crop_left,
21 "right", crop_right,
22 NULL);
24 gst_bin_add_many(GST_BIN(pipeline),
25 queue,
26 crop,
27 sink,
28 NULL);
30 gst_element_link_many(tee,
31 queue,
32 crop,
33 sink,
34 NULL);
37 static GstElement *
38 pre_tee_pipeline(GstPipeline *pipeline, int width, int height){
39 if (pipeline == NULL){
40 pipeline = GST_PIPELINE(gst_pipeline_new("wha_pipeline"));
42 char * src_name = (option_fake) ? "videotestsrc" : "v4l2src";
43 GstElement *src = gst_element_factory_make(src_name, NULL);
44 if (option_fake == 2){//set some properties for an interesting picture
45 g_object_set(G_OBJECT(src),
46 "pattern", 14,
47 "kt2", 0,
48 "kx2", 3,
49 "ky2", 3,
50 "kt", 3,
51 "kxy", 2,
52 NULL);
55 GstElement *tee = gst_element_factory_make ("tee", NULL);
56 GstCaps *caps;
57 caps = gst_caps_new_simple("video/x-raw-yuv",
58 "width", G_TYPE_INT, width,
59 "height", G_TYPE_INT, height,
60 NULL);
61 gst_caps_merge(caps, gst_caps_new_simple("video/x-raw-rgb",
62 "width", G_TYPE_INT, width,
63 "height", G_TYPE_INT, height,
64 NULL));
66 gst_bin_add_many(GST_BIN(pipeline),
67 src,
68 tee,
69 NULL);
71 gst_element_link_filtered(src,
72 tee,
73 caps);
74 return tee;
78 static void hide_mouse(GtkWidget *widget){
79 GdkWindow *w = GDK_WINDOW(widget->window);
80 GdkDisplay *display = gdk_display_get_default();
81 GdkCursor *cursor = gdk_cursor_new_for_display(display, GDK_BLANK_CURSOR);
82 gdk_window_set_cursor(w, cursor);
83 gdk_cursor_unref (cursor);
88 static GstBusSyncReply
89 sync_bus_call(GstBus *bus, GstMessage *msg, gpointer data)
91 // ignore anything but 'prepare-xwindow-id' element messages
92 if ((GST_MESSAGE_TYPE(msg) != GST_MESSAGE_ELEMENT) ||
93 (! gst_structure_has_name(msg->structure, "prepare-xwindow-id"))){
94 return GST_BUS_PASS;
96 window_t *windows = (window_t *)data;
97 g_print("Got prepare-xwindow-id msg. \n");
98 //connect this one up with the right window.
99 GstElement *sink = GST_ELEMENT(GST_MESSAGE_SRC(msg));
100 int done = 0;
102 g_print("found sink %p\n", sink);
103 for (int i = 0; i < option_screens; i++){
104 const window_t *w = windows + i;
105 if (w->sink == sink){
106 gst_x_overlay_set_xwindow_id(GST_X_OVERLAY(sink), w->xid);
107 g_print("connected sink %d to window %lu\n", i, w->xid);
108 hide_mouse(w->widget);
109 done = 1;
110 break;
114 if (! done){
115 g_print("couldn't find a window for this sink!\n");
118 gst_message_unref(msg);
119 return GST_BUS_DROP;
122 static void
123 toggle_fullscreen(GtkWidget *widget){
124 GdkWindowState state = gdk_window_get_state(GDK_WINDOW(widget->window));
125 if (state == GDK_WINDOW_STATE_FULLSCREEN){
126 gtk_window_unfullscreen(GTK_WINDOW(widget));
128 else{
129 gtk_window_fullscreen(GTK_WINDOW(widget));
133 static gboolean
134 key_press_event_cb(GtkWidget *widget, GdkEventKey *event, gpointer data)
136 g_print("got key %c\n", event->keyval);
137 switch (event->keyval){
138 case 'f':
139 toggle_fullscreen(widget);
140 break;
141 case 'q':
142 g_signal_emit_by_name(widget, "destroy");
143 break;
144 default:
145 break;
147 return TRUE;
150 static void
151 destroy_cb(GtkWidget *widget, gpointer data)
153 GMainLoop *loop = (GMainLoop*) data;
154 g_print("Window destroyed\n");
155 g_main_loop_quit(loop);
156 gtk_widget_destroy(widget);
159 static void
160 video_widget_realize_cb(GtkWidget *widget, gpointer data)
162 window_t *w = (window_t *)data;
163 w->xid = GDK_WINDOW_XID(GDK_WINDOW(widget->window));
164 g_print("realised window %d with XID %lu\n", w->id, w->xid);
165 hide_mouse(widget);
169 static void
170 set_up_window(GMainLoop *loop, GtkWidget *window, int screen_no){
171 static const GdkColor black = {0, 0, 0, 0};
172 gtk_window_set_default_size(GTK_WINDOW(window), option_width, option_height);
174 if (option_fullscreen){
175 gtk_window_fullscreen(GTK_WINDOW(window));
178 GdkScreen * screen = gdk_screen_get_default();
179 int x, y;
181 if (option_force_multiscreen){
182 /*Ask gtk to find the approriate monitor. But first warn if
183 the request seems broken. */
184 int monitors = gdk_screen_get_n_monitors(screen);
185 if (screen_no >= monitors){
186 g_print("Asking for %d out of %d monitors! expect unhappiness!",
187 screen_no, monitors);
189 GdkRectangle monitor_shape;
190 gdk_screen_get_monitor_geometry(screen, screen_no, &monitor_shape);
191 x = monitor_shape.x + 1;
192 y = monitor_shape.y + 1;
194 else {
195 /*simple placement heuristic, places windows evenly across display.
196 This should work with equally sized monitors/projectors, and allows
197 testing on a single monitor. */
198 int width = gdk_screen_get_width(screen);
199 x = (width / option_screens) * screen_no + 1;
200 y = 50;
203 gtk_window_move(GTK_WINDOW(window), x, y);
204 g_print("putting window %d at %d\n", screen_no, x);
206 // attach key press signal to key press callback
207 gtk_widget_set_events(window, GDK_KEY_PRESS_MASK);
208 g_signal_connect(G_OBJECT(window), "key-press-event", G_CALLBACK(key_press_event_cb), NULL);
209 g_signal_connect(G_OBJECT(window), "destroy", G_CALLBACK(destroy_cb), loop);
211 gtk_widget_modify_bg(window, GTK_STATE_NORMAL, &black);
212 gtk_widget_show_all(window);
213 hide_mouse(window);
216 static GstElement *
217 gstreamer_start(GMainLoop *loop, window_t windows[MAX_SCREENS])
219 int input_width = option_screens * option_width;
220 //crop _left/_right are amount to cut, not coordinate of cut
221 int crop_left = 0;
222 int crop_right = input_width - option_width;
224 GstElement *pipeline = gst_pipeline_new("e_wha");
225 GstElement *tee = pre_tee_pipeline(GST_PIPELINE(pipeline), input_width, option_height);
227 int i;
228 for (i = 0; i < option_screens; i++){
229 window_t *w = windows + i;
230 w->widget = gtk_window_new(GTK_WINDOW_TOPLEVEL);
231 w->sink = gst_element_factory_make("xvimagesink", NULL);
232 w->id = i;
233 g_signal_connect(w->widget, "realize", G_CALLBACK(video_widget_realize_cb), w);
234 set_up_window(loop, w->widget, i);
235 post_tee_pipeline(GST_PIPELINE(pipeline), tee, w->sink, crop_left, crop_right);
236 crop_left += option_width;
237 crop_right -= option_width;
240 GstBus *bus = gst_pipeline_get_bus(GST_PIPELINE(pipeline));
241 gst_bus_set_sync_handler(bus, (GstBusSyncHandler)sync_bus_call, windows);
242 gst_object_unref(bus);
244 gst_element_set_state(pipeline, GST_STATE_PLAYING);
245 return pipeline;
248 static void
249 gstreamer_stop(GstElement *pipeline)
251 gst_element_set_state(pipeline, GST_STATE_NULL);
252 gst_object_unref(pipeline);
255 gint main (gint argc, gchar *argv[])
257 //initialise threads before any gtk stuff (because not using gtk_init)
258 static window_t windows[MAX_SCREENS];
259 g_type_init();
260 g_thread_init(NULL);
261 /*this is more complicated than plain gtk_init/gst_init, so that options from
262 all over can be gathered and presented together.
264 GOptionGroup *gst_opts = gst_init_get_option_group();
265 GOptionGroup *gtk_opts = gtk_get_option_group(TRUE);
266 GOptionContext *ctx = g_option_context_new("...!");
267 g_option_context_add_main_entries(ctx, entries, NULL);
268 g_option_context_add_group(ctx, gst_opts);
269 g_option_context_add_group(ctx, gtk_opts);
270 GError *error = NULL;
271 if (!g_option_context_parse(ctx, &argc, &argv, &error)){
272 g_print ("Error initializing: %s\n", GST_STR_NULL(error->message));
273 exit (1);
275 g_option_context_free(ctx);
276 /*sanitise options*/
277 if (option_screens > MAX_SCREENS)
278 option_screens = MAX_SCREENS;
279 if (option_screens < MIN_SCREENS)
280 option_screens = MIN_SCREENS;
281 if (option_width > MAX_PIXELS)
282 option_width = MAX_PIXELS;
283 if (option_height > MAX_PIXELS)
284 option_height = MAX_PIXELS;
286 GMainLoop *loop = g_main_loop_new(NULL, FALSE);
288 GstElement *pipeline = gstreamer_start(loop, windows);
290 g_main_loop_run(loop);
292 gstreamer_stop(pipeline);
293 return 0;