Merge pull request #278 from dunst-project/unit_testing

Merge the work that was done in a separate repository.
This commit is contained in:
Nikos Tsipinakis 2017-01-14 20:32:56 +02:00 committed by GitHub
commit f0ca0c8e6b
49 changed files with 2834 additions and 856 deletions

2
.gitignore vendored
View File

@ -5,4 +5,6 @@ vgcore.*
config.h
dunst.1
org.knopwob.dunst.service
dunst.systemd.service
dunstify
test/test

17
.travis.yml Normal file
View File

@ -0,0 +1,17 @@
before_install:
- sudo apt-get -qq update
- sudo apt-get install -y libdbus-1-dev libx11-dev libxinerama-dev libxft-dev libxss-dev libxdg-basedir-dev libglib2.0-dev libpango1.0-dev libcairo2-dev libfreetype6-dev libnotify-dev libgtk2.0-dev
dist: trusty
sudo: required
language: c
script: make && make test
compiler:
- clang
- gcc
notifications:
irc:
channels:
- "chat.freenode.net#dunst"
on_success: always
on_failure: always

23
INSTALL
View File

@ -1,23 +0,0 @@
The following dependencies are needed:
dbus
libxinerama
libxft
libxss
libxdg-basedir
glib
pango/cairo
On Debian and Debian-based distros you'll probably also need the *-dev packages.
With all dependencies present you can build dunst with:
$ make
If you're getting compilation errors after an update try compiling
with the default config.h.
If you didn't edit your config.h you can just delete it.
and install with:
$ sudo make PREFIX=/usr install

View File

@ -3,15 +3,10 @@
include config.mk
SRC = x.c \
dunst.c \
dbus.c \
utils.c \
option_parser.c \
settings.c \
rules.c \
menu.c \
notification.c
CFLAGS += -I.
LDFLAGS += -L.
SRC = $(shell ls src/*.c)
OBJ = ${SRC:.c=.o}
V ?= 0
@ -29,7 +24,7 @@ options:
.c.o:
@echo CC -c $<
${CC} -c $< ${CFLAGS}
${CC} -o $@ -c $< ${CFLAGS}
${OBJ}: config.h config.mk
@ -38,52 +33,75 @@ config.h: config.def.h
@echo creating $@ from $<
@cp $< $@
dunst: ${OBJ}
dunst: options ${OBJ} main.o
@echo "${CC} ${CFLAGS} -o $@ ${OBJ} ${LDFLAGS}"
@${CC} ${CFLAGS} -o $@ ${OBJ} ${LDFLAGS}
@${CC} ${CFLAGS} -o $@ ${OBJ} main.o ${LDFLAGS}
dunstify:
@${CC} -o $@ dunstify.c -std=c99 $(shell pkg-config --libs --cflags glib-2.0 libnotify)
@${CC} ${CFLAGS} -o $@ dunstify.c -std=c99 $(shell pkg-config --libs --cflags glib-2.0 libnotify)
debug: ${OBJ}
@echo CC -o $@
@${CC} ${CFLAGS} -O0 -o dunst ${OBJ} ${LDFLAGS}
clean:
@echo cleaning
rm -f ${OBJ}
rm -f dunst
rm -f dunst.1
clean-dunst:
rm -f dunst ${OBJ} main.o
rm -f org.knopwob.dunst.service
rm -f core
rm -f dunst.systemd.service
clean-dunstify:
rm -f dunstify
doc: dunst.1
dunst.1: README.pod
clean-doc:
rm -f docs/dunst.1
clean: clean-dunst clean-dunstify clean-doc test-clean
distclean: clean clean-config
clean-config:
rm -f config.h
doc: docs/dunst.1
docs/dunst.1: docs/dunst.pod
pod2man --name=dunst -c "Dunst Reference" --section=1 --release=${VERSION} $< > $@
service:
@sed "s|##PREFIX##|$(PREFIX)|" org.knopwob.dunst.service.in > org.knopwob.dunst.service
@sed "s|##PREFIX##|$(PREFIX)|" contrib/dunst.systemd.service.in > dunst.systemd.service
install: all
@echo installing executables to ${DESTDIR}${PREFIX}/bin
install-dunst: dunst doc
mkdir -p ${DESTDIR}${PREFIX}/bin
cp -f dunst ${DESTDIR}${PREFIX}/bin
chmod 755 ${DESTDIR}${PREFIX}/bin/dunst
@echo installing manual pages to ${DESTDIR}${MANPREFIX}/man1
install -m755 dunst ${DESTDIR}${PREFIX}/bin
mkdir -p ${DESTDIR}${MANPREFIX}/man1
cp -f dunst.1 ${DESTDIR}${MANPREFIX}/man1/
chmod 644 ${DESTDIR}${MANPREFIX}/man1/dunst.1
mkdir -p "${DESTDIR}${PREFIX}/share/dunst"
cp -f dunstrc ${DESTDIR}${PREFIX}/share/dunst
mkdir -p "${DESTDIR}${PREFIX}/share/dbus-1/services/"
cp -vf org.knopwob.dunst.service "${DESTDIR}${PREFIX}/share/dbus-1/services/org.knopwob.dunst.service"
install -m644 docs/dunst.1 ${DESTDIR}${MANPREFIX}/man1
install-doc:
mkdir -p ${DESTDIR}${PREFIX}/share/dunst
install -m644 dunstrc ${DESTDIR}${PREFIX}/share/dunst
install-service: service
mkdir -p ${DESTDIR}${PREFIX}/share/dbus-1/services/
install -m644 org.knopwob.dunst.service ${DESTDIR}${PREFIX}/share/dbus-1/services
install: install-dunst install-doc install-service
uninstall:
@echo removing executables from ${DESTDIR}${PREFIX}/bin
@echo Removing executables from ${DESTDIR}${PREFIX}/bin
rm -f ${DESTDIR}${PREFIX}/bin/dunst
@echo removing manual page from ${DESTDIR}${MANPREFIX}/man1
rm -f ${DESTDIR}${MANPREFIX}/man1/dunst
rm -f ${DESTDIR}${PREFIX}/share/dbus-1/service/org.knopwob.dunst.service
@echo Removing manual page from ${DESTDIR}${MANPREFIX}/man1
rm -f ${DESTDIR}${MANPREFIX}/man1/dunst.1
@echo Removing service file from ${DESTDIR}${PREFIX}/share/dbus-1/services
rm -f ${DESTDIR}${PREFIX}/share/dbus-1/services/org.knopwob.dunst.service
@echo Removing documentation directory ${DESTDIR}${PREFIX}/share/dunst
rm -rf ${DESTDIR}${PREFIX}/share/dunst
test: test/test
cd test && ./test
TEST_SRC = $(shell ls test/*.c)
TEST_OBJ = $(TEST_SRC:.c=.o)
test/test: ${OBJ} ${TEST_OBJ}
${CC} ${CFLAGS} -o $@ ${TEST_OBJ} ${OBJ} ${LDFLAGS}
test-clean:
rm -f test/test test/*.o
.PHONY: all options clean dist install uninstall

46
README.md Normal file
View File

@ -0,0 +1,46 @@
## Dunst
## Description
Dunst is a highly configurable and lightweight notification daemon.
## Compiling
Dunst has a number of build dependencies that must be present before attempting configuration. The names are different depending on distribution:
- dbus
- libxinerama
- libxft
- libxss
- libxdg-basedir
- glib
- pango
- cairo
On Debian and Debian-based distros you'll probably also need the *-dev packages.
To build it:
```bash
cd dunst
make
sudo make PREFIX=/usr install
```
## Bug reports
Please use the [issue tracker][issue-tracker] provided by GitHub to send us bug reports or feature requests.
## Author
written by Sascha Kruse <knopwob@googlemail.com>
[issue-tracker]: https://github.com/knopwob/dunst/issues
## Copyright
copyright 2013 Sascha Kruse and contributors (see LICENSE for licensing information)
If you feel that copyrights are violated, please send me an email.

View File

@ -1,201 +0,0 @@
=head1 NAME
dunst - A customizable and lightweight notification-daemon
=head1 SYNOPSIS
dunst [-geometry geom] [-shrink shrink] [-fn font] [-nf/nb/lf/lb/cf/cb color]
[-to/nto/lto/cto secs] [-format fmt] [-key key] [-mod mod] [-mon n]
[-t/title title] [-c/class class] [-v]
=head1 DESCRIPTION
Dunst is a highly configurable and lightweight notification daemon.
=head1 OPTIONS
=over 4
=item B<-h/--help>
display help message.
=item B<-fn font>
Defines the font or font set used. Optionally set the size as a decimal number after the font name and space.
Multiple font options can be separated with commas.
=item B<-lb/nb/cb color>
defines the background color of low/normal/critical messages.
=item B<-lf/nf/cf color>
defines the foreground color of low/normal/critical messages.
=item B<-to secs>
defines the default timeout. Can be (partially) overridden by
the following options.
=item B<-lto/nto/cto secs>
timeouts for low/normal/critical messages.
=item B<-key key>
remove notification by pressing key.
=item B<-all_key key>
remove all notifications by pressing key.
=item B<-history_key key>
redisplay last notification by pressing key.
=item B<-format fmt>
defines the format of the message. See FORMAT.
=item B<-mon n>
show the notification on monitor n.
=item B<-follow mode>
display notifications on focused monitor. Possible options are "mouse" to follow the mouse cursor, "keyboard" to follow the window with the keyboard focus and "none" to disable focus following. "mouse" and "keyboard" overwrite the -mon option.
=item B<-s>
sort messages by urgency.
=item B<-ns>
don't sort messages by urgency.
=item B<-geometry [{width}]x{height}][+/-{x}+/-{y}]>
The geometry of the message window. The height is measured
in lines everything else in pixels. If the width is omitted
but the height is given ("-geometry x2"), the message window
expands over the whole screen (dmenu-like).
If width is 0, the window expands to the longest
message displayed. A positive x is measured from the left,
a negative from the right side of the screen.
Y is measured from the top and down respectively.
see also EXAMPLES show the notification on monitor n.
=item B<-t/-title title>
Define the title of notification windows spawned by dunst.
=item B<-t/-class class>
Define the class of notification windows spawned by dunst.
=item B<-shrink>
Shrink window if it's smaller than the width. Will be ignored if width is 0.
=item B<-lh/-line_height> height
The height of a single line in pixel. If the height is smaller than the font height, it will get raised to the font height.
=item B<-print>
Print notifications to stdout. This might be useful for logging, setting up rules or using the output in other scripts.
=item B<-v/--version>
print version information.
=item B<-config file>
use alternative config file.
=item B<-always_run_script>
Always run rule-defined scripts, even if the notification is suppressed with format = "".
=back
=head1 FORMAT
fmt is a string containing placeholders. The placeholders will be replaced with the corresponding text, or if the text is not present, nothing. Possible placeholders are:
=over 4
=item B<%a> appname
=item B<%s> summary
=item B<%b> body
=item B<%i> iconname (including its path)
=item B<%I> iconname (without its path)
=item B<%p> progress value ([ 0%] to [100%])
=back
=head1 COLORS
Every option that needs a color as argument accepts #RGB, #RRGGBB and
X color names.
=head1 NOTIFY-SEND
dunst is able to get different colors for a message via notify-send.
In order to do that you have to add a hint via the -h option.
The progress value can be set with a hint, too.
=over 4
=item notify-send -h string:fgcolor:#ff4444
=item notify-send -h string:bgcolor:#4444ff -h string:fgcolor:#ff4444
=item notify-send -h int:value:42 "Working ..."
=back
=head1 MISCELLANEOUS
Dunst can be paused by sending a notification with a summary of "DUNST_COMMAND_PAUSE"
and resumed with a summary of "DUNST_COMMAND_RESUME". Alternatively you can send SIGUSR1 and SIGUSR2 to pause and unpause respectively. For Example:
=over 4
=item killall -SIGUSR1 dunst # pause
=item killall -SIGUSR2 dunst # resume
=back
When paused dunst will not display any notifications but keep all notifications in a queue.
This can for example be wrapped around a screen locker (i3lock, slock) to prevent flickering
of notifications through the lock and to read all missed notifications after returning to the computer.
=head1 CONFIGURATION
An example configuration file is included (usually /usr/share/dunst/dunstrc).
To change the configuration, copy this file to ~/.config/dunst/dunstrc and edit
it accordingly.
=head1 AUTHOR
written by Sascha Kruse <knopwob@googlemail.com>
=head1 COPYRIGHT
copyright 2013 Sascha Kruse and contributors (see LICENSE for licensing information)
If you feel that copyrights are violated, please send me an email.
=head1 SEE ALSO
dwm(1), dmenu(1), twmn(1), notify-send(1)

View File

@ -1,3 +1,7 @@
===================================================================================
Release Notes For v1.0.0
===================================================================================
PACKAGE MAINTAINERS:
There are new dependencies introduced with this version:
*glib
@ -53,3 +57,23 @@ PACKAGE MAINTAINERS:
Please don't open bug reports when dunstify doesn't work with your
version of libnotify
===================================================================================
Release Notes For v0.4.0
===================================================================================
Since dunst has lost its ability to show notifications independend of
dbus/libnotify a long time ago I think it is time that the describtion reflects
that. Even though this breaks the acronym. So if you're a packager please update
the package description to read something like:
short:
"Dunst - a dmenu-ish notification-daemon"
long:
"Dunst is a highly configurable and lightweight notification-daemon"
Release Tarballs are now available at:
http://www.knopwob.org/public/dunst-release/
For more information have a look at the CHANGELOG and the new options in dunstrc.

View File

@ -1,17 +0,0 @@
Release Notes For v0.4.0
Since dunst has lost its ability to show notifications independend of
dbus/libnotify a long time ago I think it is time that the describtion reflects
that. Even though this breaks the acronym. So if you're a packager please update
the package description to read something like:
short:
"Dunst - a dmenu-ish notification-daemon"
long:
"Dunst is a highly configurable and lightweight notification-daemon"
Release Tarballs are now available at:
http://www.knopwob.org/public/dunst-release/
For more information have a look at the CHANGELOG and the new options in dunstrc.

View File

@ -56,6 +56,8 @@ char *dmenu = "/usr/bin/dmenu";
char *browser = "/usr/bin/firefox";
int max_icon_size = 32;
/* paths to default icons */
char *icon_folders = "/usr/share/icons/gnome/16x16/status/:/usr/share/icons/gnome/16x16/devices/";

View File

@ -5,7 +5,7 @@ MANPREFIX = ${PREFIX}/share/man
# In dist tarballs, the version is stored in the VERSION files.
VERSION := '$(shell [ -f VERSION ] && cat VERSION)'
ifeq ('',$(VERSION))
VERSION := $(shell git describe)
VERSION := $(shell git describe --tags)
endif
# Xinerama, comment if you don't want it
@ -22,12 +22,12 @@ ifeq (${PKG_CONFIG}, ${EMPTY})
endif
# flags
CPPFLAGS += -D_BSD_SOURCE -DVERSION=\"${VERSION}\" ${XINERAMAFLAGS} ${INIFLAGS}
CPPFLAGS += -D_DEFAULT_SOURCE -DVERSION=\"${VERSION}\" ${XINERAMAFLAGS} ${INIFLAGS}
CFLAGS += -g --std=gnu99 -pedantic -Wall -Wno-overlength-strings -Os ${STATIC} ${CPPFLAGS} ${EXTRACFLAGS}
pkg_config_packs := dbus-1 x11 freetype2 xext xft xscrnsaver \
"glib-2.0 >= 2.36" gio-2.0 \
pango cairo pangocairo
pango cairo pangocairo gdk-2.0
# check if we need libxdg-basedir
ifeq (,$(findstring STATIC_CONFIG,$(CFLAGS)))

View File

@ -0,0 +1,11 @@
[Unit]
Description=Dunst notification daemon
[Service]
Type=simple
ExecStart=##PREFIX##/bin/dunst
Environment=DISPLAY=:0
[Install]
WantedBy=multi-user.target

304
docs/dunst.pod Normal file
View File

@ -0,0 +1,304 @@
=head1 NAME
dunst - A customizable and lightweight notification-daemon
=head1 SYNOPSIS
dunst [-conf file] [-fn font] [-geometry geom] [-lf/lb/lfr/nf/nb/nfr/cf/cb/cfr color]
[-lto/nto/cto secs] [-lh/nh/sep_height height] [-format fmt] [-follow mode] [-mon n]
[-history_length n] [-t/title title] [-c/class class] [-v]
=head1 DESCRIPTION
Dunst is a highly configurable and lightweight notification daemon.
=head1 OPTIONS
=over 4
=item B<-h/--help>
display help message.
=item B<-conf/-config file>
use alternative config file.
=item B<-fn font>
Defines the font or font set used. Optionally set the size as a decimal number after the font name and space.
Multiple font options can be separated with commas.
=item B<-geometry [{width}]x{height}][+/-{x}+/-{y}]>
The geometry of the message window. The height is measured
in lines everything else in pixels. If the width is omitted
but the height is given ("-geometry x2"), the message window
expands over the whole screen (dmenu-like).
If width is 0, the window expands to the longest
message displayed. A positive x is measured from the left,
a negative from the right side of the screen.
Y is measured from the top and down respectively.
=item B<-lf/nf/cf color>
Defines the foreground color of low/normal/critical messages.
=item B<-lb/nb/cb color>
Defines the background color of low/normal/critical messages.
=item B<-lfr/nfr/cfr color>
Defines the frame color of low/normal/critical messages.
=item B<-lto/nto/cto secs>
Defines timeouts for low/normal/critical messages.
=item B<-lh/-line_height height>
Defines the height of a single line in pixel. If the height is smaller than the font height, it will get raised to the font height.
=item B<-nh/-notification_height height>
Defines the height of the notification window.
=item B<-sep_height/-separator_height height>
Defines the height of the separator line.
=item B<-format fmt>
Defines the format of the message. See FORMAT.
=item B<-follow mode>
Display notifications on focused monitor. Possible options are "mouse" to follow the mouse cursor, "keyboard" to follow the window
with the keyboard focus and "none" to disable focus following. "mouse" and "keyboard" overwrite the -mon option.
=item B<-mon n>
Show the notification on monitor n.
=item B<-history_length n>
Max amount of notifications kept in history.
=item B<-t/-title title>
Defines the title of notification windows spawned by dunst.
=item B<-t/-class class>
Defines the class of notification windows spawned by dunst.
=item B<-key key>
Remove notification by pressing key.
=item B<-all_key key>
Remove all notifications by pressing key.
=item B<-history_key key>
Redisplay last notification by pressing key.
=item B<-context_key key>
Shortcut for context menu.
=item B<-alignment position>
Defines the align position of notifications to left/center/right.
=item B<-icon_position position>
Defines the align position of icons to left/right or off to disable icons.
=item B<-max_icon_size size>
Defines the max size for icons to scale. If icon_position is set to off, icons won't be displayed.
=item B<-icon_folders path>
Defines the path to the folder with the default icons.
=item B<-li/ni/ci icon>
Defines the icon for low/normal/critical messages.
=item B<-padding pad>
Defines padding between text and separator.
=item B<-horizontal_padding pad>
Defines horizontal padding.
=item B<-transparency range>
Defines transparency range between 0-100
=item B<-sep_color/-separator_color color>
Defines color of the separator line.
=item B<-frame_color color>
Defines color of the frame around window.
=item B<-frame_width width>
Defines width of frame around window.
=item B<-idle_threshold secs>
Don't timeout notifications if user is longer idle than threshold.
=item B<-show_age_threshold secs>
Show age of message if message is older than show_age_threshold seconds. Set to -1 to disable.
=item B<-bounce_freq freq>
The frequency with which text that is longer than the notification window allows bounces back and forth.
=item B<-markup>
Allow markups in notifications/formats.
=item B<-plain>
Treat incoming notifications as plain text.
=item B<-indicate_hidden>
Show how many notifications are hidden.
=item B<-word_wrap>
Truncating long lines or do word wrap.
=item B<-ignore_newline>
Ignore newline characters in notifications.
=item B<-stack_duplicates>
Merge multiple notifications with the same content.
=item B<-hide_duplicates_count>
Hide count of the merged notifications with the same content.
=item B<-sticky_history>
Don't timeout notifications popped up from history.
=item B<-show_indicators>
Show indicators for actions and URLs.
=item B<-sort>
Sort messages by urgency.
=item B<-shrink>
Shrink window if it's smaller than the width. Will be ignored if width is 0.
=item B<-print>
Print notifications to stdout. This might be useful for logging, setting up rules or using the output in other scripts.
=item B<-v/--version>
Print version information.
=item B<-always_run_script>
Always run rule-defined scripts, even if the notification is suppressed with format = "".
=back
=head1 FORMAT
fmt is a string containing placeholders.
The placeholders will be replaced with the corresponding text, or if the text is not present, nothing. Possible placeholders are:
=over 4
=item B<%a> appname
=item B<%s> summary
=item B<%b> body
=item B<%i> iconname (including its path)
=item B<%I> iconname (without its path)
=item B<%p> progress value ([ 0%] to [100%])
=back
=head1 COLORS
Every option that needs a color as argument accepts #RGB, #RRGGBB and
X color names.
=head1 NOTIFY-SEND
dunst is able to get different colors for a message via notify-send.
In order to do that you have to add a hint via the -h option.
The progress value can be set with a hint, too.
=over 4
=item notify-send -h string:fgcolor:#ff4444
=item notify-send -h string:bgcolor:#4444ff -h string:fgcolor:#ff4444
=item notify-send -h int:value:42 "Working ..."
=back
=head1 MISCELLANEOUS
Dunst can be paused by sending a notification with a summary of "DUNST_COMMAND_PAUSE"
and resumed with a summary of "DUNST_COMMAND_RESUME". Alternatively you can send SIGUSR1 and SIGUSR2 to pause and unpause respectively. For Example:
=over 4
=item killall -SIGUSR1 dunst # pause
=item killall -SIGUSR2 dunst # resume
=back
When paused dunst will not display any notifications but keep all notifications in a queue.
This can for example be wrapped around a screen locker (i3lock, slock) to prevent flickering
of notifications through the lock and to read all missed notifications after returning to the computer.
=head1 CONFIGURATION
An example configuration file is included (usually /usr/share/dunst/dunstrc).
To change the configuration, copy this file to ~/.config/dunst/dunstrc and edit
it accordingly.
=head1 AUTHOR
written by Sascha Kruse <knopwob@googlemail.com>
=head1 COPYRIGHT
copyright 2013 Sascha Kruse and contributors (see LICENSE for licensing information)
If you feel that copyrights are violated, please send me an email.
=head1 SEE ALSO
dwm(1), dmenu(1), twmn(1), notify-send(1)

View File

@ -49,7 +49,7 @@ void print_capabilities(void)
GList *caps = notify_get_server_caps();
for (GList *iter = caps; iter; iter = iter->next) {
if (strlen(iter->data) > 0) {
g_print("%s\n", iter->data);
g_print("%s\n", (char *)iter->data);
}
}
}
@ -171,7 +171,7 @@ int get_id(NotifyNotification *n)
return kn->id;
}
int put_id(NotifyNotification *n, guint32 id)
void put_id(NotifyNotification *n, guint32 id)
{
knickers *kn = n->priv;
@ -199,7 +199,7 @@ void closed(NotifyNotification *n, gpointer foo)
void add_action(NotifyNotification *n, char *str)
{
char *action = str;
char *label = strstr(str, ",");
char *label = strchr(str, ',');
if (!label || *(label+1) == '\0') {
g_printerr("Malformed action. Excpected \"action,label\", got \"%s\"", str);
@ -215,14 +215,14 @@ void add_action(NotifyNotification *n, char *str)
void add_hint(NotifyNotification *n, char *str)
{
char *type = str;
char *name = strstr(str, ":");
char *name = strchr(str, ':');
if (!name || *(name+1) == '\0') {
g_printerr("Malformed hint. Expected \"type:name:value\", got \"%s\"", str);
return;
}
*name = '\0';
name++;
char *value = strstr(name, ":");
char *value = strchr(name, ':');
if (!value || *(value+1) == '\0') {
g_printerr("Malformed hint. Expected \"type:name:value\", got \"%s\"", str);
return;
@ -312,3 +312,5 @@ int main(int argc, char *argv[])
die(0);
}
/* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */

29
dunstrc
View File

@ -69,6 +69,12 @@
# screen width minus the width defined in within the geometry option.
geometry = "300x5-30+20"
# Define the title of the windows spawned by dunst
title = Dunst
# Define the class of the windows spawned by dunst
class = Dunst
# Shrink window if it's smaller than the width. Will be ignored if
# width is 0.
shrink = no
@ -99,6 +105,12 @@
# will be ignored.
follow = mouse
# Merge multiple notifications with the same content
stack_duplicates = true
# Hide count of the merged notifications with the same content
hide_duplicates_count = false
# Should a notification popped up from history be sticky or timeout
# as if it would normally do.
sticky_history = yes
@ -151,9 +163,15 @@
# Align icons left/right/off
icon_position = off
# Scale larger icons down to this size, set to 0 to disable
max_icon_size = 32
# Paths to default icons.
icon_folders = /usr/share/icons/gnome/16x16/status/:/usr/share/icons/gnome/16x16/devices/
# Always run rule-defined scripts, even if the notification is suppressed
always_run_stript = true
[frame]
width = 3
color = "#aaaaaa"
@ -173,7 +191,8 @@
# Redisplay last message(s).
# On the US keyboard layout "grave" is normally above TAB and left
# of "1".
# of "1". Make sure this key actually exists on your keyboard layout,
# e.g. check output of 'xmodmap -pke'
history = ctrl+grave
# Context menu.
@ -185,17 +204,23 @@
background = "#222222"
foreground = "#888888"
timeout = 10
# Icon for notifications with low urgency, uncomment to enable
#icon = /path/to/icon
[urgency_normal]
background = "#285577"
foreground = "#ffffff"
timeout = 10
# Icon for notifications with normal urgency, uncomment to enable
#icon = /path/to/icon
[urgency_critical]
background = "#900000"
foreground = "#ffffff"
frame_color = "#ff0000"
timeout = 0
# Icon for notifications with critical urgency, uncomment to enable
#icon = /path/to/icon
# Every section that isn't one of the above is interpreted as a rules to
# override settings for certain messages.

7
main.c Normal file
View File

@ -0,0 +1,7 @@
#include "src/dunst.h"
int main(int argc, char *argv[])
{
return dunst_main(argc, argv);
}
/* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */

View File

@ -59,21 +59,22 @@ static const char *introspection_xml =
" </interface>"
"</node>";
static void onGetCapabilities(GDBusConnection * connection,
static void on_get_capabilities(GDBusConnection * connection,
const gchar * sender,
const GVariant * parameters,
GDBusMethodInvocation * invocation);
static void onNotify(GDBusConnection * connection,
static void on_notify(GDBusConnection * connection,
const gchar * sender,
GVariant * parameters, GDBusMethodInvocation * invocation);
static void onCloseNotification(GDBusConnection * connection,
static void on_close_notification(GDBusConnection * connection,
const gchar * sender,
GVariant * parameters,
GDBusMethodInvocation * invocation);
static void onGetServerInformation(GDBusConnection * connection,
static void on_get_server_information(GDBusConnection * connection,
const gchar * sender,
const GVariant * parameters,
GDBusMethodInvocation * invocation);
static RawImage * get_raw_image_from_data_hint(GVariant *icon_data);
void handle_method_call(GDBusConnection * connection,
const gchar * sender,
@ -84,13 +85,13 @@ void handle_method_call(GDBusConnection * connection,
GDBusMethodInvocation * invocation, gpointer user_data)
{
if (g_strcmp0(method_name, "GetCapabilities") == 0) {
onGetCapabilities(connection, sender, parameters, invocation);
on_get_capabilities(connection, sender, parameters, invocation);
} else if (g_strcmp0(method_name, "Notify") == 0) {
onNotify(connection, sender, parameters, invocation);
on_notify(connection, sender, parameters, invocation);
} else if (g_strcmp0(method_name, "CloseNotification") == 0) {
onCloseNotification(connection, sender, parameters, invocation);
on_close_notification(connection, sender, parameters, invocation);
} else if (g_strcmp0(method_name, "GetServerInformation") == 0) {
onGetServerInformation(connection, sender, parameters,
on_get_server_information(connection, sender, parameters,
invocation);
} else {
printf("WARNING: sender: %s; unknown method_name: %s\n", sender,
@ -98,7 +99,7 @@ void handle_method_call(GDBusConnection * connection,
}
}
static void onGetCapabilities(GDBusConnection * connection,
static void on_get_capabilities(GDBusConnection * connection,
const gchar * sender,
const GVariant * parameters,
GDBusMethodInvocation * invocation)
@ -118,7 +119,7 @@ static void onGetCapabilities(GDBusConnection * connection,
g_variant_unref(value);
}
static void onNotify(GDBusConnection * connection,
static void on_notify(GDBusConnection * connection,
const gchar * sender,
GVariant * parameters, GDBusMethodInvocation * invocation)
{
@ -140,9 +141,11 @@ static void onNotify(GDBusConnection * connection,
gchar *fgcolor = NULL;
gchar *bgcolor = NULL;
gchar *category = NULL;
RawImage *raw_icon = NULL;
actions->actions = NULL;
actions->count = 0;
actions->dmenu_str = NULL;
{
GVariantIter *iter = g_variant_iter_new(parameters);
@ -232,6 +235,23 @@ static void onNotify(GDBusConnection * connection,
dict_value, NULL);
}
dict_value =
g_variant_lookup_value(content,
"image-data",
G_VARIANT_TYPE("(iiibiiay)"));
if (!dict_value) {
dict_value =
g_variant_lookup_value(content,
"icon_data",
G_VARIANT_TYPE("(iiibiiay)"));
}
if (dict_value) {
raw_icon =
get_raw_image_from_data_hint(
dict_value);
}
dict_value =
g_variant_lookup_value(content,
"value",
@ -275,14 +295,12 @@ static void onNotify(GDBusConnection * connection,
}
}
notification *n = malloc(sizeof(notification));
if(n == NULL) {
die("Unable to allocate memory", EXIT_FAILURE);
}
notification *n = notification_create();
n->appname = appname;
n->summary = summary;
n->body = body;
n->icon = icon;
n->raw_icon = raw_icon;
n->timeout = timeout;
n->allow_markup = settings.allow_markup;
n->plain_text = settings.plain_text;
@ -314,7 +332,7 @@ static void onNotify(GDBusConnection * connection,
run(NULL);
}
static void onCloseNotification(GDBusConnection * connection,
static void on_close_notification(GDBusConnection * connection,
const gchar * sender,
GVariant * parameters,
GDBusMethodInvocation * invocation)
@ -326,7 +344,7 @@ static void onCloseNotification(GDBusConnection * connection,
g_dbus_connection_flush(connection, NULL, NULL, NULL);
}
static void onGetServerInformation(GDBusConnection * connection,
static void on_get_server_information(GDBusConnection * connection,
const gchar * sender,
const GVariant * parameters,
GDBusMethodInvocation * invocation)
@ -339,10 +357,10 @@ static void onGetServerInformation(GDBusConnection * connection,
g_dbus_connection_flush(connection, NULL, NULL, NULL);
}
void notificationClosed(notification * n, int reason)
void notification_closed(notification * n, int reason)
{
if (!dbus_conn) {
printf("DEBUG: notificationClosed but not (yet) connected\n");
printf("DEBUG: notification_closed but not (yet) connected\n");
return;
}
@ -356,12 +374,12 @@ void notificationClosed(notification * n, int reason)
"NotificationClosed", body, &err);
if (err) {
printf("notificationClosed ERROR\n");
printf("notification_closed ERROR\n");
}
}
void actionInvoked(notification * n, const char *identifier)
void action_invoked(notification * n, const char *identifier)
{
GVariant *body = g_variant_new("(us)", n->id, identifier);
GError *err = NULL;
@ -412,6 +430,40 @@ static void on_name_lost(GDBusConnection * connection,
exit(1);
}
static RawImage * get_raw_image_from_data_hint(GVariant *icon_data)
{
RawImage *image = malloc(sizeof(RawImage));
GVariant *data_variant;
gsize expected_len;
g_variant_get (icon_data,
"(iiibii@ay)",
&image->width,
&image->height,
&image->rowstride,
&image->has_alpha,
&image->bits_per_sample,
&image->n_channels,
&data_variant);
expected_len = (image->height - 1) * image->rowstride + image->width
* ((image->n_channels * image->bits_per_sample + 7) / 8);
if (expected_len != g_variant_get_size (data_variant)) {
fprintf(stderr, "Expected image data to be of length %" G_GSIZE_FORMAT
" but got a " "length of %" G_GSIZE_FORMAT,
expected_len,
g_variant_get_size (data_variant));
free(image);
return NULL;
}
image->data = (guchar *) g_memdup (g_variant_get_data (data_variant),
g_variant_get_size (data_variant));
return image;
}
int initdbus(void)
{
guint owner_id;
@ -434,7 +486,10 @@ int initdbus(void)
void dbus_tear_down(int owner_id)
{
if (introspection_data)
g_dbus_node_info_unref(introspection_data);
g_bus_unown_name(owner_id);
}
/* vim: set ts=8 sw=8 tw=0: */
/* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */

View File

@ -10,9 +10,9 @@
int initdbus(void);
void dbus_tear_down(int id);
/* void dbus_poll(int timeout); */
void notificationClosed(notification * n, int reason);
void actionInvoked(notification * n, const char *identifier);
void notification_closed(notification * n, int reason);
void action_invoked(notification * n, const char *identifier);
#endif
/* vim: set ts=8 sw=8 tw=0: */
/* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */

View File

@ -34,6 +34,7 @@
#include "utils.h"
#include "rules.h"
#include "notification.h"
#include "menu.h"
#include "option_parser.h"
#include "settings.h"
@ -272,7 +273,7 @@ gboolean run(void *data)
return false;
}
gboolean pause_signal (gpointer data)
gboolean pause_signal(gpointer data)
{
pause_display = true;
wake_up();
@ -280,7 +281,7 @@ gboolean pause_signal (gpointer data)
return G_SOURCE_CONTINUE;
}
gboolean unpause_signal (gpointer data)
gboolean unpause_signal(gpointer data)
{
pause_display = false;
wake_up();
@ -288,7 +289,31 @@ gboolean unpause_signal (gpointer data)
return G_SOURCE_CONTINUE;
}
int main(int argc, char *argv[])
gboolean quit_signal(gpointer data)
{
g_main_loop_quit(mainloop);
return G_SOURCE_CONTINUE;
}
static void teardown_notification(gpointer data)
{
notification *n = data;
notification_free(n);
}
static void teardown(void)
{
regex_teardown();
g_queue_free_full(history, teardown_notification);
g_queue_free_full(displayed, teardown_notification);
g_queue_free_full(queue, teardown_notification);
x_free();
}
int dunst_main(int argc, char *argv[])
{
history = g_queue_new();
@ -318,10 +343,7 @@ int main(int argc, char *argv[])
x_setup();
if (settings.startup_notification) {
notification *n = malloc(sizeof(notification));
if(n == NULL) {
die("Unable to allocate memory", EXIT_FAILURE);
}
notification *n = notification_create();
n->appname = strdup("dunst");
n->summary = strdup("startup");
n->body = strdup("dunst is up and running");
@ -330,14 +352,6 @@ int main(int argc, char *argv[])
n->allow_markup = false;
n->plain_text = true;
n->urgency = LOW;
n->icon = NULL;
n->category = NULL;
n->msg = NULL;
n->dbus_client = NULL;
n->color_strings[0] = NULL;
n->color_strings[1] = NULL;
n->actions = NULL;
n->urls = NULL;
notification_init(n, 0);
}
@ -364,14 +378,30 @@ int main(int argc, char *argv[])
g_source_attach(x11_source, NULL);
g_unix_signal_add(SIGUSR1, pause_signal, NULL);
g_unix_signal_add(SIGUSR2, unpause_signal, NULL);
guint pause_src = g_unix_signal_add(SIGUSR1, pause_signal, NULL);
guint unpause_src = g_unix_signal_add(SIGUSR2, unpause_signal, NULL);
/* register SIGINT/SIGTERM handler for
* graceful termination */
guint term_src = g_unix_signal_add(SIGTERM, quit_signal, NULL);
guint int_src = g_unix_signal_add(SIGINT, quit_signal, NULL);
run(NULL);
g_main_loop_run(mainloop);
g_main_loop_unref(mainloop);
/* remove signal handler watches */
g_source_remove(pause_src);
g_source_remove(unpause_src);
g_source_remove(term_src);
g_source_remove(int_src);
g_source_destroy(x11_source);
dbus_tear_down(owner_id);
teardown();
return 0;
}
@ -392,4 +422,4 @@ void print_version(void)
exit(EXIT_SUCCESS);
}
/* vim: set ts=8 sw=8 tw=0: */
/* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */

View File

@ -3,6 +3,7 @@
#pragma once
#include <glib.h>
#include <stdio.h>
#include <stdbool.h>
#include "x.h"
@ -12,7 +13,8 @@
#define PERR(msg, errnum) printf("(%d) %s : %s\n", __LINE__, (msg), (strerror(errnum)))
#define LENGTH(X) (sizeof X / sizeof X[0])
#define ColLast 2
#define ColLast 3
#define ColFrame 2
#define ColFG 1
#define ColBG 0
@ -27,12 +29,14 @@ extern GQueue *displayed;
extern GQueue *history;
extern GSList *rules;
extern bool pause_display;
extern const char *color_strings[2][3];
extern const char *color_strings[3][3];
/* return id of notification */
gboolean run(void *data);
void wake_up(void);
int dunst_main(int argc, char *argv[]);
void check_timeouts(void);
void history_pop(void);
void history_push(notification *n);
@ -43,4 +47,4 @@ char *extract_urls(const char *str);
void context_menu(void);
void wake_up(void);
void pause_signal_handler(int sig);
/* vim: set ts=8 sw=8 tw=0: */
/* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */

View File

@ -1,6 +1,7 @@
/* copyright 2013 Sascha Kruse and contributors (see LICENSE for licensing information) */
#define _GNU_SOURCE
#include <stdlib.h>
#include <stdbool.h>
#include <regex.h>
#include <stdio.h>
@ -15,33 +16,50 @@
#include "settings.h"
#include "dbus.h"
/*
* Exctract all urls from a given string.
*
* Return: a string of urls separated by \n
*
*/
static bool is_initialized = false;
static regex_t cregex;
static int regex_init(void)
{
if (is_initialized)
return 1;
char *regex =
"\\b(https?://|ftps?://|news://|mailto:|file://|www\\.)"
"[-[:alnum:]_\\@;/?:&=%$.+!*\x27,~#]*"
"(\\([-[:alnum:]_\\@;/?:&=%$.+!*\x27,~#]*\\)|[-[:alnum:]_\\@;/?:&=%$+*~])+";
int ret = regcomp(&cregex, regex, REG_EXTENDED | REG_ICASE);
if (ret != 0) {
fputs("failed to compile regex", stderr);
return 0;
} else {
is_initialized = true;
return 1;
}
}
void regex_teardown(void)
{
if (is_initialized)
{
regfree(&cregex);
is_initialized = false;
}
}
/*
* Exctract all urls from a given string.
*
* Return: a string of urls separated by \n
*
*/
char *extract_urls(const char *to_match)
{
static bool is_initialized = false;
static regex_t cregex;
if (!is_initialized) {
char *regex =
"\\b(https?://|ftps?://|news://|mailto:|file://|www\\.)"
"[-[:alnum:]_\\@;/?:&=%$.+!*\x27,~#]*"
"(\\([-[:alnum:]_\\@;/?:&=%$.+!*\x27,~#]*\\)|[-[:alnum:]_\\@;/?:&=%$+*~])+";
int ret = regcomp(&cregex, regex, REG_EXTENDED | REG_ICASE);
if (ret != 0) {
printf("failed to compile regex\n");
return NULL;
} else {
is_initialized = true;
}
}
char *urls = NULL;
if (!regex_init())
return NULL;
const char *p = to_match;
regmatch_t m;
@ -69,10 +87,10 @@ char *extract_urls(const char *to_match)
return urls;
}
/*
* Open url in browser.
*
*/
/*
* Open url in browser.
*
*/
void open_browser(const char *url)
{
int browser_pid1 = fork();
@ -93,10 +111,10 @@ void open_browser(const char *url)
}
}
/*
* Notify the corresponding client
* that an action has been invoked
*/
/*
* Notify the corresponding client
* that an action has been invoked
*/
void invoke_action(const char *action)
{
notification *invoked = NULL;
@ -131,14 +149,14 @@ void invoke_action(const char *action)
}
if (invoked && action_identifier) {
actionInvoked(invoked, action_identifier);
action_invoked(invoked, action_identifier);
}
}
/*
* Dispatch whatever has been returned
* by the menu.
*/
/*
* Dispatch whatever has been returned
* by the menu.
*/
void dispatch_menu_result(const char *input)
{
char *in = strdup(input);
@ -164,10 +182,10 @@ void dispatch_menu_result(const char *input)
free(in);
}
/*
* Open the context menu that let's the user
* select urls/actions/etc
*/
/*
* Open the context menu that let's the user
* select urls/actions/etc
*/
void context_menu(void)
{
char *dmenu_input = NULL;
@ -239,4 +257,4 @@ void context_menu(void)
free(dmenu_input);
}
/* vim: set ts=8 sw=8 tw=0: */
/* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */

View File

@ -5,3 +5,6 @@
char *extract_urls(const char *to_match);
void open_browser(const char *url);
void invoke_action(const char *action);
void regex_teardown(void);
/* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */

View File

@ -1,7 +1,7 @@
/* copyright 2013 Sascha Kruse and contributors (see LICENSE for licensing information) */
#define _GNU_SOURCE
#include <stdlib.h>
#include <time.h>
#include <glib.h>
#include <errno.h>
@ -9,6 +9,7 @@
#include <stdbool.h>
#include <unistd.h>
#include <sys/wait.h>
#include <assert.h>
#include "dbus.h"
#include "x.h"
@ -21,10 +22,10 @@
int next_notification_id = 1;
/*
* print a human readable representation
* of the given notification to stdout.
*/
/*
* print a human readable representation
* of the given notification to stdout.
*/
void notification_print(notification * n)
{
printf("{\n");
@ -37,6 +38,7 @@ void notification_print(notification * n)
printf("\tformatted: '%s'\n", n->msg);
printf("\tfg: %s\n", n->color_strings[ColFG]);
printf("\tbg: %s\n", n->color_strings[ColBG]);
printf("\tframe: %s\n", n->color_strings[ColFrame]);
printf("\tid: %d\n", n->id);
if (n->urls) {
printf("\turls\n");
@ -59,10 +61,10 @@ void notification_print(notification * n)
printf("}\n");
}
/*
* Run the script associated with the
* given notification.
*/
/*
* Run the script associated with the
* given notification.
*/
void notification_run_script(notification * n)
{
if (!n->script || strlen(n->script) < 1)
@ -114,10 +116,10 @@ void notification_run_script(notification * n)
}
}
/*
* Helper function to compare to given
* notifications.
*/
/*
* Helper function to compare to given
* notifications.
*/
int notification_cmp(const void *va, const void *vb)
{
notification *a = (notification *) va;
@ -133,93 +135,83 @@ int notification_cmp(const void *va, const void *vb)
}
}
/*
* Wrapper for notification_cmp to match glib's
* compare functions signature.
*/
/*
* Wrapper for notification_cmp to match glib's
* compare functions signature.
*/
int notification_cmp_data(const void *va, const void *vb, void *data)
{
return notification_cmp(va, vb);
}
/*
* Free the memory used by the given notification.
*/
int notification_is_duplicate(const notification *a, const notification *b)
{
return strcmp(a->appname, b->appname) == 0
&& strcmp(a->summary, b->summary) == 0
&& strcmp(a->body, b->body) == 0
&& (settings.icon_position != icons_off ? strcmp(a->icon, b->icon) == 0 : 1)
&& a->urgency == b->urgency;
}
/*
* Free the memory used by the given notification.
*/
void notification_free(notification * n)
{
if (n == NULL)
return;
assert(n != NULL);
free(n->appname);
free(n->summary);
free(n->body);
free(n->icon);
free(n->msg);
free(n->dbus_client);
g_free(n->category);
if (n->text_to_render)
g_free(n->text_to_render);
if (n->urls)
g_free(n->urls);
if (n->actions) {
g_strfreev(n->actions->actions);
free(n->actions->dmenu_str);
}
if (n->raw_icon) {
if (n->raw_icon->data)
free(n->raw_icon->data);
free(n->raw_icon);
}
free(n);
}
/*
* Strip any markup from text
*/
/*
* Strip any markup from text
*/
char *notification_strip_markup(char *str)
{
char *replace_buf, *start, *end;
if (str == NULL) {
return NULL;
}
/* strip all tags */
string_strip_delimited(str, '<', '>');
/* unquote the remainder */
str = string_replace_all("&quot;", "\"", str);
str = string_replace_all("&apos;", "'", str);
str = string_replace_all("&amp;", "&", str);
str = string_replace_all("&lt;", "<", str);
str = string_replace_all("&gt;", ">", str);
/* remove tags */
str = string_replace_all("<b>", "", str);
str = string_replace_all("</b>", "", str);
str = string_replace_all("<br>", " ", str);
str = string_replace_all("<br/>", " ", str);
str = string_replace_all("<br />", " ", str);
str = string_replace_all("<i>", "", str);
str = string_replace_all("</i>", "", str);
str = string_replace_all("<u>", "", str);
str = string_replace_all("</u>", "", str);
str = string_replace_all("</a>", "", str);
while ((start = strstr(str, "<a href")) != NULL) {
end = strstr(start, ">");
if (end != NULL) {
replace_buf = strndup(start, end - start + 1);
str = string_replace(replace_buf, "", str);
free(replace_buf);
} else {
break;
}
}
while ((start = strstr(str, "<img src")) != NULL) {
end = strstr(start, "/>");
if (end != NULL) {
replace_buf = strndup(start, end - start + 2);
str = string_replace(replace_buf, "", str);
free(replace_buf);
} else {
break;
}
}
return str;
}
/*
* Quote a text string for rendering with pango
*/
/*
* Quote a text string for rendering with pango
*/
char *notification_quote_markup(char *str)
{
if (str == NULL) {
@ -235,10 +227,10 @@ char *notification_quote_markup(char *str)
return str;
}
/*
* Replace all occurrences of "needle" with a quoted "replacement",
* according to the allow_markup/plain_text settings.
*/
/*
* Replace all occurrences of "needle" with a quoted "replacement",
* according to the allow_markup/plain_text settings.
*/
char *notification_replace_format(const char *needle, const char *replacement,
char *haystack, bool allow_markup,
bool plain_text) {
@ -254,19 +246,25 @@ char *notification_replace_format(const char *needle, const char *replacement,
tmp = notification_quote_markup(tmp);
ret = string_replace_all(needle, tmp, haystack);
free(tmp);
} else if (!allow_markup) {
} else {
tmp = strdup(replacement);
if (!settings.ignore_newline) {
if (settings.ignore_newline) {
tmp = string_replace_all("<br>", " ", tmp);
tmp = string_replace_all("<br/>", " ", tmp);
tmp = string_replace_all("<br />", " ", tmp);
} else {
tmp = string_replace_all("<br>", "\n", tmp);
tmp = string_replace_all("<br/>", "\n", tmp);
tmp = string_replace_all("<br />", "\n", tmp);
}
tmp = notification_strip_markup(tmp);
tmp = notification_quote_markup(tmp);
if (!allow_markup) {
tmp = notification_strip_markup(tmp);
tmp = notification_quote_markup(tmp);
}
ret = string_replace_all(needle, tmp, haystack);
free(tmp);
} else {
ret = string_replace_all(needle, replacement, haystack);
}
return ret;
@ -304,21 +302,48 @@ char *notification_extract_markup_urls(char **str_ptr) {
}
free(replace_buf);
} else {
break;
break;
}
}
*str_ptr = str;
return urls;
}
/*
* Initialize the given notification and add it to
* the queue. Replace notification with id if id > 0.
*/
/*
* Create notification struct and initialise everything to NULL,
* this function is guaranteed to return a valid pointer.
*/
notification *notification_create(void)
{
notification *n = malloc(sizeof(notification));
if(n == NULL) die("Unable to allocate memory", EXIT_FAILURE);
memset(n, 0, sizeof(notification));
return n;
}
void notification_init_defaults(notification *n)
{
assert(n != NULL);
if(n->appname == NULL) n->appname = g_strdup("unknown");
if(n->summary == NULL) n->summary = g_strdup("");
if(n->body == NULL) n->body = g_strdup("");
if(n->category == NULL) n->category = g_strdup("");
}
/*
* Initialize the given notification and add it to
* the queue. Replace notification with id if id > 0.
*
* n should be a pointer to a notification allocated with
* notification_create, it is undefined behaviour to pass a notification
* allocated some other way.
*/
int notification_init(notification * n, int id)
{
if (n == NULL)
return -1;
assert(n != NULL);
//Prevent undefined behaviour by initialising required fields
notification_init_defaults(n);
if (strcmp("DUNST_COMMAND_PAUSE", n->summary) == 0) {
pause_display = true;
@ -362,7 +387,7 @@ int notification_init(notification * n, int id)
n->msg = string_replace_all("%p", "", n->msg);
}
n->msg = g_strstrip(n->msg);
n->msg = g_strchomp(n->msg);
if (id == 0) {
n->id = ++next_notification_id;
@ -378,9 +403,7 @@ int notification_init(notification * n, int id)
for (GList * iter = g_queue_peek_head_link(queue); iter;
iter = iter->next) {
notification *orig = iter->data;
if (strcmp(orig->appname, n->appname) == 0
&& strcmp(orig->summary, n->summary) == 0
&& strcmp(orig->body, n->body) == 0) {
if (notification_is_duplicate(orig, n)) {
/* If the progress differs this was probably intended to replace the notification
* but notify-send was used. So don't increment dup_count in this case
*/
@ -403,9 +426,7 @@ int notification_init(notification * n, int id)
for (GList * iter = g_queue_peek_head_link(displayed); iter;
iter = iter->next) {
notification *orig = iter->data;
if (strcmp(orig->appname, n->appname) == 0
&& strcmp(orig->summary, n->summary) == 0
&& strcmp(orig->body, n->body) == 0) {
if (notification_is_duplicate(orig, n)) {
/* notifications that differ only in progress hints should be expected equal,
* but we want the latest message, with the latest hint value
*/
@ -438,20 +459,21 @@ int notification_init(notification * n, int id)
n->color_strings[ColBG] = xctx.color_strings[ColBG][n->urgency];
}
if (!n->color_strings[ColFrame]) {
n->color_strings[ColFrame] = xctx.color_strings[ColFrame][n->urgency];
}
n->timeout =
n->timeout == -1 ? settings.timeouts[n->urgency] : n->timeout;
n->start = 0;
if (n->icon == NULL) {
n->icon = strdup(settings.icons[n->urgency]);
}
else if (strlen(n->icon) <= 0) {
if (n->icon != NULL && strlen(n->icon) <= 0) {
free(n->icon);
n->icon = strdup(settings.icons[n->urgency]);
n->icon = NULL;
}
if (n->category == NULL) {
n->category = "";
if (n->raw_icon == NULL && n->icon == NULL) {
n->icon = strdup(settings.icons[n->urgency]);
}
n->timestamp = time(NULL);
@ -505,15 +527,15 @@ int notification_init(notification * n, int id)
return n->id;
}
/*
* Close the notification that has id.
*
* reasons:
* -1 -> notification is a replacement, no NotificationClosed signal emitted
* 1 -> the notification expired
* 2 -> the notification was dismissed by the user_data
* 3 -> The notification was closed by a call to CloseNotification
*/
/*
* Close the notification that has id.
*
* reasons:
* -1 -> notification is a replacement, no NotificationClosed signal emitted
* 1 -> the notification expired
* 2 -> the notification was dismissed by the user_data
* 3 -> The notification was closed by a call to CloseNotification
*/
int notification_close_by_id(int id, int reason)
{
notification *target = NULL;
@ -541,20 +563,19 @@ int notification_close_by_id(int id, int reason)
}
if (reason > 0 && reason < 4 && target != NULL) {
notificationClosed(target, reason);
notification_closed(target, reason);
}
wake_up();
return reason;
}
/*
* Close the given notification. SEE notification_close_by_id.
*/
/*
* Close the given notification. SEE notification_close_by_id.
*/
int notification_close(notification * n, int reason)
{
if (n == NULL)
return -1;
assert(n != NULL);
return notification_close_by_id(n->id, reason);
}
@ -567,11 +588,11 @@ void notification_update_text_to_render(notification *n)
char *buf = NULL;
char *msg = g_strstrip(n->msg);
char *msg = g_strchomp(n->msg);
/* print dup_count and msg */
if (n->dup_count > 0 && (n->actions || n->urls)
&& settings.show_indicators) {
if ((n->dup_count > 0 && !settings.hide_duplicates_count)
&& (n->actions || n->urls) && settings.show_indicators) {
buf = g_strdup_printf("(%d%s%s) %s",
n->dup_count,
n->actions ? "A" : "",
@ -580,7 +601,7 @@ void notification_update_text_to_render(notification *n)
buf = g_strdup_printf("(%s%s) %s",
n->actions ? "A" : "",
n->urls ? "U" : "", msg);
} else if (n->dup_count > 0) {
} else if (n->dup_count > 0 && !settings.hide_duplicates_count) {
buf = g_strdup_printf("(%d) %s", n->dup_count, msg);
} else {
buf = g_strdup(msg);
@ -627,4 +648,4 @@ int notification_get_ttl(notification *n) {
int notification_get_age(notification *n) {
return time(NULL) - n->timestamp;
}
/* vim: set ts=8 sw=8 tw=0: */
/* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */

View File

@ -7,6 +7,16 @@
#define NORM 1
#define CRIT 2
typedef struct _raw_image {
int width;
int height;
int rowstride;
int has_alpha;
int bits_per_sample;
int n_channels;
unsigned char *data;
} RawImage;
typedef struct _actions {
char **actions;
char *dmenu_str;
@ -18,6 +28,7 @@ typedef struct _notification {
char *summary;
char *body;
char *icon;
RawImage *raw_icon;
char *msg; /* formatted message */
char *category;
char *text_to_render;
@ -33,7 +44,7 @@ typedef struct _notification {
int id;
int dup_count;
int displayed_height;
const char *color_strings[2];
const char *color_strings[3];
bool first_render;
int progress; /* percentage + 1, 0 to hide */
@ -43,11 +54,13 @@ typedef struct _notification {
Actions *actions;
} notification;
notification *notification_create(void);
int notification_init(notification * n, int id);
void notification_free(notification * n);
int notification_close_by_id(int id, int reason);
int notification_cmp(const void *a, const void *b);
int notification_cmp_data(const void *a, const void *b, void *data);
int notification_is_duplicate(const notification *a, const notification *b);
void notification_run_script(notification * n);
int notification_close(notification * n, int reason);
void notification_print(notification * n);
@ -56,3 +69,5 @@ char *notification_quote_markup(char *str);
void notification_update_text_to_render(notification *n);
int notification_get_ttl(notification *n);
int notification_get_age(notification *n);
/* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */

View File

@ -175,7 +175,7 @@ int ini_get_bool(char *section, char *key, int def)
case '0':
return false;
default:
return false;
return def;
}
}
}
@ -214,7 +214,7 @@ int load_ini_file(FILE * fp)
continue;
if (*start == '[') {
char *end = strstr(start + 1, "]");
char *end = strchr(start + 1, ']');
if (!end) {
printf
("Warning: invalid config file at line %d\n",
@ -232,7 +232,7 @@ int load_ini_file(FILE * fp)
continue;
}
char *equal = strstr(start + 1, "=");
char *equal = strchr(start + 1, '=');
if (!equal) {
printf("Warning: invalid config file at line %d\n",
line_num);
@ -244,9 +244,9 @@ int load_ini_file(FILE * fp)
char *key = g_strstrip(start);
char *value = g_strstrip(equal + 1);
char *quote = strstr(value, "\"");
char *quote = strchr(value, '"');
if (quote) {
char *closing_quote = strstr(quote + 1, "\"");
char *closing_quote = strchr(quote + 1, '"');
if (!closing_quote) {
printf
("Warning: invalid config file at line %d\n",
@ -257,9 +257,7 @@ int load_ini_file(FILE * fp)
closing_quote = '\0';
} else {
char *comment = strstr(value, "#");
if (!comment)
comment = strstr(value, ";");
char *comment = strpbrk(value, "#;");
if (comment)
comment = '\0';
}
@ -291,7 +289,7 @@ int cmdline_find_option(char *key)
return -1;
}
char *key1 = g_strdup(key);
char *key2 = strstr(key1, "/");
char *key2 = strchr(key1, '/');
if (key2) {
*key2 = '\0';
@ -421,7 +419,7 @@ double option_get_double(char *ini_section, char *ini_key, char *cmdline_key,
double val = cmdline_get_double(cmdline_key, def, description);
if (!str)
return ini_get_int(ini_section, ini_key, def);
return ini_get_double(ini_section, ini_key, def);
else
return val;
}
@ -473,4 +471,4 @@ char *cmdline_create_usage(void)
return g_strdup(usage_str);
}
/* vim: set ts=8 sw=8 tw=0: */
/* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */

View File

@ -33,4 +33,4 @@ int option_get_bool(char *ini_section, char *ini_key, char *cmdline_key,
*/
char *next_section(char *section);
/* vim: set ts=8 sw=8 tw=0: */
/* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */

View File

@ -78,4 +78,4 @@ bool rule_matches_notification(rule_t * r, notification * n)
&& (!r->category || !fnmatch(r->category, n->category, 0))
&& (r->msg_urgency == -1 || r->msg_urgency == n->urgency));
}
/* vim: set ts=8 sw=8 tw=0: */
/* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */

View File

@ -34,3 +34,5 @@ void rule_init(rule_t * r);
void rule_apply(rule_t * r, notification * n);
void rule_apply_all(notification * n);
bool rule_matches_notification(rule_t * r, notification * n);
/* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */

View File

@ -1,6 +1,8 @@
/* copyright 2013 Sascha Kruse and contributors (see LICENSE for licensing information) */
#include <glib.h>
#include <stdlib.h>
#include <string.h>
#ifndef STATIC_CONFIG
#include <basedir.h>
@ -169,6 +171,10 @@ void load_settings(char *cmdline_config_path)
option_get_int("global", "show_age_threshold",
"-show_age_threshold", show_age_threshold,
"When should the age of the notification be displayed?");
settings.hide_duplicates_count =
option_get_bool("global", "hide_duplicates_count",
"-hide_duplicates_count", false,
"Hide count of the merged notifications with the same content");
settings.sticky_history =
option_get_bool("global", "sticky_history", "-sticky_history",
sticky_history,
@ -179,8 +185,8 @@ void load_settings(char *cmdline_config_path)
"Max amount of notifications kept in history");
settings.show_indicators =
option_get_bool("global", "show_indicators", "-show_indicators",
show_indicators,
"Show indicators for actions \"(A)\" and URLs \"(U)\"");
show_indicators,
"Show indicators for actions \"(A)\" and URLs \"(U)\"");
settings.separator_height =
option_get_int("global", "separator_height",
"-sep_height/-separator_height", separator_height,
@ -249,6 +255,10 @@ void load_settings(char *cmdline_config_path)
}
}
settings.max_icon_size =
option_get_int("global", "max_icon_size", "-max_icon_size", max_icon_size,
"Scale larger icons down to this size, set to 0 to disable");
settings.icon_folders =
option_get_string("global", "icon_folders", "-icon_folders", icon_folders,
"paths to default icons");
@ -267,6 +277,9 @@ void load_settings(char *cmdline_config_path)
settings.lowfgcolor =
option_get_string("urgency_low", "foreground", "-lf", lowfgcolor,
"Foreground color for notifications with low urgency");
settings.lowframecolor =
option_get_string("urgency_low", "frame_color", "-lfr", NULL,
"Frame color for notifications with low urgency");
settings.timeouts[LOW] =
option_get_int("urgency_low", "timeout", "-lto", timeouts[LOW],
"Timeout for notifications with low urgency");
@ -281,6 +294,9 @@ void load_settings(char *cmdline_config_path)
option_get_string("urgency_normal", "foreground", "-nf",
normfgcolor,
"Foreground color for notifications with normal urgency");
settings.normframecolor =
option_get_string("urgency_normal", "frame_color", "-nfr", NULL,
"Frame color for notifications with normal urgency");
settings.timeouts[NORM] =
option_get_int("urgency_normal", "timeout", "-nto", timeouts[NORM],
"Timeout for notifications with normal urgency");
@ -295,6 +311,9 @@ void load_settings(char *cmdline_config_path)
option_get_string("urgency_critical", "foreground", "-cf",
critfgcolor,
"Foreground color for notifications with ciritical urgency");
settings.critframecolor =
option_get_string("urgency_critical", "frame_color", "-cfr", NULL,
"Frame color for notifications with critical urgency");
settings.timeouts[CRIT] =
option_get_int("urgency_critical", "timeout", "-cto", timeouts[CRIT],
"Timeout for notifications with critical urgency");
@ -363,6 +382,7 @@ void load_settings(char *cmdline_config_path)
r->summary = ini_get_string(cur_section, "summary", r->summary);
r->body = ini_get_string(cur_section, "body", r->body);
r->icon = ini_get_string(cur_section, "icon", r->icon);
r->category = ini_get_string(cur_section, "category", r->category);
r->timeout = ini_get_int(cur_section, "timeout", r->timeout);
r->allow_markup = ini_get_bool(cur_section, "allow_markup", r->allow_markup);
r->plain_text = ini_get_bool(cur_section, "plain_text", r->plain_text);
@ -372,7 +392,7 @@ void load_settings(char *cmdline_config_path)
r->bg = ini_get_string(cur_section, "background", r->bg);
r->format = ini_get_string(cur_section, "format", r->format);
r->new_icon = ini_get_string(cur_section, "new_icon", r->new_icon);
r->script = ini_get_string(cur_section, "script", NULL);
r->script = ini_get_string(cur_section, "script", NULL);
}
#ifndef STATIC_CONFIG
@ -383,4 +403,4 @@ void load_settings(char *cmdline_config_path)
}
#endif
}
/* vim: set ts=8 sw=8 tw=0: */
/* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */

View File

@ -6,13 +6,17 @@ typedef struct _settings {
bool allow_markup;
bool plain_text;
bool stack_duplicates;
bool hide_duplicates_count;
char *font;
char *normbgcolor;
char *normfgcolor;
char *normframecolor;
char *critbgcolor;
char *critfgcolor;
char *critframecolor;
char *lowbgcolor;
char *lowfgcolor;
char *lowframecolor;
char *format;
int timeouts[3];
char *icons[3];
@ -29,7 +33,7 @@ typedef struct _settings {
float bounce_freq;
int sticky_history;
int history_length;
int show_indicators;
int show_indicators;
int verbosity;
int word_wrap;
int ignore_newline;
@ -49,6 +53,7 @@ typedef struct _settings {
char **dmenu_cmd;
char *browser;
enum icon_position_t icon_position;
int max_icon_size;
char *icon_folders;
enum follow_mode f_mode;
bool always_run_script;
@ -61,3 +66,5 @@ typedef struct _settings {
extern settings_t settings;
void load_settings(char *cmdline_config_path);
/* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */

View File

@ -11,10 +11,10 @@
#include "dunst.h"
char *string_replace_char(char needle, char replacement, char *haystack) {
char *current = haystack;
while ((current = strchr (current, needle)) != NULL)
*current++ = replacement;
return haystack;
char *current = haystack;
while ((current = strchr (current, needle)) != NULL)
*current++ = replacement;
return haystack;
}
char *string_replace_at(char *buf, int pos, int len, const char *repl)
@ -25,13 +25,21 @@ char *string_replace_at(char *buf, int pos, int len, const char *repl)
buf_len = strlen(buf);
repl_len = strlen(repl);
size = (buf_len - len) + repl_len + 1;
tmp = malloc(size);
if (repl_len <= len) {
tmp = buf;
} else {
tmp = malloc(size);
}
memcpy(tmp, buf, pos);
memcpy(tmp + pos, repl, repl_len);
memcpy(tmp + pos + repl_len, buf + pos + len, buf_len - (pos + len) + 1);
memmove(tmp + pos + repl_len, buf + pos + len, buf_len - (pos + len) + 1);
if(tmp != buf) {
free(buf);
}
free(buf);
return tmp;
}
@ -105,17 +113,19 @@ char **string_to_argv(const char *s)
return argv;
}
int digit_count(int i)
void string_strip_delimited(char *str, char a, char b)
{
i = ABS(i);
int len = 1;
while (i > 0) {
len++;
i /= 10;
int iread=-1, iwrite=0, copen=0;
while (str[++iread] != 0) {
if (str[iread] == a) {
++copen;
} else if (str[iread] == b && copen > 0) {
--copen;
} else if (copen == 0) {
str[iwrite++] = str[iread];
}
}
return len;
str[iwrite] = 0;
}
void die(char *text, int exit_value)
@ -124,4 +134,4 @@ void die(char *text, int exit_value)
exit(exit_value);
}
/* vim: set ts=8 sw=8 tw=0: */
/* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */

View File

@ -17,10 +17,11 @@ char *string_append(char *a, const char *b, const char *sep);
char **string_to_argv(const char *s);
/* strip content between two delimiter characters (inplace) */
void string_strip_delimited(char *str, char a, char b);
/* exit with an error message */
void die(char *msg, int exit_value);
int digit_count(int i);
#endif
/* vim: set ts=8 sw=8 tw=0: */
/* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */

View File

@ -14,6 +14,7 @@
#include <X11/Xatom.h>
#include <pango/pangocairo.h>
#include <cairo-xlib.h>
#include <gdk/gdk.h>
#include "x.h"
#include "utils.h"
@ -39,15 +40,15 @@ typedef struct _colored_layout {
PangoLayout *l;
color_t fg;
color_t bg;
color_t frame;
char *text;
PangoAttrList *attr;
cairo_surface_t *icon;
notification *n;
} colored_layout;
cairo_ctx_t cairo_ctx;
static color_t frame_color;
/* FIXME refactor setup teardown handlers into one setup and one teardown */
static void x_follow_setup_error_handler(void);
static int x_follow_tear_down_error_handler(void);
@ -59,8 +60,6 @@ static void x_handle_click(XEvent ev);
static void x_screen_info(screen_info * scr);
static void x_win_setup(void);
static color_t x_color_hex_to_double(int hexValue)
{
color_t color;
@ -68,7 +67,7 @@ static color_t x_color_hex_to_double(int hexValue)
color.g = ((hexValue >> 8) & 0xFF) / 255.0;
color.b = ((hexValue) & 0xFF) / 255.0;
return color;
return color;
}
static color_t x_string_to_color_t(const char *str)
@ -111,20 +110,23 @@ static color_t calculate_foreground_color(color_t bg)
}
static color_t x_get_separator_color(color_t fg, color_t bg)
static color_t x_get_separator_color(colored_layout *cl, colored_layout *cl_next)
{
switch (settings.sep_color) {
case FRAME:
return x_string_to_color_t(settings.frame_color);
if (cl_next->n->urgency > cl->n->urgency)
return cl_next->frame;
else
return cl->frame;
case CUSTOM:
return x_string_to_color_t(settings.sep_custom_color_str);
case FOREGROUND:
return fg;
return cl->fg;
case AUTO:
return calculate_foreground_color(bg);
return calculate_foreground_color(cl->bg);
default:
printf("Unknown separator color type. Please file a Bugreport.\n");
return fg;
return cl->fg;
}
}
@ -137,8 +139,6 @@ static void x_cairo_setup(void)
cairo_ctx.context = cairo_create(cairo_ctx.surface);
cairo_ctx.desc = pango_font_description_from_string(settings.font);
frame_color = x_string_to_color_t(settings.frame_color);
}
static void r_setup_pango_layout(PangoLayout *layout, int width)
@ -191,6 +191,17 @@ static bool have_dynamic_width(void)
return (xctx.geometry.mask & WidthValue && xctx.geometry.w == 0);
}
static bool is_readable_file(const char *filename)
{
return (access(filename, R_OK) != -1);
}
const char *get_filename_ext(const char *filename) {
const char *dot = strrchr(filename, '.');
if(!dot || dot == filename) return "";
return dot + 1;
}
static dimension_t calculate_dimensions(GSList *layouts)
{
dimension_t dim;
@ -217,6 +228,7 @@ static dimension_t calculate_dimensions(GSList *layouts)
dim.w = scr.dim.w;
}
dim.h += 2 * settings.frame_width;
dim.h += (g_slist_length(layouts) - 1) * settings.separator_height;
int text_width = 0, total_width = 0;
@ -274,11 +286,41 @@ static dimension_t calculate_dimensions(GSList *layouts)
return dim;
}
static cairo_surface_t *get_icon_surface(char *icon_path)
static cairo_surface_t *gdk_pixbuf_to_cairo_surface(const GdkPixbuf *pixbuf)
{
cairo_surface_t *icon_surface = NULL;
cairo_t *cr;
cairo_format_t format;
double width, height;
format = gdk_pixbuf_get_has_alpha(pixbuf) ? CAIRO_FORMAT_ARGB32 : CAIRO_FORMAT_RGB24;
width = gdk_pixbuf_get_width(pixbuf);
height = gdk_pixbuf_get_height(pixbuf);
icon_surface = cairo_image_surface_create(format, width, height);
cr = cairo_create(icon_surface);
gdk_cairo_set_source_pixbuf(cr, pixbuf, 0, 0);
cairo_paint(cr);
cairo_destroy(cr);
return icon_surface;
}
static GdkPixbuf *get_pixbuf_from_file(const char *icon_path)
{
GdkPixbuf *pixbuf = NULL;
if (is_readable_file(icon_path)) {
GError *error = NULL;
pixbuf = gdk_pixbuf_new_from_file(icon_path, &error);
if (pixbuf == NULL)
g_free(error);
}
return pixbuf;
}
static GdkPixbuf *get_pixbuf_from_path(char *icon_path)
{
GdkPixbuf *pixbuf = NULL;
gchar *uri_path = NULL;
if (strlen(icon_path) > 0 && settings.icon_position != icons_off) {
if (strlen(icon_path) > 0) {
if (g_str_has_prefix(icon_path, "file://")) {
uri_path = g_filename_from_uri(icon_path, NULL, NULL);
if (uri_path != NULL) {
@ -287,14 +329,10 @@ static cairo_surface_t *get_icon_surface(char *icon_path)
}
/* absolute path? */
if (icon_path[0] == '/' || icon_path[0] == '~') {
icon_surface = cairo_image_surface_create_from_png(icon_path);
if (cairo_surface_status(icon_surface) != CAIRO_STATUS_SUCCESS) {
cairo_surface_destroy(icon_surface);
icon_surface = NULL;
}
pixbuf = get_pixbuf_from_file(icon_path);
}
/* search in icon_folders */
if (icon_surface == NULL) {
if (pixbuf == NULL) {
char *start = settings.icon_folders,
*end, *current_folder, *maybe_icon_path;
do {
@ -305,19 +343,16 @@ static cairo_surface_t *get_icon_surface(char *icon_path)
maybe_icon_path = g_strconcat(current_folder, "/", icon_path, ".png", NULL);
free(current_folder);
icon_surface = cairo_image_surface_create_from_png(maybe_icon_path);
pixbuf = get_pixbuf_from_file(maybe_icon_path);
free(maybe_icon_path);
if (cairo_surface_status(icon_surface) == CAIRO_STATUS_SUCCESS) {
return icon_surface;
} else {
cairo_surface_destroy(icon_surface);
icon_surface = NULL;
if (pixbuf != NULL) {
return pixbuf;
}
start = end + 1;
} while (*(end) != '\0');
}
if (icon_surface == NULL) {
if (pixbuf == NULL) {
fprintf(stderr,
"Could not load icon: '%s'\n", icon_path);
}
@ -325,7 +360,24 @@ static cairo_surface_t *get_icon_surface(char *icon_path)
g_free(uri_path);
}
}
return icon_surface;
return pixbuf;
}
static GdkPixbuf *get_pixbuf_from_raw_image(const RawImage *raw_image)
{
GdkPixbuf *pixbuf = NULL;
pixbuf = gdk_pixbuf_new_from_data(raw_image->data,
GDK_COLORSPACE_RGB,
raw_image->has_alpha,
raw_image->bits_per_sample,
raw_image->width,
raw_image->height,
raw_image->rowstride,
NULL,
NULL);
return pixbuf;
}
static colored_layout *r_init_shared(cairo_t *c, notification *n)
@ -340,10 +392,51 @@ static colored_layout *r_init_shared(cairo_t *c, notification *n)
pango_layout_set_ellipsize(cl->l, PANGO_ELLIPSIZE_MIDDLE);
}
cl->icon = get_icon_surface(n->icon);
GdkPixbuf *pixbuf = NULL;
if (n->raw_icon && settings.icon_position != icons_off) {
pixbuf = get_pixbuf_from_raw_image(n->raw_icon);
} else if (n->icon && settings.icon_position != icons_off) {
pixbuf = get_pixbuf_from_path(n->icon);
}
if (pixbuf != NULL) {
int w = gdk_pixbuf_get_width(pixbuf);
int h = gdk_pixbuf_get_height(pixbuf);
int larger = w > h ? w : h;
if (settings.max_icon_size && larger > settings.max_icon_size) {
GdkPixbuf *scaled;
if (w >= h) {
scaled = gdk_pixbuf_scale_simple(pixbuf,
settings.max_icon_size,
(int) ((double) settings.max_icon_size / w * h),
GDK_INTERP_BILINEAR);
} else {
scaled = gdk_pixbuf_scale_simple(pixbuf,
(int) ((double) settings.max_icon_size / h * w),
settings.max_icon_size,
GDK_INTERP_BILINEAR);
}
g_object_unref(pixbuf);
pixbuf = scaled;
}
cl->icon = gdk_pixbuf_to_cairo_surface(pixbuf);
g_object_unref(pixbuf);
} else {
cl->icon = NULL;
}
if (cl->icon && cairo_surface_status(cl->icon) != CAIRO_STATUS_SUCCESS) {
cairo_surface_destroy(cl->icon);
cl->icon = NULL;
}
cl->fg = x_string_to_color_t(n->color_strings[ColFG]);
cl->bg = x_string_to_color_t(n->color_strings[ColBG]);
cl->frame = x_string_to_color_t(n->color_strings[ColFrame]);
cl->n = n;
dimension_t dim = calculate_dimensions(NULL);
int width = dim.w;
@ -427,10 +520,10 @@ static GSList *r_create_layouts(cairo_t *c)
r_create_layout_from_notification(c, n));
}
if (xmore_is_needed && xctx.geometry.h != 1) {
/* append xmore message as new message */
layouts = g_slist_append(layouts,
r_create_layout_for_xmore(c, last, qlen));
if (xmore_is_needed && xctx.geometry.h != 1) {
/* append xmore message as new message */
layouts = g_slist_append(layouts,
r_create_layout_for_xmore(c, last, qlen));
}
return layouts;
@ -441,11 +534,15 @@ static void r_free_layouts(GSList *layouts)
g_slist_free_full(layouts, free_colored_layout);
}
static dimension_t x_render_layout(cairo_t *c, colored_layout *cl, dimension_t dim, bool first, bool last)
static dimension_t x_render_layout(cairo_t *c, colored_layout *cl, colored_layout *cl_next, dimension_t dim, bool first, bool last)
{
int h;
int h_text = 0;
pango_layout_get_pixel_size(cl->l, NULL, &h);
if (cl->icon) h = MAX(cairo_image_surface_get_height(cl->icon), h);
if (cl->icon) {
h_text = h;
h = MAX(cairo_image_surface_get_height(cl->icon), h);
}
int bg_x = 0;
int bg_y = dim.y;
@ -454,11 +551,21 @@ static dimension_t x_render_layout(cairo_t *c, colored_layout *cl, dimension_t d
double bg_half_height = settings.notification_height/2.0;
int pango_offset = (int) floor(h/2.0);
if (first) bg_height += settings.frame_width;
if (last) bg_height += settings.frame_width;
else bg_height += settings.separator_height;
cairo_set_source_rgb(c, cl->frame.r, cl->frame.g, cl->frame.b);
cairo_rectangle(c, bg_x, bg_y, bg_width, bg_height);
cairo_fill(c);
/* adding frame */
bg_x += settings.frame_width;
if (first) {
dim.y += settings.frame_width;
bg_y += settings.frame_width;
bg_height -= settings.frame_width;
if (!last) bg_height -= settings.separator_height;
}
bg_width -= 2 * settings.frame_width;
if (last)
@ -473,9 +580,15 @@ static dimension_t x_render_layout(cairo_t *c, colored_layout *cl, dimension_t d
dim.y += settings.padding;
else
dim.y += (int) (ceil(bg_half_height) - pango_offset);
if (cl->icon && settings.icon_position == icons_left)
cairo_move_to(c, cairo_image_surface_get_width(cl->icon) + 2 * settings.h_padding, dim.y);
else cairo_move_to(c, settings.h_padding, dim.y);
if (cl->icon && settings.icon_position == icons_left) {
cairo_move_to(c, settings.frame_width + cairo_image_surface_get_width(cl->icon) + 2 * settings.h_padding, bg_y + settings.padding + h/2 - h_text/2);
} else if (cl->icon && settings.icon_position == icons_right) {
cairo_move_to(c, settings.frame_width + settings.h_padding, bg_y + settings.padding + h/2 - h_text/2);
} else {
cairo_move_to(c, settings.frame_width + settings.h_padding, bg_y + settings.padding);
}
cairo_set_source_rgb(c, cl->fg.r, cl->fg.g, cl->fg.b);
pango_cairo_update_layout(c, cl->l);
pango_cairo_show_layout(c, cl->l);
@ -484,13 +597,16 @@ static dimension_t x_render_layout(cairo_t *c, colored_layout *cl, dimension_t d
else
dim.y += (int) (floor(bg_half_height) + pango_offset);
color_t sep_color = x_get_separator_color(cl->fg, cl->bg);
if (settings.separator_height > 0 && !last) {
color_t sep_color = x_get_separator_color(cl, cl_next);
cairo_set_source_rgb(c, sep_color.r, sep_color.g, sep_color.b);
cairo_rectangle(c, settings.frame_width, dim.y,
dim.w - 2 * settings.frame_width
, settings.separator_height);
if (settings.sep_color == FRAME)
// Draw over the borders on both sides to avoid
// the wrong color in the corners.
cairo_rectangle(c, 0, dim.y, dim.w, settings.separator_height);
else
cairo_rectangle(c, settings.frame_width, dim.y + settings.frame_width, dim.w - 2 * settings.frame_width, settings.separator_height);
cairo_fill(c);
dim.y += settings.separator_height;
@ -503,8 +619,8 @@ static dimension_t x_render_layout(cairo_t *c, colored_layout *cl, dimension_t d
image_x,
image_y = bg_y + settings.padding;
if (settings.icon_position == icons_left) image_x = settings.h_padding;
else image_x = bg_width - settings.h_padding - image_width;
if (settings.icon_position == icons_left) image_x = settings.frame_width + settings.h_padding;
else image_x = bg_width - settings.h_padding - image_width + settings.frame_width;
cairo_set_source_surface (c, cl->icon, image_x, image_y);
cairo_rectangle (c, image_x, image_y, image_width, image_height);
@ -523,7 +639,7 @@ void x_win_draw(void)
int width = dim.w;
int height = dim.h;
if ((have_dynamic_width() || settings.shrink) && settings.align != left) {
if ((have_dynamic_width() || settings.shrink) && settings.align != left) {
r_update_layouts_width(layouts, width);
}
@ -534,16 +650,15 @@ void x_win_draw(void)
x_win_move(width, height);
cairo_xlib_surface_set_size(cairo_ctx.surface, width, height);
cairo_set_source_rgb(c, frame_color.r, frame_color.g, frame_color.b);
cairo_rectangle(c, 0.0, 0.0, width, height);
cairo_fill(c);
cairo_move_to(c, 0, 0);
bool first = true;
for (GSList *iter = layouts; iter; iter = iter->next) {
colored_layout *cl = iter->data;
dim = x_render_layout(c, cl, dim, first, iter->next == NULL);
if (iter->next)
dim = x_render_layout(c, iter->data, iter->next->data, dim, first, iter->next == NULL);
else
dim = x_render_layout(c, iter->data, NULL, dim, first, iter->next == NULL);
first = false;
}
@ -591,7 +706,6 @@ static void x_win_move(int width, int height)
xctx.window_dim.w = width;
}
static void setopacity(Window win, unsigned long opacity)
{
Atom _NET_WM_WINDOW_OPACITY =
@ -600,15 +714,9 @@ static void setopacity(Window win, unsigned long opacity)
PropModeReplace, (unsigned char *)&opacity, 1L);
}
/*
* Returns the modifier which is NumLock.
*/
/*
* Returns the modifier which is NumLock.
*/
static KeySym x_numlock_mod()
{
static KeyCode nl = 0;
@ -659,10 +767,10 @@ end:
return sym;
}
/*
* Helper function to use glib's mainloop mechanic
* with Xlib
*/
/*
* Helper function to use glib's mainloop mechanic
* with Xlib
*/
gboolean x_mainloop_fd_prepare(GSource * source, gint * timeout)
{
if (timeout)
@ -672,18 +780,18 @@ gboolean x_mainloop_fd_prepare(GSource * source, gint * timeout)
return false;
}
/*
* Helper function to use glib's mainloop mechanic
* with Xlib
*/
/*
* Helper function to use glib's mainloop mechanic
* with Xlib
*/
gboolean x_mainloop_fd_check(GSource * source)
{
return XPending(xctx.dpy) > 0;
}
/*
* Main Dispatcher for XEvents
*/
/*
* Main Dispatcher for XEvents
*/
gboolean x_mainloop_fd_dispatch(GSource * source, GSourceFunc callback,
gpointer user_data)
{
@ -747,9 +855,9 @@ gboolean x_mainloop_fd_dispatch(GSource * source, GSourceFunc callback,
return true;
}
/*
* Check whether the user is currently idle.
*/
/*
* Check whether the user is currently idle.
*/
bool x_is_idle(void)
{
XScreenSaverQueryInfo(xctx.dpy, DefaultRootWindow(xctx.dpy),
@ -761,9 +869,9 @@ bool x_is_idle(void)
}
/* TODO move to x_mainloop_* */
/*
* Handle incoming mouse click events
*/
/*
* Handle incoming mouse click events
*/
static void x_handle_click(XEvent ev)
{
if (ev.xbutton.button == Button3) {
@ -791,10 +899,10 @@ static void x_handle_click(XEvent ev)
}
}
/*
* Return the window that currently has
* the keyboard focus.
*/
/*
* Return the window that currently has
* the keyboard focus.
*/
static Window get_focused_window(void)
{
Window focused = 0;
@ -818,10 +926,10 @@ static Window get_focused_window(void)
}
#ifdef XINERAMA
/*
* Select the screen on which the Window
* should be displayed.
*/
/*
* Select the screen on which the Window
* should be displayed.
*/
static int select_screen(XineramaScreenInfo * info, int info_len)
{
int ret = 0;
@ -884,10 +992,10 @@ sc_cleanup:
}
#endif
/*
* Update the information about the monitor
* geometry.
*/
/*
* Update the information about the monitor
* geometry.
*/
static void x_screen_info(screen_info * scr)
{
#ifdef XINERAMA
@ -921,9 +1029,18 @@ static void x_screen_info(screen_info * scr)
}
}
/*
* Setup X11 stuff
*/
void x_free(void)
{
cairo_surface_destroy(cairo_ctx.surface);
cairo_destroy(cairo_ctx.context);
if (xctx.dpy)
XCloseDisplay(xctx.dpy);
}
/*
* Setup X11 stuff
*/
void x_setup(void)
{
@ -956,6 +1073,19 @@ void x_setup(void)
xctx.color_strings[ColBG][NORM] = settings.normbgcolor;
xctx.color_strings[ColBG][CRIT] = settings.critbgcolor;
if (settings.lowframecolor)
xctx.color_strings[ColFrame][LOW] = settings.lowframecolor;
else
xctx.color_strings[ColFrame][LOW] = settings.frame_color;
if (settings.normframecolor)
xctx.color_strings[ColFrame][NORM] = settings.normframecolor;
else
xctx.color_strings[ColFrame][NORM] = settings.frame_color;
if (settings.critframecolor)
xctx.color_strings[ColFrame][CRIT] = settings.critframecolor;
else
xctx.color_strings[ColFrame][CRIT] = settings.frame_color;
/* parse and set xctx.geometry and monitor position */
if (settings.geom[0] == '-') {
xctx.geometry.negative_width = true;
@ -976,7 +1106,6 @@ void x_setup(void)
}
static void x_set_wm(Window win)
{
@ -1018,9 +1147,9 @@ static void x_set_wm(Window win)
PropModeReplace, (unsigned char *) data, 1L);
}
/*
* Setup the window
*/
/*
* Setup the window
*/
static void x_win_setup(void)
{
@ -1064,9 +1193,9 @@ static void x_win_setup(void)
}
}
/*
* Show the window and grab shortcuts.
*/
/*
* Show the window and grab shortcuts.
*/
void x_win_show(void)
{
/* window is already mapped or there's nothing to show */
@ -1089,9 +1218,9 @@ void x_win_show(void)
xctx.visible = true;
}
/*
* Hide the window and ungrab unused keyboard_shortcuts
*/
/*
* Hide the window and ungrab unused keyboard_shortcuts
*/
void x_win_hide()
{
x_shortcut_ungrab(&settings.close_ks);
@ -1104,9 +1233,9 @@ void x_win_hide()
xctx.visible = false;
}
/*
* Parse a string into a modifier mask.
*/
/*
* Parse a string into a modifier mask.
*/
KeySym x_shortcut_string_to_mask(const char *str)
{
if (!strcmp(str, "ctrl")) {
@ -1128,9 +1257,9 @@ KeySym x_shortcut_string_to_mask(const char *str)
}
/*
* Error handler for grabbing mouse and keyboard errors.
*/
/*
* Error handler for grabbing mouse and keyboard errors.
*/
static int GrabXErrorHandler(Display * display, XErrorEvent * e)
{
dunst_grab_errored = true;
@ -1157,9 +1286,9 @@ static int FollowXErrorHandler(Display * display, XErrorEvent * e)
return 0;
}
/*
* Setup the Error handler.
*/
/*
* Setup the Error handler.
*/
static void x_shortcut_setup_error_handler(void)
{
dunst_grab_errored = false;
@ -1176,9 +1305,9 @@ static void x_follow_setup_error_handler(void)
XSetErrorHandler(FollowXErrorHandler);
}
/*
* Tear down the Error handler.
*/
/*
* Tear down the Error handler.
*/
static int x_shortcut_tear_down_error_handler(void)
{
XFlush(xctx.dpy);
@ -1195,9 +1324,9 @@ static int x_follow_tear_down_error_handler(void)
return dunst_follow_errored;
}
/*
* Grab the given keyboard shortcut.
*/
/*
* Grab the given keyboard shortcut.
*/
int x_shortcut_grab(keyboard_shortcut * ks)
{
if (!ks->is_valid)
@ -1222,9 +1351,9 @@ int x_shortcut_grab(keyboard_shortcut * ks)
return 0;
}
/*
* Ungrab the given keyboard shortcut.
*/
/*
* Ungrab the given keyboard shortcut.
*/
void x_shortcut_ungrab(keyboard_shortcut * ks)
{
Window root;
@ -1235,9 +1364,9 @@ void x_shortcut_ungrab(keyboard_shortcut * ks)
}
}
/*
* Initialize the keyboard shortcut.
*/
/*
* Initialize the keyboard shortcut.
*/
void x_shortcut_init(keyboard_shortcut * ks)
{
if (ks == NULL || ks->str == NULL)
@ -1254,7 +1383,7 @@ void x_shortcut_init(keyboard_shortcut * ks)
if (str == NULL)
die("Unable to allocate memory", EXIT_FAILURE);
while (strstr(str, "+")) {
while (strchr(str, '+')) {
char *mod = str;
while (*str != '+')
str++;
@ -1291,4 +1420,4 @@ void x_shortcut_init(keyboard_shortcut * ks)
free(str_begin);
}
/* vim: set ts=8 sw=8 tw=0: */
/* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */

View File

@ -13,8 +13,6 @@
#endif
#include <X11/extensions/scrnsaver.h>
#include <X11/Xft/Xft.h>
#define BUTTONMASK (ButtonPressMask|ButtonReleaseMask)
#define FONT_HEIGHT_BORDER 2
#define DEFFONT "Monospace-11"
@ -48,10 +46,9 @@ typedef struct _xctx {
Window win;
bool visible;
dimension_t geometry;
const char *color_strings[2][3];
const char *color_strings[3][3];
XScreenSaverInfo *screensaver_info;
dimension_t window_dim;
unsigned long framec;
unsigned long sep_custom_col;
} xctx_t;
@ -77,6 +74,7 @@ KeySym x_shortcut_string_to_mask(const char *str);
/* X misc */
bool x_is_idle(void);
void x_setup(void);
void x_free(void);
gboolean x_mainloop_fd_dispatch(GSource * source, GSourceFunc callback,
gpointer user_data);
@ -84,4 +82,4 @@ gboolean x_mainloop_fd_check(GSource * source);
gboolean x_mainloop_fd_prepare(GSource * source, gint * timeout);
#endif
/* vim: set ts=8 sw=8 tw=0: */
/* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */

38
test/data/test-ini Normal file
View File

@ -0,0 +1,38 @@
#General comment
[bool]
booltrue = true #This is a test inline comment
booltrue_capital = TRUE
#This is a comment
boolfalse = false
boolfalse_capital = FALSE
boolyes = yes
boolyes_capital = YES
boolno = no
boolno_capital = NO
boolbin0 = 0
boolbin1 = 1
boolinvalid = invalidbool
[string]
simple = A simple string
quoted = "A quoted string"
quoted_with_quotes = "A string "with quotes""
[int]
simple = 5
negative = -10
decimal = 2.71828
leading_zeroes = 007
multi_char = 1024
[double]
simple = 1
decimal = 1.5
negative = -1.2
zeroes = 0.005
long = 3.141592653589793

195
test/functional-tests/test.sh Executable file
View File

@ -0,0 +1,195 @@
#!/bin/bash
function keypress {
echo "press enter to continue..."
read key
}
function basic_notifications {
../../dunstify -a "dunst tester" "normal" "<i>italic body</i>"
../../dunstify -a "dunst tester" -u c "critical" "<b>bold body</b>"
../../dunstify -a "dunst tester" "long body" "This is a notification with a very long body"
../../dunstify -a "dunst tester" "duplucate"
../../dunstify -a "dunst tester" "duplucate"
../../dunstify -a "dunst tester" "duplucate"
../../dunstify -a "dunst tester" "url" "www.google.de"
}
function show_age {
echo "###################################"
echo "show age"
echo "###################################"
killall dunst
../../dunst -config dunstrc.show_age &
../../dunstify -a "dunst tester" -u c "Show Age" "These should print their age after 2 seconds"
basic_notifications
keypress
}
function run_script {
echo "###################################"
echo "run script"
echo "###################################"
killall dunst
PATH=".:$PATH" ../../dunst -config dunstrc.run_script &
../../dunstify -a "dunst tester" -u c \
"Run Script" "After Keypress, 2 other notification should pop up. THis needs notify-send installed"
keypress
../../dunstify -a "dunst tester" -u c "trigger" "this should trigger a notification"
keypress
}
function ignore_newline {
echo "###################################"
echo "ignore newline"
echo "###################################"
killall dunst
../../dunst -config dunstrc.ignore_newline_no_wrap &
../../dunstify -a "dunst tester" -u c "Ignore Newline No Wrap" "There should be no newline anywhere"
../../dunstify -a "dunst tester" -u c "Th\nis\n\n\n is\n fu\nll of \n" "\nnew\nlines"
basic_notifications
keypress
killall dunst
../../dunst -config dunstrc.ignore_newline &
../../dunstify -a "dunst tester" -u c "Ignore Newline" \
"The only newlines you should encounter here are wordwraps. That's why I'm so long."
../../dunstify -a "dunst tester" -u c "Th\nis\n\n\n is\n fu\nll of \n" "\nnew\nlines"
basic_notifications
keypress
}
function replace {
echo "###################################"
echo "replace"
echo "###################################"
killall dunst
../../dunst -config dunstrc.default &
id=$(../../dunstify -a "dunst tester" -p "Replace" "this should get replaces after keypress")
keypress
../../dunstify -a "dunst tester" -r $id "Success?" "I hope this is not a new notification"
keypress
}
function markup {
echo "###################################"
echo "markup"
echo "###################################"
killall dunst
../../dunst -config dunstrc.markup "200x0+10+10" &
../../dunstify -a "dunst tester" "Markup Tests" -u "c"
../../dunstify -a "dunst tester" "<b>bold</b> <i>italic</i>"
../../dunstify -a "dunst tester" "<b>broken markup</i>"
keypress
killall dunst
../../dunst -config dunstrc.nomarkup "200x0+10+10" &
../../dunstify -a "dunst tester" -u c "NO Markup Tests"
../../dunstify -a "dunst tester" "<b>bold</b><i>italic</i>"
../../dunstify -a "dunst tester" "<b>broken markup</i>"
keypress
}
function corners {
echo "###################################"
echo "corners"
echo "###################################"
killall dunst
../../dunst -config dunstrc.default -geom "200x0+10+10" &
../../dunstify -a "dunst tester" -u c "upper left"
basic_notifications
keypress
killall dunst
../../dunst -config dunstrc.default -geom "200x0-10+10" &
../../dunstify -a "dunst tester" -u c "upper right"
basic_notifications
keypress
killall dunst
../../dunst -config dunstrc.default -geom "200x0-10-10" &
../../dunstify -a "dunst tester" -u c "lower right"
basic_notifications
keypress
killall dunst
../../dunst -config dunstrc.default -geom "200x0+10-10" &
../../dunstify -a "dunst tester" -u c "lower left"
basic_notifications
keypress
}
function geometry {
echo "###################################"
echo "geometry"
echo "###################################"
killall dunst
../../dunst -config dunstrc.default -geom "0x0" &
../../dunstify -a "dunst tester" -u c "0x0"
basic_notifications
keypress
killall dunst
../../dunst -config dunstrc.default -geom "200x0" &
../../dunstify -a "dunst tester" -u c "200x0"
basic_notifications
keypress
killall dunst
../../dunst -config dunstrc.default -geom "200x2" &
../../dunstify -a "dunst tester" -u c "200x2"
basic_notifications
keypress
killall dunst
../../dunst -config dunstrc.default -geom "200x1" &
../../dunstify -a "dunst tester" -u c "200x1"
basic_notifications
keypress
killall dunst
../../dunst -config dunstrc.default -geom "0x1" &
../../dunstify -a "dunst tester" -u c "0x1"
basic_notifications
keypress
killall dunst
../../dunst -config dunstrc.default -geom "-300x1" &
../../dunstify -a "dunst tester" -u c "-300x1"
basic_notifications
keypress
killall dunst
../../dunst -config dunstrc.default -geom "-300x1-20-20" &
../../dunstify -a "dunst tester" -u c "-300x1-20-20"
basic_notifications
keypress
killall dunst
../../dunst -config dunstrc.default -geom "x1" &
../../dunstify -a "dunst tester" -u c "x1-20-20" "across the screen"
basic_notifications
keypress
}
if [ -n "$1" ]; then
while [ -n "$1" ]; do
$1
shift
done
else
geometry
corners
show_age
run_script
ignore_newline
replace
markup
fi
killall dunst

1035
test/greatest.h Normal file

File diff suppressed because it is too large Load Diff

227
test/option_parser.c Normal file
View File

@ -0,0 +1,227 @@
#include "greatest.h"
#include <stdbool.h>
#include <glib.h>
#include "src/option_parser.h"
TEST test_next_section(void)
{
char *section = NULL;
ASSERT_STR_EQ("bool", (section = next_section(section)));
ASSERT_STR_EQ("string", (section = next_section(section)));
ASSERT_STR_EQ("int", (section = next_section(section)));
ASSERT_STR_EQ("double", (section = next_section(section)));
PASS();
}
TEST test_ini_get_bool(void)
{
char *bool_section = "bool";
ASSERT(ini_get_bool(bool_section, "booltrue", false));
ASSERT(ini_get_bool(bool_section, "booltrue_capital", false));
ASSERT_FALSE(ini_get_bool(bool_section, "boolfalse", true));
ASSERT_FALSE(ini_get_bool(bool_section, "boolfalse_capital", true));
ASSERT(ini_get_bool(bool_section, "boolyes", false));
ASSERT(ini_get_bool(bool_section, "boolyes_capital", false));
ASSERT_FALSE(ini_get_bool(bool_section, "boolno", true));
ASSERT_FALSE(ini_get_bool(bool_section, "boolno_capital", true));
ASSERT(ini_get_bool(bool_section, "boolbin1", false));
ASSERT_FALSE(ini_get_bool(bool_section, "boolbin0", true));
ASSERT(ini_get_bool(bool_section, "boolinvalid", true));
ASSERT_FALSE(ini_get_bool(bool_section, "boolinvalid", false));
ASSERT(ini_get_bool(bool_section, "nonexistent", true));
ASSERT_FALSE(ini_get_bool(bool_section, "nonexistent", false));
PASS();
}
TEST test_ini_get_string(void)
{
char *string_section = "string";
ASSERT_STR_EQ("A simple string", ini_get_string(string_section, "simple", ""));
ASSERT_STR_EQ("A quoted string", ini_get_string(string_section, "quoted", ""));
ASSERT_STR_EQ("A string \"with quotes\"", ini_get_string(string_section, "quoted_with_quotes", ""));
ASSERT_STR_EQ("default value", ini_get_string(string_section, "nonexistent", "default value"));
PASS();
}
TEST test_ini_get_int(void)
{
char *int_section = "int";
ASSERT_EQ(5, ini_get_int(int_section, "simple", 0));
ASSERT_EQ(-10, ini_get_int(int_section, "negative", 0));
ASSERT_EQ(2, ini_get_int(int_section, "decimal", 0));
ASSERT_EQ(7, ini_get_int(int_section, "leading_zeroes", 0));
ASSERT_EQ(1024, ini_get_int(int_section, "multi_char", 0));
ASSERT_EQ(10, ini_get_int(int_section, "nonexistent", 10));
PASS();
}
TEST test_ini_get_double(void)
{
char *double_section = "double";
ASSERT_EQ(1, ini_get_double(double_section, "simple", 0));
ASSERT_EQ(1.5, ini_get_double(double_section, "decimal", 0));
ASSERT_EQ(-1.2, ini_get_double(double_section, "negative", 0));
ASSERT_EQ(0.005, ini_get_double(double_section, "zeroes", 0));
ASSERT_EQ(3.141592653589793, ini_get_double(double_section, "long", 0));
ASSERT_EQ(10.5, ini_get_double(double_section, "nonexistent", 10.5));
PASS();
}
TEST test_cmdline_get_string(void)
{
ASSERT_STR_EQ("A simple string from the cmdline", cmdline_get_string("-string", "", ""));
ASSERT_STR_EQ("Single_word_string", cmdline_get_string("-str/-s", "", ""));
ASSERT_STR_EQ("Default", cmdline_get_string("-nonexistent", "Default", ""));
PASS();
}
TEST test_cmdline_get_int(void)
{
ASSERT_EQ(3, cmdline_get_int("-int", 0, ""));
ASSERT_EQ(2, cmdline_get_int("-int2/-i", 0, ""));
ASSERT_EQ(-7, cmdline_get_int("-negative", 0, ""));
ASSERT_EQ(4, cmdline_get_int("-zeroes", 0, ""));
ASSERT_EQ(2, cmdline_get_int("-intdecim", 0, ""));
ASSERT_EQ(10, cmdline_get_int("-nonexistent", 10, ""));
PASS();
}
TEST test_cmdline_get_double(void)
{
ASSERT_EQ(2, cmdline_get_double("-simple_double", 0, ""));
ASSERT_EQ(5.2, cmdline_get_double("-double", 0, ""));
ASSERT_EQ(3.14, cmdline_get_double("-nonexistent", 3.14, ""));
PASS();
}
TEST test_cmdline_get_bool(void)
{
ASSERT(cmdline_get_bool("-bool", false, ""));
ASSERT(cmdline_get_bool("-shortbool/-b", false, ""));
ASSERT(cmdline_get_bool("-boolnd/-n", true, ""));
ASSERT_FALSE(cmdline_get_bool("-boolnd/-n", false, ""));
PASS();
}
TEST test_cmdline_create_usage(void)
{
cmdline_get_string("-msgstring/-ms", "", "A string to test usage creation");
cmdline_get_int("-msgint/-mi", 0, "An int to test usage creation");
cmdline_get_double("-msgdouble/-md", 0, "A double to test usage creation");
cmdline_get_bool("-msgbool/-mb", false, "A bool to test usage creation");
char *usage = cmdline_create_usage();
ASSERT_FALSE(strstr(usage, "-msgstring/-ms") == NULL);
ASSERT_FALSE(strstr(usage, "A string to test usage creation") == NULL);
ASSERT_FALSE(strstr(usage, "-msgint/-mi") == NULL);
ASSERT_FALSE(strstr(usage, "An int to test usage creation") == NULL);
ASSERT_FALSE(strstr(usage, "-msgdouble/-md") == NULL);
ASSERT_FALSE(strstr(usage, "A double to test usage creation") == NULL);
ASSERT_FALSE(strstr(usage, "-msgbool/-mb") == NULL);
ASSERT_FALSE(strstr(usage, "A bool to test usage creation") == NULL);
free(usage);
PASS();
}
TEST test_option_get_string(void)
{
char *string_section = "string";
ASSERT_STR_EQ("A simple string", option_get_string(string_section, "simple", "-nonexistent", "", ""));
ASSERT_STR_EQ("Single_word_string", option_get_string(string_section, "simple", "-str/-s", "", ""));
ASSERT_STR_EQ("A simple string from the cmdline", option_get_string(string_section, "simple", "-string", "", ""));
ASSERT_STR_EQ("A simple string from the cmdline", option_get_string(string_section, "simple", "-string/-s", "", ""));
ASSERT_STR_EQ("Single_word_string", option_get_string(string_section, "simple", "-s", "", ""));
ASSERT_STR_EQ("Default", option_get_string(string_section, "nonexistent", "-nonexistent", "Default", ""));
PASS();
}
TEST test_option_get_int(void)
{
char *int_section = "int";
ASSERT_EQ(3, option_get_int(int_section, "negative", "-int", 0, ""));
ASSERT_EQ(2, option_get_int(int_section, "simple", "-int2/-i", 0, ""));
ASSERT_EQ(-7, option_get_int(int_section, "decimal", "-negative", 0, ""));
ASSERT_EQ(4, option_get_int(int_section, "simple", "-zeroes", 0, ""));
ASSERT_EQ(2, option_get_int(int_section, "simple", "-intdecim", 0, ""));
ASSERT_EQ(5, option_get_int(int_section, "simple", "-nonexistent", 0, ""));
ASSERT_EQ(-10, option_get_int(int_section, "negative", "-nonexistent", 0, ""));
ASSERT_EQ(2, option_get_int(int_section, "decimal", "-nonexistent", 0, ""));
ASSERT_EQ(7, option_get_int(int_section, "leading_zeroes", "-nonexistent", 0, ""));
ASSERT_EQ(1024, option_get_int(int_section, "multi_char", "-nonexistent", 0, ""));
ASSERT_EQ(3, option_get_int(int_section, "nonexistent", "-nonexistent", 3, ""));
PASS();
}
TEST test_option_get_double(void)
{
char *double_section = "double";
ASSERT_EQ(2, option_get_double(double_section, "simple", "-simple_double", 0, ""));
ASSERT_EQ(5.2, option_get_double(double_section, "simple", "-double", 0, ""));
ASSERT_EQ(0.005, option_get_double(double_section, "zeroes", "-nonexistent", 0, ""));
ASSERT_EQ(10.5, option_get_double(double_section, "nonexistent", "-nonexistent", 10.5, ""));
PASS();
}
TEST test_option_get_bool(void)
{
char *bool_section = "bool";
ASSERT(option_get_bool(bool_section, "boolfalse", "-bool/-b", false, ""));
ASSERT(option_get_bool(bool_section, "boolbin1", "-nonexistent", false, ""));
ASSERT_FALSE(option_get_bool(bool_section, "boolbin0", "-nonexistent", false, ""));
ASSERT_FALSE(option_get_bool(bool_section, "nonexistent", "-nonexistent", false, ""));
PASS();
}
SUITE(suite_option_parser)
{
FILE *config_file = fopen("data/test-ini", "r");
if (config_file == NULL) {
fputs("\nTest config file 'data/test-ini' couldn't be opened, failing.\n", stderr);
exit(1);
}
load_ini_file(config_file);
RUN_TEST(test_next_section);
RUN_TEST(test_ini_get_bool);
RUN_TEST(test_ini_get_string);
RUN_TEST(test_ini_get_int);
RUN_TEST(test_ini_get_double);
char cmdline[] = "dunst -bool -b "
"-string \"A simple string from the cmdline\" -s Single_word_string "
"-int 3 -i 2 -negative -7 -zeroes 04 -intdecim 2.5 "
"-simple_double 2 -double 5.2"
;
int argc;
char **argv;
g_shell_parse_argv(&cmdline[0], &argc, &argv, NULL);
cmdline_load(argc, argv);
RUN_TEST(test_cmdline_get_string);
RUN_TEST(test_cmdline_get_int);
RUN_TEST(test_cmdline_get_double);
RUN_TEST(test_cmdline_get_bool);
RUN_TEST(test_cmdline_create_usage);
RUN_TEST(test_option_get_string);
RUN_TEST(test_option_get_int);
RUN_TEST(test_option_get_double);
RUN_TEST(test_option_get_bool);
free_ini();
g_strfreev(argv);
fclose(config_file);
}
/* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */

14
test/test.c Normal file
View File

@ -0,0 +1,14 @@
#include "greatest.h"
SUITE_EXTERN(suite_utils);
SUITE_EXTERN(suite_option_parser);
GREATEST_MAIN_DEFS();
int main(int argc, char *argv[]) {
GREATEST_MAIN_BEGIN();
RUN_SUITE(suite_utils);
RUN_SUITE(suite_option_parser);
GREATEST_MAIN_END();
}
/* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */

View File

@ -1,195 +0,0 @@
#!/bin/bash
function keypress {
echo "press enter to continue..."
read key
}
function basic_notifications {
../dunstify -a "dunst tester" "normal" "<i>italic body</i>"
../dunstify -a "dunst tester" -u c "critical" "<b>bold body</b>"
../dunstify -a "dunst tester" "long body" "This is a notification with a very long body"
../dunstify -a "dunst tester" "duplucate"
../dunstify -a "dunst tester" "duplucate"
../dunstify -a "dunst tester" "duplucate"
../dunstify -a "dunst tester" "url" "www.google.de"
}
function show_age {
echo "###################################"
echo "show age"
echo "###################################"
killall dunst
../dunst -config dunstrc.show_age &
../dunstify -a "dunst tester" -u c "Show Age" "These should print their age after 2 seconds"
basic_notifications
keypress
}
function run_script {
echo "###################################"
echo "run script"
echo "###################################"
killall dunst
PATH=".:$PATH" ../dunst -config dunstrc.run_script &
../dunstify -a "dunst tester" -u c \
"Run Script" "After Keypress, 2 other notification should pop up. THis needs notify-send installed"
keypress
../dunstify -a "dunst tester" -u c "trigger" "this should trigger a notification"
keypress
}
function ignore_newline {
echo "###################################"
echo "ignore newline"
echo "###################################"
killall dunst
../dunst -config dunstrc.ignore_newline_no_wrap &
../dunstify -a "dunst tester" -u c "Ignore Newline No Wrap" "There should be no newline anywhere"
../dunstify -a "dunst tester" -u c "Th\nis\n\n\n is\n fu\nll of \n" "\nnew\nlines"
basic_notifications
keypress
killall dunst
../dunst -config dunstrc.ignore_newline &
../dunstify -a "dunst tester" -u c "Ignore Newline" \
"The only newlines you should encounter here are wordwraps. That's why I'm so long."
../dunstify -a "dunst tester" -u c "Th\nis\n\n\n is\n fu\nll of \n" "\nnew\nlines"
basic_notifications
keypress
}
function replace {
echo "###################################"
echo "replace"
echo "###################################"
killall dunst
../dunst -config dunstrc.default &
id=$(../dunstify -a "dunst tester" -p "Replace" "this should get replaces after keypress")
keypress
../dunstify -a "dunst tester" -r $id "Success?" "I hope this is not a new notification"
keypress
}
function markup {
echo "###################################"
echo "markup"
echo "###################################"
killall dunst
../dunst -config dunstrc.markup "200x0+10+10" &
../dunstify -a "dunst tester" "Markup Tests" -u "c"
../dunstify -a "dunst tester" "<b>bold</b> <i>italic</i>"
../dunstify -a "dunst tester" "<b>broken markup</i>"
keypress
killall dunst
../dunst -config dunstrc.nomarkup "200x0+10+10" &
../dunstify -a "dunst tester" -u c "NO Markup Tests"
../dunstify -a "dunst tester" "<b>bold</b><i>italic</i>"
../dunstify -a "dunst tester" "<b>broken markup</i>"
keypress
}
function corners {
echo "###################################"
echo "corners"
echo "###################################"
killall dunst
../dunst -config dunstrc.default -geom "200x0+10+10" &
../dunstify -a "dunst tester" -u c "upper left"
basic_notifications
keypress
killall dunst
../dunst -config dunstrc.default -geom "200x0-10+10" &
../dunstify -a "dunst tester" -u c "upper right"
basic_notifications
keypress
killall dunst
../dunst -config dunstrc.default -geom "200x0-10-10" &
../dunstify -a "dunst tester" -u c "lower right"
basic_notifications
keypress
killall dunst
../dunst -config dunstrc.default -geom "200x0+10-10" &
../dunstify -a "dunst tester" -u c "lower left"
basic_notifications
keypress
}
function geometry {
echo "###################################"
echo "geometry"
echo "###################################"
killall dunst
../dunst -config dunstrc.default -geom "0x0" &
../dunstify -a "dunst tester" -u c "0x0"
basic_notifications
keypress
killall dunst
../dunst -config dunstrc.default -geom "200x0" &
../dunstify -a "dunst tester" -u c "200x0"
basic_notifications
keypress
killall dunst
../dunst -config dunstrc.default -geom "200x2" &
../dunstify -a "dunst tester" -u c "200x2"
basic_notifications
keypress
killall dunst
../dunst -config dunstrc.default -geom "200x1" &
../dunstify -a "dunst tester" -u c "200x1"
basic_notifications
keypress
killall dunst
../dunst -config dunstrc.default -geom "0x1" &
../dunstify -a "dunst tester" -u c "0x1"
basic_notifications
keypress
killall dunst
../dunst -config dunstrc.default -geom "-300x1" &
../dunstify -a "dunst tester" -u c "-300x1"
basic_notifications
keypress
killall dunst
../dunst -config dunstrc.default -geom "-300x1-20-20" &
../dunstify -a "dunst tester" -u c "-300x1-20-20"
basic_notifications
keypress
killall dunst
../dunst -config dunstrc.default -geom "x1" &
../dunstify -a "dunst tester" -u c "x1-20-20" "across the screen"
basic_notifications
keypress
}
if [ -n "$1" ]; then
while [ -n "$1" ]; do
$1
shift
done
else
geometry
corners
show_age
run_script
ignore_newline
replace
markup
fi
killall dunst

136
test/utils.c Normal file
View File

@ -0,0 +1,136 @@
#include "greatest.h"
#include "src/utils.h"
TEST test_string_replace_char(void)
{
char *text = malloc(128 * sizeof(char));
strcpy(text, "a aa aaa");
ASSERT_STR_EQ("b bb bbb", string_replace_char('a', 'b', text));
strcpy(text, "Nothing to replace");
ASSERT_STR_EQ("Nothing to replace", string_replace_char('s', 'a', text));
strcpy(text, "");
ASSERT_STR_EQ("", string_replace_char('a', 'b', text));
free(text);
PASS();
}
/*
* We trust that string_replace_all and string_replace properly reallocate
* memory if the result is longer than the given string, no real way to test for
* that far as I know.
*/
TEST test_string_replace_all(void)
{
char *text = malloc(128 * sizeof(char));
strcpy(text, "aaaaa");
ASSERT_STR_EQ("bbbbb", (text = string_replace_all("a", "b", text)));
strcpy(text, "");
ASSERT_STR_EQ("", (text = string_replace_all("a", "b", text)));
strcpy(text, "Nothing to replace");
ASSERT_STR_EQ((text = string_replace_all("z", "a", text)), "Nothing to replace");
strcpy(text, "Reverse this");
ASSERT_STR_EQ("Reverse sith", (text = string_replace_all("this", "sith", text)));
strcpy(text, "abcdabc");
ASSERT_STR_EQ("xyzabcdxyzabc", (text = string_replace_all("a", "xyza", text)));
free(text);
PASS();
}
TEST test_string_replace(void)
{
char *text = malloc(128 * sizeof(char));
strcpy(text, "aaaaa");
ASSERT_STR_EQ("baaaa", (text = string_replace("a", "b", text)) );
strcpy(text, "");
ASSERT_STR_EQ((text = string_replace("a", "b", text)), "");
strcpy(text, "Nothing to replace");
ASSERT_STR_EQ((text = string_replace("z", "a", text)), "Nothing to replace");
strcpy(text, "Reverse this");
ASSERT_STR_EQ("Reverse sith", (text = string_replace("this", "sith", text)));
strcpy(text, "abcdabc");
ASSERT_STR_EQ("xyzabcdabc", (text = string_replace("a", "xyza", text)));
free(text);
PASS();
}
TEST test_string_append(void)
{
SKIP(); //TODO: Implement this
PASS();
}
TEST test_string_to_argv(void)
{
char **argv = string_to_argv("argv");
ASSERT_STR_EQ("argv", argv[0]);
ASSERT_EQ( NULL, argv[1]);
free(argv);
argv = NULL;
argv = string_to_argv("echo test");
ASSERT_STR_EQ("echo", argv[0]);
ASSERT_STR_EQ("test", argv[1]);
ASSERT_EQ( NULL, argv[2]);
free(argv);
argv = NULL;
argv = string_to_argv("");
ASSERT_EQ( NULL, argv[0]);
free(argv);
PASS();
}
TEST test_string_strip_delimited(void)
{
char *text = malloc(128 * sizeof(char));
strcpy(text, "A <simple> string_strip_delimited test");
string_strip_delimited(text, '<', '>');
ASSERT_STR_EQ("A string_strip_delimited test", text);
strcpy(text, "Remove <blink>html <b><i>tags</i></b></blink>");
string_strip_delimited(text, '<', '>');
ASSERT_STR_EQ("Remove html tags", text);
strcpy(text, "Calls|with|identical|delimiters|are|handled|properly");
string_strip_delimited(text, '|', '|');
ASSERT_STR_EQ("Calls", text);
strcpy(text, "<Return empty string if there is nothing left>");
string_strip_delimited(text, '<', '>');
ASSERT_STR_EQ("", text);
strcpy(text, "Nothing is done if there are no delimiters in the string");
string_strip_delimited(text, '<', '>');
ASSERT_STR_EQ("Nothing is done if there are no delimiters in the string", text);
free(text);
PASS();
}
SUITE(suite_utils)
{
RUN_TEST(test_string_replace_char);
RUN_TEST(test_string_replace_all);
RUN_TEST(test_string_replace);
RUN_TEST(test_string_append);
RUN_TEST(test_string_to_argv);
RUN_TEST(test_string_strip_delimited);
}
/* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */