Add options for window sticking and backgrounding
[cteddy.git] / src / cteddy.cxx
blobbb8ddddfd5494f20fd8dca8cde101f33f23daf4f
1 // Copyright 2008 Philip Allison <sane@not.co.uk>
3 // This file is part of cteddy.
4 //
5 // cteddy is free software: you can redistribute it and/or modify
6 // it under the terms of the GNU General Public License as published by
7 // the Free Software Foundation, either version 3 of the License, or
8 // (at your option) any later version.
9 //
10 // cteddy is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 // GNU General Public License for more details.
15 // You should have received a copy of the GNU General Public License
16 // along with cteddy. If not, see <http://www.gnu.org/licenses/>.
20 // Includes
23 // Language headers
24 #include <string>
25 #include <iostream>
26 #include <cstring>
27 #include <cerrno>
29 // Library headers
30 #include <gtk/gtk.h>
31 #include <gdk/gdkkeysyms.h>
33 // System headers
34 #include <sys/types.h>
35 #include <sys/stat.h>
36 #include <fcntl.h>
37 #include <unistd.h>
41 // Globals
44 // Main window
45 GtkWidget* window;
47 // GTK/GDK returned pointer
48 GError* gerr = NULL;
50 // Image to display and its dimensions
51 GdkPixbuf* pixbuf;
52 gint width, height;
54 // Whether or not the key & mouse binding for quit are disabled
55 bool noquit = false;
59 // Implementation
62 // Re-paint the window when necessary
63 gboolean handle_expose(GtkWidget* widget, GdkEventExpose* event)
65 cairo_t* cairo = gdk_cairo_create(widget->window);
67 // Blank the surface
68 cairo_set_source_rgba (cairo, 1.0f, 1.0f, 1.0f, 0.0f);
69 cairo_set_operator (cairo, CAIRO_OPERATOR_SOURCE);
70 cairo_paint (cairo);
72 // Set cairo's source pattern from the desired image
73 gdk_cairo_set_source_pixbuf(cairo, pixbuf, 0.0f, 0.0f);
75 // Draw the image
76 cairo_rectangle(cairo, 0.0f, 0.0f,
77 static_cast<double>(width), static_cast<double>(height));
78 cairo_fill(cairo);
80 cairo_destroy(cairo);
82 return false;
85 // Handle mouse click events
86 gboolean window_clicked(GtkWidget* widget, GdkEventButton* event)
88 if (event->type == GDK_BUTTON_PRESS)
89 // Move the window with the mouse
90 gtk_window_begin_move_drag(GTK_WINDOW(window),
91 event->button, static_cast<gint>(event->x_root),
92 static_cast<gint>(event->y_root), event->time);
93 return true;
96 // Set up an RGBA colourmap for the window
97 void handle_screen(GtkWidget* window, GdkScreen* old, gpointer data)
99 GdkScreen* s = gtk_widget_get_screen(window);
100 GdkColormap* c = gdk_screen_get_rgba_colormap(s);
102 if (!c)
103 // Use standard RGB colourmap if we must. Window will have
104 // an ugly black border if this is the case.
105 c = gdk_screen_get_rgb_colormap(s);
107 gtk_widget_set_colormap(window, c);
110 // Handle key press events
111 void handle_key_press(GtkWidget *Window, GdkEventKey *theEvent)
113 switch( theEvent->keyval )
115 case GDK_q:
116 // Quit on "q"
117 if (!noquit)
118 gtk_main_quit();
119 break;
121 default:
122 // Ignore all other keys
123 break;
128 // Entry point
129 int main (int argc, char* argv[])
131 gtk_init(&argc, &argv);
133 // Whether or not to set the window's keep-on-top hint
134 bool ontop = false;
136 // Whether or not the window's sticky hint should be set
137 bool sticky = false;
139 // Whether or not to fork into the background
140 bool background = true;
142 // Mimic the image loading behaviour of xteddy, to an extent.
144 // Start from the name of the executable itself
145 std::string image(argv[0]);
147 // Take just the last component of the path
148 std::string::size_type s = image.find_last_of('/');
149 if (s != std::string::npos)
150 image = image.substr(s + 1);
152 // Parse options
153 // XXX This code is really quick and nasty
154 for (int i = 1; i < argc; ++i)
156 // Allow image specification with -F<file>
157 if (strncmp(argv[i], "-F", 2) == 0)
158 image = argv[i] + 2;
159 // Allow disablement of "q" keybinding
160 else if (strncmp(argv[i], "-noquit", 7) == 0)
161 noquit = true;
162 // Allow keep-on-top
163 else if (strncmp(argv[i], "-float", 6) == 0)
164 ontop = true;
165 // Allow sticking to all desktops
166 else if (strncmp(argv[i], "-stick", 6) == 0)
167 sticky = true;
168 // Allow disablement of backgrounding
169 else if (strncmp(argv[i], "-nobg", 5) == 0)
170 background = false;
173 // If the given path contains slashes, assume it is absolute or
174 // relative to the current working directory and use it verbatim.
175 // Otherwise, search for it.
176 if (image.find_last_of('/') == std::string::npos)
178 // Append extensions one by one and search for the image
179 // XXX This code is really, REALLY quick and nasty
180 char* extensions[17];
181 bool found = false;
182 extensions[0] = '\0';
183 extensions[1] = ".png"; extensions[2] = ".gif"; extensions[3] = ".jpg";
184 extensions[4] = ".jpeg"; extensions[5] = ".pnm"; extensions[6] = ".xpm";
185 extensions[7] = ".tif"; extensions[8] = ".tiff";
186 extensions[9] = ".PNG"; extensions[10] = ".GIF"; extensions[11] = ".JPG";
187 extensions[12] = ".JPEG"; extensions[13] = ".PNM"; extensions[14] = ".XPM";
188 extensions[15] = ".TIF"; extensions[16] = ".TIFF";
189 for (size_t i = 0; i < 17; ++i)
191 std::string searchimage(PKGDATADIR "/");
192 searchimage.append(image);
193 if (i > 0) searchimage.append(extensions[i]);
194 struct stat s;
195 if (stat(searchimage.c_str(), &s) == 0)
197 image = searchimage;
198 found = true;
199 break;
201 // Try converting a leading x to a c and vice versa
202 if (image.at(0) == 'x')
204 std::string ximage(PKGDATADIR "/c");
205 ximage.append(image.substr(1));
206 if (i > 0) ximage.append(extensions[i]);
207 if (stat(ximage.c_str(), &s) == 0)
209 image = ximage;
210 found = true;
211 break;
214 if (image.at(0) == 'c')
216 std::string ximage(PKGDATADIR "/x");
217 ximage.append(image.substr(1));
218 if (i > 0) ximage.append(extensions[i]);
219 if (stat(ximage.c_str(), &s) == 0)
221 image = ximage;
222 found = true;
223 break;
226 // Try prepending c or x
227 std::string ximage(PKGDATADIR "/c");
228 ximage.append(image);
229 if (i > 0) ximage.append(extensions[i]);
230 if (stat(ximage.c_str(), &s) == 0)
232 image = ximage;
233 found = true;
234 break;
236 ximage = PKGDATADIR "/x";
237 ximage.append(image);
238 if (i > 0) ximage.append(extensions[i]);
239 if (stat(ximage.c_str(), &s) == 0)
241 image = ximage;
242 found = true;
243 break;
247 if (!found)
249 std::cerr << "Cannot find image!" << std::endl;
250 return 1;
254 // Load the image from the supplied file path
255 pixbuf = gdk_pixbuf_new_from_file(image.c_str(), &gerr);
256 if (gerr)
258 std::cerr << "Cannot load image \"" << image << "\": " << gerr->message << std::endl;
259 g_error_free(gerr);
260 return 1;
264 // Create the window
265 window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
267 // Top-level windows don't normally respond to mouse button events directly.
268 // We want this one to, though.
269 gtk_widget_add_events(window, GDK_BUTTON_PRESS_MASK);
271 // Set up event callbacks
272 g_signal_connect(G_OBJECT(window), "expose-event", G_CALLBACK(handle_expose), NULL);
273 g_signal_connect(G_OBJECT(window), "destroy", G_CALLBACK(gtk_main_quit), NULL);
274 g_signal_connect(G_OBJECT(window), "button-press-event", G_CALLBACK(window_clicked), NULL);
275 g_signal_connect(G_OBJECT(window), "screen-changed", G_CALLBACK(handle_screen), NULL);
276 g_signal_connect(G_OBJECT(window), "key-press-event", G_CALLBACK(handle_key_press), NULL);
279 // Grap pixmap and alpha-thresholded bitmap from original pixmap
280 GdkPixmap* pixmap;
281 GdkBitmap* bitmap;
282 gdk_pixbuf_render_pixmap_and_mask(pixbuf, &pixmap, &bitmap, 128);
284 // Set window size to image size
285 gdk_drawable_get_size(pixmap, &width, &height);
286 gtk_widget_set_size_request(window, width, height);
288 // If we have an alpha channel on the image, set the window's input shape,
289 // clearing it completely first
290 if (bitmap)
292 gtk_widget_input_shape_combine_mask(window, NULL, 0, 0);
293 gtk_widget_input_shape_combine_mask(window, bitmap, 0, 0);
296 g_object_unref(G_OBJECT(pixmap));
297 g_object_unref(G_OBJECT(bitmap));
300 // Set up various other properties we're interested in
301 gtk_window_set_title(GTK_WINDOW(window), "cteddy");
302 gtk_window_set_resizable(GTK_WINDOW(window), false);
303 gtk_window_set_decorated(GTK_WINDOW(window), false);
304 gtk_widget_set_app_paintable(window, true);
306 // Trigger initial attempt to switch to an RGBA colourmap
307 handle_screen(window, NULL, NULL);
309 // Have to do this before we can use window->window as a GdkWindow*
310 gtk_widget_realize(window);
313 // Load in the heart-shaped cursor image and use it for our window's cursor
314 GdkPixbuf* heart = gdk_pixbuf_new_from_file(PKGDATADIR "/heart.png", &gerr);
315 if (gerr)
317 std::cerr << "Cannot load image: " << gerr->message << std::endl;
318 g_error_free(gerr);
319 return 1;
321 GdkCursor* cursor = gdk_cursor_new_from_pixbuf(gdk_display_get_default(),
322 heart, 12, 12);
323 gdk_window_set_cursor(window->window, cursor);
326 // Clear the window's default background
327 gdk_window_set_back_pixmap(window->window, NULL, false);
329 // Don't steal input focus when we start
330 g_object_set(G_OBJECT(window), "focus-on-map", false, NULL);
332 // Honour float & sticky options
333 if (ontop)
334 gtk_window_set_keep_above(GTK_WINDOW(window), true);
335 if (sticky)
336 gtk_window_stick(GTK_WINDOW(window));
338 gtk_widget_show_all(window);
340 // Fork into background if desired
341 if (background)
343 pid_t p = fork();
344 if (p < 0)
346 std::cerr << "Could not fork: " << strerror(errno) << std::endl;
347 return 1;
349 else if (p == 0)
351 // Close stdin
352 while (close(0) != 0)
354 if (errno == EINTR)
355 continue;
356 std::cerr << "Could not close stdin: " << strerror(errno) << std::endl;
357 return 1;
359 // Redirect stdout & stderr to /dev/null
360 int nullfd;
361 while ((nullfd = open("/dev/null", O_WRONLY)) < 0)
363 if (errno == EINTR)
364 continue;
365 std::cerr << "Cannot open /dev/null: " << strerror(errno) << std::endl;
366 return 1;
368 while (dup2(nullfd, 1) < 0)
370 if (errno == EINTR)
371 continue;
372 std::cerr << "Cannot redirect stdout to /dev/null: " << strerror(errno) << std::endl;
373 return 1;
375 while (dup2(nullfd, 2) < 0)
377 if (errno == EINTR)
378 continue;
379 std::cerr << "Cannot redirect stderr to /dev/null: " << strerror(errno) << std::endl;
380 return 1;
382 // Create new process session
383 if (setsid() < 0)
385 std::cerr << "Cannot create new session: " << strerror(errno) << std::endl;
386 return 1;
389 else
390 return 0;
393 gtk_main();
395 return 0;