Skip to content

Instantly share code, notes, and snippets.

@chikatoike
Created July 28, 2013 02:33
Show Gist options
  • Save chikatoike/6097123 to your computer and use it in GitHub Desktop.
Save chikatoike/6097123 to your computer and use it in GitHub Desktop.
growl for linux display test
/* Copyright 2011 by Yasuhiro Matsumoto
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
* OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include <gtk/gtk.h>
#ifdef _WIN32
# include <gdk/gdkwin32.h>
#endif
#include "gol.h"
#include "plugins/from_url.h"
#include "test.xpm"
#include "display_test.xpm"
static gchar* param;
static GList* notifications;
static GList* popup_collections;
static GdkPixmap* pixmap;
static GdkBitmap* bitmap;
static gint pixmap_width, pixmap_height;
static GdkColor inst_color_white_;
static const GdkColor* const color_white = &inst_color_white_;
static PangoFontDescription* font_sans12_desc;
static PangoFontDescription* font_sans8_desc;
static GdkRectangle screen_rect;
typedef struct {
NOTIFICATION_INFO* ni;
gint pos;
gint x, y;
gint timeout;
gint offset;
gboolean sticky;
gboolean hover;
struct {
GtkWidget* popup;
GtkWidget* title;
GtkWidget* text;
} widget;
} DISPLAY_INFO;
static inline void
free_display_info(DISPLAY_INFO* const di) {
gtk_widget_destroy(di->widget.popup);
free_notification_info(di->ni);
g_free(di);
}
static gboolean
open_url(const gchar* url) {
#if defined(_WIN32)
return (int) ShellExecute(NULL, "open", url, NULL, NULL, SW_SHOW) > 32;
#elif defined(MACOSX)
GError* error = NULL;
const gchar *argv[] = {"open", (gchar*) url, NULL};
return g_spawn_async(NULL, argv, NULL, G_SPAWN_SEARCH_PATH, NULL, NULL, NULL, &error);
#else
GError* error = NULL;
gchar *argv[] = {"xdg-open", (gchar*) url, NULL};
return g_spawn_async(NULL, argv, NULL, G_SPAWN_SEARCH_PATH, NULL, NULL, NULL, &error);
#endif
}
static void
display_clicked(GtkWidget* GOL_UNUSED_ARG(widget), GdkEvent* GOL_UNUSED_ARG(event), gpointer user_data) {
DISPLAY_INFO* const di = (DISPLAY_INFO*) user_data;
if (di->timeout >= 30) di->timeout = 30;
if (di->ni->url && *di->ni->url) open_url(di->ni->url);
}
static void
display_enter(GtkWidget* GOL_UNUSED_ARG(widget), GdkEventMotion* GOL_UNUSED_ARG(event), gpointer user_data) {
((DISPLAY_INFO*) user_data)->hover = TRUE;
}
static void
display_leave(GtkWidget* GOL_UNUSED_ARG(widget), GdkEventMotion* GOL_UNUSED_ARG(event), gpointer user_data) {
((DISPLAY_INFO*) user_data)->hover = FALSE;
}
static inline DISPLAY_INFO*
reset_display_info(DISPLAY_INFO*, NOTIFICATION_INFO*);
static gboolean
display_animation_func(gpointer data) {
DISPLAY_INFO* const di = (DISPLAY_INFO*) data;
if (di->hover) return TRUE; // Do nothing.
--di->timeout;
if (di->timeout < 0) {
notifications = g_list_remove(notifications, di);
popup_collections = g_list_append(popup_collections, di);
reset_display_info(di, NULL);
return FALSE;
}
if (di->timeout > 450) {
gtk_window_set_opacity(GTK_WINDOW(di->widget.popup), (double) (500-di->timeout)/50.0*0.8);
}
if (di->timeout < 50) {
gtk_window_set_opacity(GTK_WINDOW(di->widget.popup), (double) di->timeout/50.0*0.8);
}
return TRUE;
}
static gboolean
display_expose(GtkWidget *widget, GdkEventExpose *event, gpointer GOL_UNUSED_ARG(data)) {
gdk_window_clear_area(
widget->window,
event->area.x, event->area.y, event->area.width, event->area.height);
gdk_draw_pixmap(
widget->window,
widget->style->fg_gc[GTK_STATE_NORMAL],
pixmap,
0, 0,
0, 0, pixmap_width, pixmap_height);
return FALSE;
}
static void
label_size_allocate(GtkWidget* label, GtkAllocation* allocation, gpointer GOL_UNUSED_ARG(data)) {
gtk_widget_set_size_request(label, allocation->width - 2, -1);
}
static inline GtkWidget*
get_container_nth_child(GtkContainer* const cont, const gint n) {
GtkWidget* widget = NULL;
gint cnt = 0;
void
nth_getter(GtkWidget* const wid, gpointer GOL_UNUSED_ARG(user_data)) {
if (cnt++ == n) widget = wid;
}
gtk_container_foreach(cont, nth_getter, NULL);
return widget;
}
static inline GtkBox*
DISPLAY_HBOX(const DISPLAY_INFO* const di) {
GtkWidget* const ebox = get_container_nth_child(GTK_CONTAINER(di->widget.popup), 0);
if (!ebox) return NULL;
GtkWidget* const vbox = get_container_nth_child(GTK_CONTAINER(ebox), 0);
if (!vbox) return NULL;
return GTK_BOX(get_container_nth_child(GTK_CONTAINER(vbox), 0));
}
static inline GtkWidget*
DISPLAY_HBOX_NTH_ELEM(const DISPLAY_INFO* const di, const gint n) {
GtkBox* const hbox = DISPLAY_HBOX(di);
if (!hbox) return NULL;
return get_container_nth_child(GTK_CONTAINER(hbox), n);
}
static inline GtkImage*
DISPLAY_ICON_FIELD(const DISPLAY_INFO* const di) {
return GTK_IMAGE(DISPLAY_HBOX_NTH_ELEM(di, 0));
}
static inline GtkLabel*
DISPLAY_TITLE_FIELD(const DISPLAY_INFO* const di) {
return di ? GTK_LABEL(di->widget.title) : NULL;
}
static inline GtkLabel*
DISPLAY_TEXT_FIELD(const DISPLAY_INFO* const di) {
return di ? GTK_LABEL(di->widget.text) : NULL;
}
static inline void
box_set_icon_if_has(const DISPLAY_INFO* const di) {
if (!di) return;
const NOTIFICATION_INFO* const ni = di->ni;
if (!ni->icon || !*ni->icon) return;
GdkPixbuf* const pixbuf =
(ni->local ? pixbuf_from_url_as_file
: pixbuf_from_url)(ni->icon, NULL);
if (!pixbuf) return;
GdkPixbuf* const tmp = gdk_pixbuf_scale_simple(pixbuf, 32, 32, GDK_INTERP_TILES);
GtkWidget* const image = gtk_image_new_from_pixbuf(tmp ? tmp : pixbuf);
if (image) {
GtkBox* const hbox = DISPLAY_HBOX(di);
gtk_box_pack_start(hbox, image, FALSE, FALSE, 0);
gtk_box_reorder_child(hbox, DISPLAY_HBOX_NTH_ELEM(di, 0), 1);
}
if (tmp) g_object_unref(tmp);
g_object_unref(pixbuf);
}
static inline void
remove_icon(const DISPLAY_INFO* const di) {
if (!di) return;
GtkBox* const hbox = DISPLAY_HBOX(di);
GList* const children = gtk_container_get_children(GTK_CONTAINER(hbox));
if (g_list_length(children) != 1) {
GtkWidget* const image = g_list_nth_data(children, 0);
gtk_box_reorder_child(hbox, image, -1);
gtk_container_remove(GTK_CONTAINER(hbox), image);
}
g_list_free(children);
}
static inline DISPLAY_INFO*
create_popup_skelton() {
DISPLAY_INFO* const di = g_new0(DISPLAY_INFO, 1);
if (!di) return NULL;
di->widget.popup = gtk_window_new(GTK_WINDOW_POPUP);
if (!di->widget.popup) {
free_display_info(di);
return NULL;
}
gtk_window_set_title(GTK_WINDOW(di->widget.popup), "growl-for-linux");
gtk_window_set_resizable(GTK_WINDOW(di->widget.popup), FALSE);
gtk_window_set_decorated(GTK_WINDOW(di->widget.popup), FALSE);
gtk_window_set_keep_above(GTK_WINDOW(di->widget.popup), TRUE);
gtk_window_stick(GTK_WINDOW(di->widget.popup));
GtkWidget* const ebox = gtk_event_box_new();
if (!ebox) {
free_display_info(di);
return NULL;
}
gtk_event_box_set_visible_window(GTK_EVENT_BOX(ebox), FALSE);
g_signal_connect(G_OBJECT(ebox), "button-press-event", G_CALLBACK(display_clicked), di);
g_signal_connect(G_OBJECT(ebox), "enter-notify-event", G_CALLBACK(display_enter), di);
g_signal_connect(G_OBJECT(ebox), "leave-notify-event", G_CALLBACK(display_leave), di);
gtk_container_add(GTK_CONTAINER(di->widget.popup), ebox);
GtkWidget* const vbox = gtk_vbox_new(FALSE, 5);
if (!vbox) {
free_display_info(di);
return NULL;
}
gtk_container_set_border_width(GTK_CONTAINER(vbox), 18);
gtk_container_add(GTK_CONTAINER(ebox), vbox);
GtkWidget* const hbox = gtk_hbox_new(FALSE, 5);
if (!hbox) {
free_display_info(di);
return NULL;
}
gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, TRUE, 0);
di->widget.title = gtk_label_new(NULL);
if (!di->widget.title) {
free_display_info(di);
return NULL;
}
gtk_widget_modify_fg(di->widget.title, GTK_STATE_NORMAL, color_white);
gtk_widget_modify_font(di->widget.title, font_sans12_desc);
gtk_box_pack_start(GTK_BOX(hbox), di->widget.title, FALSE, FALSE, 0);
di->widget.text = gtk_label_new(NULL);
if (!di->widget.text) {
free_display_info(di);
return NULL;
}
gtk_widget_modify_fg(di->widget.text, GTK_STATE_NORMAL, color_white);
gtk_widget_modify_font(di->widget.text, font_sans8_desc);
g_signal_connect(G_OBJECT(di->widget.text), "size-allocate", G_CALLBACK(label_size_allocate), NULL);
gtk_label_set_justify(GTK_LABEL(di->widget.text), GTK_JUSTIFY_LEFT);
gtk_label_set_line_wrap(GTK_LABEL(di->widget.text), TRUE);
gtk_label_set_line_wrap_mode(GTK_LABEL(di->widget.text), PANGO_WRAP_CHAR);
gtk_box_pack_start(GTK_BOX(vbox), di->widget.text, TRUE, FALSE, 0);
gtk_widget_set_app_paintable(di->widget.popup, TRUE);
return di;
}
static inline DISPLAY_INFO*
reset_display_info(DISPLAY_INFO* const di, NOTIFICATION_INFO* const ni) {
di->timeout = 500;
di->offset = 0;
di->pos = 0;
di->hover = FALSE;
free_notification_info(di->ni);
di->ni = ni;
gtk_widget_hide_all(di->widget.popup);
gtk_window_set_opacity(GTK_WINDOW(di->widget.popup), 0);
remove_icon(di);
return di;
}
static inline gpointer
list_pop_front(GList** list) {
if (!list) return NULL;
const gpointer elem = g_list_nth_data(*list, 0);
*list = g_list_remove(*list, elem);
return elem;
}
static inline DISPLAY_INFO*
get_popup_skelton(NOTIFICATION_INFO* const ni) {
DISPLAY_INFO* const di = (DISPLAY_INFO*) list_pop_front(&popup_collections);
if (di) {
di->ni = ni;
return di;
}
return reset_display_info(create_popup_skelton(), ni);
}
G_MODULE_EXPORT gboolean
display_show(NOTIFICATION_INFO* const ni) {
DISPLAY_INFO* const di = get_popup_skelton(ni);
if (!di) return FALSE;
gint
is_differ_pos(gconstpointer p, gconstpointer GOL_UNUSED_ARG(user_data)) {
return ((const DISPLAY_INFO*) p)->pos == di->pos++;
}
GList* const found = g_list_find_custom(notifications, NULL, is_differ_pos);
if (found) --di->pos;
const gint vert_count = screen_rect.height / 110;
const gint cx = di->pos / vert_count;
const gint cy = di->pos % vert_count;
di->x = screen_rect.x + screen_rect.width - (cx + 1) * 250;
di->y = screen_rect.y + screen_rect.height - (cy + 1) * 110;
if (di->y < 0) {
free_display_info(di);
return FALSE;
}
notifications = g_list_insert_before(notifications, found, di);
box_set_icon_if_has(di);
gtk_label_set_text(DISPLAY_TITLE_FIELD(di), di->ni->title);
gtk_label_set_text(DISPLAY_TEXT_FIELD(di), di->ni->text);
gtk_window_move(GTK_WINDOW(di->widget.popup), di->x, di->y);
gtk_widget_show_all(di->widget.popup);
g_timeout_add(10, display_animation_func, di);
if (pixmap == NULL) {
pixmap = gdk_pixmap_create_from_xpm_d(di->widget.popup->window, &bitmap, NULL, test);
}
gdk_drawable_get_size(pixmap, &pixmap_width, &pixmap_height);
gtk_widget_set_size_request(di->widget.popup, pixmap_width, pixmap_height);
gdk_window_shape_combine_mask(di->widget.popup->window, bitmap, 0, 0);
g_signal_connect(G_OBJECT(di->widget.popup), "expose-event", G_CALLBACK(display_expose), di);
return FALSE;
}
G_MODULE_EXPORT gboolean
display_init() {
gdk_color_parse("white", &inst_color_white_);
font_sans12_desc = pango_font_description_new();
pango_font_description_set_family(font_sans12_desc, "Sans");
pango_font_description_set_size(font_sans12_desc, 12 * PANGO_SCALE);
font_sans8_desc = pango_font_description_new();
pango_font_description_set_family(font_sans8_desc, "Sans");
pango_font_description_set_size(font_sans8_desc, 8 * PANGO_SCALE);
GdkScreen* const screen = gdk_screen_get_default();
const gint monitor_num = gdk_screen_get_primary_monitor(screen);
gdk_screen_get_monitor_geometry(screen, monitor_num, &screen_rect);
return TRUE;
}
G_MODULE_EXPORT void
display_term() {
pango_font_description_free(font_sans12_desc);
pango_font_description_free(font_sans8_desc);
void
list_free_deep(GList* const list) {
void
deleter_wrapper(gpointer data, gpointer GOL_UNUSED_ARG(user_data)) {
free_display_info((DISPLAY_INFO*) data);
}
g_list_foreach(list, deleter_wrapper, NULL);
g_list_free(list);
}
list_free_deep(notifications);
list_free_deep(popup_collections);
// FIXME: g_list_free_full will fail symbol lookup.
//void
//deleter(gpointer data) {
// free_display_info((DISPLAY_INFO*) data);
//}
//g_list_free_full(notifications, deleter);
//g_list_free_full(popup_collections, deleter);
}
G_MODULE_EXPORT const gchar*
display_name() {
return "test";
}
G_MODULE_EXPORT const gchar*
display_description() {
return
"<span size=\"large\"><b>test</b></span>\n"
"<span>This is test notification display.</span>\n"
"<span>Fade-in black box. And fadeout after a while.</span>\n";
}
G_MODULE_EXPORT char**
display_thumbnail() {
return display_test;
}
G_MODULE_EXPORT char*
display_get_param() {
return param;
}
G_MODULE_EXPORT void
display_set_param(const gchar* p) {
if (param) g_free(param);
param = g_strdup(p);
}
// vim:set et sw=2 ts=2 ai:
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment