1 // Copyright 2008 Philip Allison <sane@not.co.uk>
3 // This file is part of cteddy.
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.
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/>.
31 #include <gdk/gdkkeysyms.h>
34 #include <sys/types.h>
47 // GTK/GDK returned pointer
50 // Image to display and its dimensions
54 // Whether or not the key & mouse binding for quit are disabled
62 // Re-paint the window when necessary
63 gboolean
handle_expose(GtkWidget
* widget
, GdkEventExpose
* event
)
65 cairo_t
* cairo
= gdk_cairo_create(widget
->window
);
68 cairo_set_source_rgba (cairo
, 1.0f
, 1.0f
, 1.0f
, 0.0f
);
69 cairo_set_operator (cairo
, CAIRO_OPERATOR_SOURCE
);
72 // Set cairo's source pattern from the desired image
73 gdk_cairo_set_source_pixbuf(cairo
, pixbuf
, 0.0f
, 0.0f
);
76 cairo_rectangle(cairo
, 0.0f
, 0.0f
,
77 static_cast<double>(width
), static_cast<double>(height
));
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
);
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
);
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
)
122 // Ignore all other keys
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
136 // Whether or not the window's sticky hint should be set
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);
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)
159 // Allow disablement of "q" keybinding
160 else if (strncmp(argv
[i
], "-noquit", 7) == 0)
163 else if (strncmp(argv
[i
], "-float", 6) == 0)
165 // Allow sticking to all desktops
166 else if (strncmp(argv
[i
], "-stick", 6) == 0)
168 // Allow disablement of backgrounding
169 else if (strncmp(argv
[i
], "-nobg", 5) == 0)
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];
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
]);
195 if (stat(searchimage
.c_str(), &s
) == 0)
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)
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)
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)
236 ximage
= PKGDATADIR
"/x";
237 ximage
.append(image
);
238 if (i
> 0) ximage
.append(extensions
[i
]);
239 if (stat(ximage
.c_str(), &s
) == 0)
249 std::cerr
<< "Cannot find image!" << std::endl
;
254 // Load the image from the supplied file path
255 pixbuf
= gdk_pixbuf_new_from_file(image
.c_str(), &gerr
);
258 std::cerr
<< "Cannot load image \"" << image
<< "\": " << gerr
->message
<< std::endl
;
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
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
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
);
317 std::cerr
<< "Cannot load image: " << gerr
->message
<< std::endl
;
321 GdkCursor
* cursor
= gdk_cursor_new_from_pixbuf(gdk_display_get_default(),
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
334 gtk_window_set_keep_above(GTK_WINDOW(window
), true);
336 gtk_window_stick(GTK_WINDOW(window
));
338 gtk_widget_show_all(window
);
340 // Fork into background if desired
346 std::cerr
<< "Could not fork: " << strerror(errno
) << std::endl
;
352 while (close(0) != 0)
356 std::cerr
<< "Could not close stdin: " << strerror(errno
) << std::endl
;
359 // Redirect stdout & stderr to /dev/null
361 while ((nullfd
= open("/dev/null", O_WRONLY
)) < 0)
365 std::cerr
<< "Cannot open /dev/null: " << strerror(errno
) << std::endl
;
368 while (dup2(nullfd
, 1) < 0)
372 std::cerr
<< "Cannot redirect stdout to /dev/null: " << strerror(errno
) << std::endl
;
375 while (dup2(nullfd
, 2) < 0)
379 std::cerr
<< "Cannot redirect stderr to /dev/null: " << strerror(errno
) << std::endl
;
382 // Create new process session
385 std::cerr
<< "Cannot create new session: " << strerror(errno
) << std::endl
;