feat: added dunst with centering branch

master
Toerd@archlinux 5 years ago
parent abd783625b
commit 0df656e3da

@ -61,7 +61,7 @@ A,P,bspwm,windowmanagement
A,P,sxhkd,Keyboard shortcuts
A,P,compton,Required for st to get transparent background (xcomp graphic glitches)
A,P,libnotify,notifications
A,P,dunst,Notification Server
A,M,dunst,Notification Server (with centering support)
# A,A,polybar,status bar
A,A,lemonbar-xft-git,status bar
A,A,xtitle,helper to get window titles

1 # First column: A = All, L = Laptop, D = Desktop
61 # Other display related stuff
62 A,P,redshift,No more burning eyes at night <3
63 # Sound
64 A,P,alsa-plugins,Extra alsa plugins
65 A,P,alsa-utils,An alternative implementation of Linux sound support
66 A,A,apulse,Compatibility layer for applications that rely on pulseaudio
67 # pulseaudio

@ -0,0 +1,24 @@
<!--
If you want to report bugs, it's the best to get something reproducible!
These program calls might help:
While the notification gets sent:
`dbus-monitor path=/org/freedesktop/Notifications`
If dunst segfaults (please install the debug symbols or install dunst manually again):
`gdb -ex run dunst -ex bt`
* ISSUE DESCRIPTION GOES BELOW THIS LINE * -->
### Installation info
<!-- If your version dates before 1.2, please rule out, that the behavior is fixed in master already -->
- Version: `<!-- output of dunst -v -->`
- Install type: `<!-- [package|manually|...] -->`
- Distro and version: ` `

@ -0,0 +1,9 @@
dunst
*.o
core
vgcore.*
dunst.1
org.knopwob.dunst.service
dunst.systemd.service
dunstify
test/test

@ -0,0 +1,27 @@
addons:
apt:
packages:
- libdbus-1-dev
- libx11-dev
- libxrandr-dev
- libxinerama-dev
- libxss-dev
- libxdg-basedir-dev
- libglib2.0-dev
- libpango1.0-dev
- libcairo2-dev
- libnotify-dev
- libgtk-3-dev
dist: trusty
sudo: false
language: c
script: CFLAGS=-Werror make all dunstify test
compiler:
- gcc
- clang
notifications:
irc:
channels:
- "chat.freenode.net#dunst"
on_success: change
on_failure: always

@ -0,0 +1,4 @@
Sascha Kruse (http://github.com/knopwob)
contributors:
See `git shortlog` for a list of contributors and their contributions

@ -0,0 +1,115 @@
# Dunst changelog
## Unreleased
### Added
- `ellipsize` option to control how long lines should be ellipsized when `word_wrap` is set to `false`
### Fixed
- `new_icon` rule being ignored on notifications that had a raw icon
- Do not replace format strings, which are in notification content
- DBus related memory leaks closed:
On the DBus connection, all hints never got freed. For raw icons, dunst saved them three times.
## Changed
- transient hints are now handled
An additional rule option (`match_transient` and `set_transient`) is added
to optionally reset the transient setting
## 1.2.0 - 2017-07-12
### Added
- `always_run_script` option to run script even if a notification is suppressed
- Support for more icon file types
- Support for raw icons
- `hide_duplicate_count` option to hide the number of duplicate notifications
- Support for per-urgency frame colours
- `markup` setting for more fine-grained control over how markup is handled
- `history_ignore` rule action to exclude a notification from being added to the history
- Support for setting the dpi value dunst will use for font rendering via the `Xft.dpi` X resource
- Experimental support for per-monitor dpi calculation
- `max_icon_size` option to scale down icons if they exceed a certain size
- Middle click on notifications can be used to trigger actions
- Systemd service file, installed by default
- `%n` format flag for getting progress value without any extra characters
### Changed
- Text and icons are now centred vertically
- Notifications aren't considered duplicate if urgency or icons differ
- The maximum length of a notification is limited to 5000 characters
- The frame width and color settings were moved to the global section as `frame_width` and `frame_color` respectively
- Dropped Xinerama in favour of RandR, Xinerama can be enabled with the `-force_xinerama` option if needed
### Deprecated
- `allow_markup` is deprecated with `markup` as its replacement
- The urgency specific command line flags have been deprecated with no replacement, respond to issue #328 on the bug tracker if you depend on them
### Fixed
- Infinite loop if there are 2 configuration file sections with the same name
- URLs with dashes and underscores in them are now parsed properly
- Many memory leaks
- Category based rules were applied without actually matching
- dmenu command not parsing quoted arguments correctly
- Icon alignment with dynamic width
- Issue when loading configuration files with very long lines
- '\n' is no longer expanded to a newline inside notification text
- Notification window wasn't redrawn if obscured on systems without a compositor
- `ignore_newline` now works regardless of the markup setting
- dmenu process being left as a zombie if no option was selected
- Crash when opening urls parsed from `<a href="">` tags
## 1.1.0 - 2014-07-29
- fix nasty memory leak
- icon support (still work in progress)
- fix issue where keybindings aren't working when numlock is activated
## 1.0.0 - 2013-04-15
- use pango/cairo as drawing backend
- make use of pangos ability to parse markup
- support for actions via context menu
- indicator for actions/urls found
- use blocking I/O. No more waking up the CPU multiple times per second to check for new dbus messages
## 0.5.0 - 2013-01-26
- new default dunstrc
- frames for window
- trigger scripts on matching notifications
- context menu for urls (using dmenu)
- pause and resume function
- use own code for ini parsing (this removes inih)
- progress hints
## 0.4.0 - 2012-09-27
- separator between notifications
- word wrap long lines
- real transparance
- bouncing text (alternative to word_wrap)
- new option for line height
- better multihead support
- don't die when keybindings can't be grabbed
- bugfix: forgetting geometry
- (optional) static configuration
## 0.3.1 - 2012-08-02
- fix -mon option
## 0.3.0 - 2012-07-30
- full support for Desktop Notification Specification (mandatory parts)
- option to select monitor on which notifications are shown
- follow focus
- oneline mode
- text alignment
- show age of notifications
- sticky history
- filter duplicate messages
- keybinding to close all notifications
- new way to specify keybindings
- cleanup / bugfixes etc.
- added dunst.service
## 0.2.0 - 2012-06-26
- introduction of dunstrc
- removed static configuration via config.h
- don't timeout when user is idle
- xft-support
- history (a.k.a. redisplay old notifications)

@ -0,0 +1,29 @@
Copyright © 2013, Sascha Kruse and contributors
All rights reserved.
All files (unless otherwise noted) are licensed under the BSD license:
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of Sascha Kruse nor the
names of contributors may be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY Sascha Kruse ''AS IS'' AND ANY
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL Sascha Kruse BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

@ -0,0 +1,105 @@
# dunst - Notification-daemon
# See LICENSE file for copyright and license details.
include config.mk
VERSION := "1.2.0-non-git"
ifneq ($(wildcard ./.git/.),)
VERSION := $(shell git describe --tags)
endif
LIBS := $(shell pkg-config --libs ${pkg_config_packs})
INCS := $(shell pkg-config --cflags ${pkg_config_packs})
ifneq (clean, $(MAKECMDGOALS))
ifeq ($(and $(INCS),$(LIBS)),)
$(error "pkg-config failed!")
endif
endif
CFLAGS += -I. ${INCS}
LDFLAGS+= -L. ${LIBS}
SRC := $(sort $(shell find src/ -name '*.c'))
OBJ := ${SRC:.c=.o}
TEST_SRC := $(sort $(shell find test/ -name '*.c'))
TEST_OBJ := $(TEST_SRC:.c=.o)
.PHONY: all debug
all: doc dunst service
debug: CFLAGS += ${CFLAGS_DEBUG}
debug: LDFLAGS += ${LDFLAGS_DEBUG}
debug: CPPFLAGS += ${CPPFLAGS_DEBUG}
debug: all
.c.o:
${CC} -o $@ -c $< ${CFLAGS}
${OBJ}: config.mk
dunst: ${OBJ} main.o
${CC} ${CFLAGS} -o $@ ${OBJ} main.o ${LDFLAGS}
dunstify: dunstify.o
${CC} ${CFLAGS} -o $@ dunstify.o ${LDFLAGS}
.PHONY: test
test: test/test
cd test && ./test
test/test: ${OBJ} ${TEST_OBJ}
${CC} ${CFLAGS} -o $@ ${TEST_OBJ} ${OBJ} ${LDFLAGS}
.PHONY: doc
doc: docs/dunst.1
docs/dunst.1: docs/dunst.pod
pod2man --name=dunst -c "Dunst Reference" --section=1 --release=${VERSION} $< > $@
.PHONY: service
service:
@sed "s|##PREFIX##|$(PREFIX)|" org.knopwob.dunst.service.in > org.knopwob.dunst.service
@sed "s|##PREFIX##|$(PREFIX)|" dunst.systemd.service.in > dunst.systemd.service
.PHONY: clean clean-dunst clean-dunstify clean-doc clean-tests
clean: clean-dunst clean-dunstify clean-doc clean-tests
clean-dunst:
rm -f dunst ${OBJ} main.o
rm -f org.knopwob.dunst.service
rm -f dunst.systemd.service
clean-dunstify:
rm -f dunstify.o
rm -f dunstify
clean-doc:
rm -f docs/dunst.1
clean-tests:
rm -f test/test test/*.o
.PHONY: install install-dunst install-doc install-service uninstall
install: install-dunst install-doc install-service
install-dunst: dunst doc
mkdir -p ${DESTDIR}${PREFIX}/bin
install -m755 dunst ${DESTDIR}${PREFIX}/bin
mkdir -p ${DESTDIR}${MANPREFIX}/man1
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 -Dm644 dunst.systemd.service ${DESTDIR}${PREFIX}/lib/systemd/user/dunst.service
uninstall:
rm -f ${DESTDIR}${PREFIX}/bin/dunst
rm -f ${DESTDIR}${MANPREFIX}/man1/dunst.1
rm -f ${DESTDIR}${PREFIX}/share/dbus-1/services/org.knopwob.dunst.service
rm -f ${DESTDIR}${PREFIX}/lib/systemd/user/dunst.service
rm -rf ${DESTDIR}${PREFIX}/share/dunst

@ -0,0 +1,51 @@
[![Build Status](https://travis-ci.org/dunst-project/dunst.svg?branch=master)](https://travis-ci.org/dunst-project/dunst)
## Dunst
* [Wiki][wiki]
* [Description](#description)
* [Compiling](#compiling)
* [Copyright](#copyright)
## 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](https://github.com/dunst-project/dunst/wiki/Dependencies):
- dbus
- libxinerama
- libxrandr
- libxss
- libxdg-basedir
- glib
- pango/cairo
- libgtk-3-dev
Checkout the [wiki][wiki] for more information.
## Bug reports
Please use the [issue tracker][issue-tracker] provided by GitHub to send us bug reports or feature requests. You can also join us on the IRC channel `#dunst` on Freenode.
## Mantainers
Nikos Tsipinakis <nikos@tsipinakis.com>
Jonathan Lusso <jonilusso@gmail.com>
## Author
written by Sascha Kruse <dunst@knopwob.de>
## Copyright
copyright 2013 Sascha Kruse and contributors (see LICENSE for licensing information)
If you feel that copyrights are violated, please send me an email.
[issue-tracker]: https://github.com/dunst-project/dunst/issues
[wiki]: https://github.com/dunst-project/dunst/wiki

@ -0,0 +1,146 @@
===================================================================================
Release Notes For v1.2.0
===================================================================================
After about 3 years of inactivity, dunst is back under active development.
Version 1.2 is supposed to be fully backwards compatible with 1.1 but due to
the number of changes and the time since the last release there may be some
overlooked breakages. If one is found it should be reported to the bug tracker.
For users:
* Markup
The `allow_markup` setting has been deprecated in favour of `markup` which
is a multi-value setting that can be used to control much more accurately
how markup in notifications should be handled. Currently it only supports
`no`, `strip` and `full` as values but it is planned to be expanded soon.
To preserve backwards compatibility, `allow_markup` is still supported but
users are encouraged to update their configuration files since it will be
removed after a few major releases.
* DPI handling
The DPI value used is now retrieved from the `Xft.dpi` X resource if
available. If not, the default value 96 will be used.
Additionally, as an experiment a per-monitor dpi setting, which tries to
calculate an acceptable dpi values for each monitor, has been added to the
experimental section of the configuration file.
* RandR and Xinerama
Dunst switched from using the Xinerama extension to provide multi-monitor
support to using the more modern RandR extension. While this change won't
affect the majority of users, some legacy drivers do not support RandR. In
that case, the `force_xinerama` option was added as a way to fall back to
the old method.
The downside of forcing Xinerama to be used is that dunst won't be able to
detect when a monitor is added or removed meaning that follow mode might
break if the screen layout changes.
* Frame settings
All the settings in the frame section of the configuration file have been
deprecated and have been moved into the global section. The `color` and `size`
settings became `frame_color` and `frame_size` respectively. As with
`allow_markup`, the old format still works but it'll be removed in one of the
next major releases.
* Deprecation of urgency-specific command line flags
The urgency specific command line flags (`-li, -ni, -ci, -lf, -nf, -cf, -lb,
-nb, -cb, -lfr, -nfr, -cfr, -lto, -nto, -cto`) have been deprecated with no
plan for a replacement. If you rely on them please respond to issue #328 on
the bug tracker with your use case.
For maintainers:
* The project homepage has been changed to https://dunst-project.org
* The main repository has been changed to https://github.com/dunst-project/dunst
* Dependency changes:
- Dependency on libraries that were unused in the code but were mentioned as
dependencies has been dropped. Dunst no longer depends on: libfreetype,
libxft and libxext.
- Added dependency on libxradnr and libgtk2.0.
For a full list of changes see CHANGELOG.md.
===================================================================================
Release Notes For v1.0.0
===================================================================================
PACKAGE MAINTAINERS:
There are new dependencies introduced with this version:
*glib
*pango/cairo
* The drawing backend was moved from Xlib to Cairo/Pango.
This change requires some user intervention since Pango
uses different font strings. For example "Monospace-12"
becomes "Monospace 12". Font sizes might also get interpreted
differently.
* Markup
Markup within the notification can be interpreted by pango.
Examples are <i>italic</i> and <b>bold</b>. If the Markup
can't be parsed by pango, the tags are stripped from the
notification and the text is displayed as plain text. An error
message gets printed to stdout describing why the markup could
not be parsed.
To make use of markup the option allow_markup must be set in dunstrc.
If this option is not set, dunst falls back to the old behaviour
(stripping all tags from the text and display plain text).
* Actions are now supported.
If a client adds an action to a notification this gets indicated
by an (A) infront of the notification. In this case the
context menu shortcut can be used to invoke this action.
* Indicator for URLs.
If dunst detects an URL within the notification this gets indicated
by an (U) infront of the notification. As with actions the URL can
be opened with the context menu shortcut. (This requires the browser
option to be set in the dunstrc).
* dunstify ( a drop-in replacement for notify-send)
Since notify-send lacks some features I need to for testing, I've
written dunstify. It supports the same option as notify-send + The
abillity to print the id of the notification to stdout and to replace
or close existing notifications.
example:
id=$(dunstify -p "Replace" "this should get replaced after the sleep")
sleep 5
dunstify -r $id "replacement"
see dunstify --help for additional info.
Since dunstify depends on non-public parts of libnotify it may break
on every other libnotify version than that version that I use.
Because of this it does not get build and installed by default.
It can be build with "make dunstify". An installation target does
not exist.
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.

@ -0,0 +1,148 @@
/* see example dunstrc for additional explanations about these options */
settings_t defaults = {
.font = "-*-terminus-medium-r-*-*-16-*-*-*-*-*-*-*",
.markup = MARKUP_NO,
.normbgcolor = "#1793D1",
.normfgcolor = "#DDDDDD",
.critbgcolor = "#ffaaaa",
.critfgcolor = "#000000",
.lowbgcolor = "#aaaaff",
.lowfgcolor = "#000000",
.format = "%s %b", /* default format */
.timeouts = { 10*G_USEC_PER_SEC, 10*G_USEC_PER_SEC, 0 }, /* low, normal, critical */
.icons = { "dialog-information", "dialog-information", "dialog-warning" }, /* low, normal, critical */
.transparency = 0, /* transparency */
.geom = "0x0", /* geometry */
.title = "Dunst", /* the title of dunst notification windows */
.class = "Dunst", /* the class of dunst notification windows */
.shrink = false, /* shrinking */
.sort = true, /* sort messages by urgency */
.indicate_hidden = true, /* show count of hidden messages */
.idle_threshold = 0, /* don't timeout notifications when idle for x seconds */
.show_age_threshold = -1, /* show age of notification, when notification is older than x seconds */
.align = left, /* text alignment [left/center/right] */
.sticky_history = true,
.history_length = 20, /* max amount of notifications kept in history */
.show_indicators = true,
.word_wrap = false,
.ellipsize = middle,
.ignore_newline = false,
.line_height = 0, /* if line height < font height, it will be raised to font height */
.notification_height = 0, /* if notification height < font height and padding, it will be raised */
.separator_height = 2, /* height of the separator line between two notifications */
.padding = 0,
.h_padding = 0, /* horizontal padding */
.sep_color = AUTO, /* AUTO, FOREGROUND, FRAME, CUSTOM */
.sep_custom_color_str = NULL,/* custom color if sep_color is set to CUSTOM */
.frame_width = 0,
.frame_color = "#888888",
/* show a notification on startup
* This is mainly for crash detection since dbus restarts dunst
* automatically after a crash, so crashes might get unnotices otherwise
* */
.startup_notification = false,
/* monitor to display notifications on */
.monitor = 0,
/* path to dmenu */
.dmenu = "/usr/bin/dmenu",
.browser = "/usr/bin/firefox",
.max_icon_size = 0,
/* paths to default icons */
.icon_path = "/usr/share/icons/gnome/16x16/status/:/usr/share/icons/gnome/16x16/devices/",
/* follow focus to different monitor and display notifications there?
* possible values:
* FOLLOW_NONE
* FOLLOW_MOUSE
* FOLLOW_KEYBOARD
*
* everything else than FOLLOW_NONE overrides 'monitor'
*/
.f_mode = FOLLOW_NONE,
/* keyboard shortcuts
* use for example "ctrl+shift+space"
* use "none" to disable
*/
.close_ks = {.str = "none",
.code = 0,.sym = NoSymbol,.is_valid = false
}, /* ignore this */
.close_all_ks = {.str = "none",
.code = 0,.sym = NoSymbol,.is_valid = false
}, /* ignore this */
.history_ks = {.str = "none",
.code = 0,.sym = NoSymbol,.is_valid = false
}, /* ignore this */
.context_ks = {.str = "none",
.code = 0,.sym = NoSymbol,.is_valid = false
}, /* ignore this */
};
rule_t default_rules[] = {
/* name can be any unique string. It is used to identify
* the rule in dunstrc to override it there
*/
/* an empty rule with no effect */
{
.name = "empty",
.appname = NULL,
.summary = NULL,
.body = NULL,
.icon = NULL,
.category = NULL,
.msg_urgency = -1,
.timeout = -1,
.urgency = -1,
.markup = MARKUP_NULL,
.history_ignore = 1,
.match_transient = 1,
.set_transient = -1,
.new_icon = NULL,
.fg = NULL,
.bg = NULL,
.format = NULL,
.script = NULL,
},
/* ignore transient hints in history by default */
{
.name = "ignore_transient_in_history",
.appname = NULL,
.summary = NULL,
.body = NULL,
.icon = NULL,
.category = NULL,
.msg_urgency = -1,
.timeout = -1,
.urgency = -1,
.markup = MARKUP_NULL,
.history_ignore = 1,
.match_transient = 1,
.set_transient = -1,
.new_icon = NULL,
.fg = NULL,
.bg = NULL,
.format = NULL,
.script = NULL,
},
};
/* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */

@ -0,0 +1,38 @@
# paths
PREFIX ?= /usr/local
MANPREFIX = ${PREFIX}/share/man
# uncomment to disable parsing of dunstrc
# or use "CFLAGS=-DSTATIC_CONFIG make" to build
#STATIC= -DSTATIC_CONFIG # Warning: This is deprecated behavior
# flags
CPPFLAGS += -D_DEFAULT_SOURCE -DVERSION=\"${VERSION}\"
CFLAGS += -g --std=gnu99 -pedantic -Wall -Wno-overlength-strings -Os ${STATIC} ${CPPFLAGS}
LDFLAGS += -lm -L${X11LIB}
CPPFLAGS_DEBUG := -DDEBUG_BUILD
CFLAGS_DEBUG := -O0
LDFLAGS_DEBUG :=
pkg_config_packs := dbus-1 \
gio-2.0 \
gdk-3.0 \
"glib-2.0 >= 2.36" \
pangocairo \
x11 \
xinerama \
"xrandr >= 1.5" \
xscrnsaver
# check if we need libxdg-basedir
ifeq (,$(findstring STATIC_CONFIG,$(CFLAGS)))
pkg_config_packs += libxdg-basedir
else
$(warning STATIC_CONFIG is deprecated behavior. It will get removed in future releases)
endif
# dunstify also needs libnotify
ifneq (,$(findstring dunstify,${MAKECMDGOALS}))
pkg_config_packs += libnotify
endif

@ -0,0 +1,6 @@
#!/bin/bash
summary="$2"
body="$3"
echo "$summary $body" | espeak

@ -0,0 +1,720 @@
=head1 NAME
dunst - A customizable and lightweight notification-daemon
=head1 SYNOPSIS
dunst [-conf file] [-font font] [-geometry geom] [-format fmt] [-follow mode] [-monitor n] [-history_length n] ...
=head1 DESCRIPTION
Dunst is a highly configurable and lightweight notification daemon.
=head1 COMMAND LINE OPTIONS
=over 4
=item B<-h/--help>
List all command line flags
=item B<-conf/-config file>
Use alternative config file.
=item B<-v/--version>
Print version information.
=item B<-print>
Print notifications to stdout. This might be useful for logging, setting up
rules or using the output in other scripts.
=back
=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.
The configuration is divided into sections in an ini-like format. The 'global'
section contains most general settings while the 'shortcuts' sections contains
all keyboard configuration and the 'experimental' section all the features that
have not yet been tested thoroughly.
Any section that is not one of the above is assumed to be a rule, see RULES for
more details.
For backwards compatibility reasons the section name 'frame' is considered bound
and can't be used as a rule.
=head2 Command line
Each configuration option in the global section can be overridden from the
command line by adding a single dash in front of it's name.
For example the font option can be overridden by running
$ dunst -font "LiberationSans Mono 4"
Configuration options that take boolean values can only currently be set to
"true" through the command line via the same method. e.g.
$ dunst -shrink
This is a known limitation of the way command line parameters are parsed and
will be changed in the future.
Available settings per section:
=head2 Global section
=over 4
=item B<monitor> (default: 0)
Specifies on which monitor the notifications should be displayed in, count
starts at 0. See the B<follow> setting.
=item B<follow> (values: [none/mouse/keyboard] default: none)
Defines where the notifications should be placed in a multi-monitor setup. All
values except I<none> override the B<monitor> setting.
=over 4
=item B<none>
The notifications will be placed on the monitor specified by the B<monitor>
setting.
=item B<mouse>
The notifications will be placed on the monitor that the mouse is currently in.
=item B<keyboard>
The notifications will be placed on the monitor that contains the window with
keyboard focus.
=back
=item B<geometry> (format: [{width}][x{height}][+/-{x}[+/-{y}]], default: "0x0+0-0")
The geometry of the window the notifications will be displayed in.
=over 4
=item B<width>
The width of the notification window in pixels. A negative value sets the width
to the screen width B<minus the absolute value of the width>. If the width is
omitted then the window expands to cover the whole screen. If it's 0 the window
expands to the width of the longest message being displayed.
=item B<height>
The number of notifications that can appear at one time. When this
limit is reached any additional notifications will be queued and displayed when
the currently displayed ones either time out or are manually dismissed. If
B<indicate_hidden> is true, then the specified limit is reduced by 1 and the
last notification is a message informing how many hidden notifications are
waiting to be displayed. See the B<indicate_hidden> entry for more information.
The physical(pixel) height of the notifications vary depending on the number of
lines that need to be displayed.
See B<notification_height> for changing the physical height.
=item B<x/y>
Respectively the horizontal and vertical offset in pixels from the corner
of the screen that the notification should be drawn at. For the horizontal(x)
offset, a positive value is measured from the left of the screen while a
negative one from the right. For the vertical(y) offset, a positive value is
measured from the top while a negative from the bottom.
It's important to note that the positive and negative sign B<DOES> affect the
position even if the offset is 0. For example, a horizontal offset of +0 puts
the notification on the left border of the screen while a horizontal offset of
-0 at the right border. The same goes for the vertical offset.
=back
=item B<indicate_hidden> (values: [true/false], default: true)
If this is set to true, a notification indicating how many notifications are
not being displayed due to the notification limit (see B<geometry>) will be
shown B<in place of the last notification slot>.
Meaning that if this is enabled the number of visible notifications will be 1
less than what is specified in geometry, the last slot will be taken by the
hidden count.
=item B<shrink> (values: [true/false], default: false)
Shrink window if it's smaller than the width. Will be ignored if width is 0.
This is used mainly in order to have the shrinking benefit of dynamic width (see
geometry) while also having an upper bound on how long a notification can get
before wrapping.
=item B<transparency> (default: 0)
A 0-100 range on how transparent the notification window should be, with 0
being fully opaque and 100 invisible.
This setting will only work if a compositor is running.
=item B<notification_height> (default: 0)
The minimum height of the notification window in pixels. If the text and
padding cannot fit in within the height specified by this value, the height
will be increased as needed.
=item B<separator_height> (default: 2)
The height in pixels of the separator between notifications, if set to 0 there
will be no separating line between notifications.
=item B<padding> (default: 0)
The distance in pixels from the content to the separator/border of the window
in the vertical axis
=item B<horizontal_padding> (default: 0)
The distance in pixels from the content to the border of the window
in the horizontal axis
=item B<frame_width> (default: 0)
Defines width in pixels of frame around the notification window. Set to 0 to
disable.
=item B<frame_color color> (default: #888888)
Defines color of the frame around the notification window. See COLORS.
=item B<separator_color> (values: [auto/foreground/frame/#RRGGBB] default: auto)
Sets the color of the separator line between two notifications.
=over 4
=item B<auto>
Dunst tries to find a color that fits the rest of the notification color
scheme automatically.
=item B<foreground>
The color will be set to the same as the foreground color of the topmost
notification that's being separated.
=item B<frame>
The color will be set to the frame color of the notification with the highest
urgency between the 2 notifications that are being separated.
=item B<anything else>
Any other value is interpreted as a color, see COLORS
=back
=item B<sort> (values: [true/false], default: true)
If set to true, display notifications with higher urgency above the others.
=item B<idle_threshold> (default: 0)
Don't timeout notifications if user is idle longer than this time.
See TIME FORMAT for valid times.
Set to 0 to disable.
Transient notifications will ignore this setting and timeout anyway.
Use a rule overwriting with 'set_transient = no' to disable this behavior.
=item B<font> (default: "Monospace 8")
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.
This options is parsed as a Pango font description.
=item B<line_height> (default: 0)
The amount of extra spacing between text lines in pixels. Set to 0 to
disable.
=item B<markup> (values: [full/strip/no], default: no)
Defines how markup in notifications is handled.
It's important to note that markup in the format option will be parsed
regardless of what this is set to.
Possible values:
=over 4
=item B<full>
Allow a small subset of html markup in notifications
<b>bold</b>
<i>italic</i>
<s>strikethrough</s>
<u>underline</u>
For a complete reference see
<http://developer.gnome.org/pango/stable/PangoMarkupFormat.html>
=item B<strip>
This setting is provided for compatibility with some broken
clients that send markup even though it's not enabled on the
server.
Dunst will try to strip the markup but the parsing is simplistic so using this
option outside of matching rules for specific applications B<IS GREATLY
DISCOURAGED>.
See RULES
=item B<no>
Disable markup parsing, incoming notifications will be treated as
plain text. Dunst will not advertise that it can parse markup if this is set as
a global setting.
=back
=item B<format> (default: "%s %b")
Specifies how the various attributes of the notification should be formatted on
the notification window.
Regardless of the status of the B<markup> setting, any markup tags that are
present in the format will be parsed. Note that because of that, if a literal
ampersand (&) is needed it needs to be escaped as '&amp;'
If '\n' is present anywhere in the format, it will be replaced with
a literal newline.
If any of the following strings are present, they will be replaced with the
equivalent notification attribute.
=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%])
=item B<%n> progress value without any extra characters
=item B<%%> Literal %
=back
If any of these exists in the format but hasn't been specified in the
notification (e.g. no icon has been set), the placeholders will simply be
removed from the format.
=item B<alignment> (values: [left/center/right], default: left)
Defines how the text should be aligned within the notification.
=item B<show_age_threshold> (default: -1)
Show age of message if message is older than this time.
See TIME FORMAT for valid times.
Set to -1 to disable.
=item B<word_wrap> (values: [true/false], default: false)
Specifies how very long lines should be handled
If it's set to false, long lines will be truncated an ellipsised.
If it's set to true, long lines will be broken into multiple lines expanding
the notification window height as necessary for them to fit.
=item B<ellipsize> (values: [start/middle/end], default: middle)
If word_wrap is set to false, specifies where truncated lines should be
ellipsized.
=item B<ignore_newline> (values: [true/false], default: false)
If set to true, replace newline characters in notifications with whitespace.
=item B<stack_duplicates> (values: [true/false], default: true)
If set to true, duplicate notifications will be stacked together instead of
being displayed separately.
Two notifications are considered duplicate if the name of the program that sent
it, summary, body, icon and urgency are all identical.
=item B<hide_duplicates_count> (values: [true/false], default: false)
Hide the count of stacked duplicate notifications.
=item B<show_indicators> (values: [true/false], default: true)
Show an indicator if a notification contains actions and/or open-able URLs. See
ACTIONS below for further details.
=item B<icon_position> (values: [left/right/off], default: off)
Defines the position of the icon in the notification window. Setting it to off
disables icons.
=item B<max_icon_size> (default: 0)
Defines the maximum size in pixels for the icons.
If the icon is smaller than the specified value it won't be affected.
If it's larger then it will be scaled down so that the larger axis is equivalent
to the specified size.
Set to 0 to disable icon scaling. (default)
If B<icon_position> is set to off, this setting is ignored.
=item B<icon_path> (default: "/usr/share/icons/gnome/16x16/status/:/usr/share/icons/gnome/16x16/devices/")
Can be set to a colon-separated list of paths to search for icons to use with
notifications.
Dunst doesn't currently do any type of icon lookup outside of these
directories.
=item B<sticky_history> (values: [true/false], default: true)
If set to true, notifications that have been recalled from history will not
time out automatically.
=item B<history_length> (default: 20)
Maximum number of notifications that will be kept in history. After that limit
is reached, older notifications will be deleted once a new one arrives. See
HISTORY.
=item B<dmenu> (default: "/usr/bin/dmenu")
The command that will be run when opening the context menu. Should be either
a dmenu command or a dmenu-compatible menu.
=item B<browser> (default: "/usr/bin/firefox")
The command that will be run when opening a URL. The URL to be opened will be
appended to the end of the value of this setting.
=item B<always_run_script> (values: [true/false] default: true]
Always run rule-defined scripts, even if the notification is suppressed with
format = "". See SCRIPTING.
=item B<title> (default: "Dunst")
Defines the title of notification windows spawned by dunst. (_NET_WM_NAME
property). There should be no need to modify this setting for regular use.
=item B<class> (default: "Dunst")
Defines the class of notification windows spawned by dunst. (First part of
WM_CLASS). There should be no need to modify this setting for regular use.
=item B<startup_notification> (values: [true/false], default: false)
Display a notification on startup. This is usually used for debugging and there
shouldn't be any need to use this option.
=item B<force_xinerama> (values: [true/false], default: false)
Use the Xinerama extension instead of RandR for multi-monitor support. This
setting is provided for compatibility with older nVidia drivers that do not
support RandR and using it on systems that support RandR is highly discouraged.
By enabling this setting dunst will not be able to detect when a monitor is
connected or disconnected which might break follow mode if the screen layout
changes.
=back
=head2 Shortcut section
Keyboard shortcuts are defined in the following format: "Modifier+key" where the
modifier is one of ctrl,mod1,mod2,mod3,mod4 and key is any keyboard key.
=over 4
=item B<close>
B<command line flag>: -key <key>
Specifies the keyboard shortcut for closing a notification.
=item B<close_all>
B<command line flag>: -all_key <key>
Specifies the keyboard shortcut for closing all currently displayed notifications.
=item B<history>
B<command line flag>: -history_key <key>
Specifies the keyboard shortcut for recalling a single notification from history.
=item B<context>
B<command line flag>: -context_key <key>
Specifies the keyboard shortcut that opens the context menu.
=back
=head2 Urgency sections
The urgency sections work in a similar way to rules and can be used to specify
attributes for the different urgency levels of notifications (low, normal,
critical). Currently only the background, foreground, timeout, frame_color and
icon attributes can be modified.
The urgency sections are urgency_low, urgency_normal, urgency_critical for low,
normal and critical urgency respectively.
See the example configuration file for examples.
Additionally, you can override these settings via the following command line
flags:
Please note these flags may be removed in the future. See issue #328 in the bug
tracker for discussions (See REPORTING BUGS).
=over 4
=item B<-li/ni/ci icon>
Defines the icon for low, normal and critical notifications respectively.
Where I<icon> is a path to an image file containing the icon.
=item B<-lf/nf/cf color>
Defines the foreground color for low, normal and critical notifications respectively.
See COLORS for the value format.
=item B<-lb/nb/cb color>
Defines the background color for low, normal and critical notifications respectively.
See COLORS for the value format.
=item B<-lfr/nfr/cfr color>
Defines the frame color for low, normal and critical notifications respectively.
See COLORS for more information
=item B<-lto/nto/cto secs>
Defines the timeout time for low, normal and critical notifications
respectively.
See TIME FORMAT for valid times.
=back
=head1 HISTORY
Dunst saves a number of notifications (specified by B<history_length>) in memory.
These notifications can be recalled (i.e. redesiplayed) by pressing the
B<history_key> (see the shortcuts section), whether these notifications will
time out like if they have been just send depends on the value of the
B<sticky_history> setting.
Past notifications are redisplayed in a first-in-last-out order, meaning that
pressing the history key once will bring up the most recent notification that
had been closed/timed out.
=head1 RULES
Rules allow the conditional modification of notifications. They are defined by
creating a section in the configuration file that has any name that is not
already used internally (i.e. any name other than 'global', 'experimental',
'frame', 'shortcuts', 'urgency_low', 'urgency_normal' and 'urgency_critical').
There are 2 parts in configuring a rule: Defining the filters that control when
a rule should apply and then the actions that should be taken when the rule is
matched.
=over 4
=item B<filtering>
Notifications can be matched for any of the following attributes: appname,
summary, body, icon, category, match_transient and msg_urgency where each is
the respective notification attribute to be matched and 'msg_urgency' is the
urgency of the notification, it is named so to not conflict with trying to
modify the urgency.
To define a matching rule simply assign the specified value to the value that
should be matched, for example:
appname="notify-send"
Matches only messages that were send via notify-send. If multiple filter
expressions are present, all of them have to match for the rule to be applied
(logical AND).
Shell-like globing is supported.
=item B<modifying>
The following attributes can be overridden: timeout, urgency, foreground,
background, new_icon, set_transient, format where, as with the filtering attributes,
each one corresponds to the respective notification attribute to be modified.
As with filtering, to make a rule modify an attribute simply assign it in the
rule definition.
If the format is set to an empty string, the notification will not be
suppressed.
=back
=head2 SCRIPTING
Within rules you can specify a script to be run every time the rule is matched
by assigning the 'script' option to the name of the script to be run.
When the script is called details of the notification that triggered it will be
passed via command line parameters in the following order: appname, summary,
body, icon, urgency.
Where icon is the absolute path to the icon file if there is one and urgency is
one of "LOW", "NORMAL" or "CRITICAL".
If the notification is suppressed, the script will not be run unless
B<always_run_scripts> is set to true.
If '~/' occurs at the beginning of the script parameter, it will get replaced by the
users' home directory. If the value is not an absolute path, the directories in the
PATH variable will be searched for an executable of the same name.
=head1 COLORS
Colors are interpreted as X11 color values. This includes both verbatim
color names such as "Yellow", "Blue", "White", etc as well as #RGB and #RRGGBB
values.
B<NOTE>: '#' is interpreted as a comment, to use it the entire value needs to
be in quotes like so: separator_color="#123456"
=head2 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 ACTIONS
Dunst allows notifiers (i.e.: programs that send the notifications) to specify
actions. Dunst has support for both displaying indicators for these, and
interacting with these actions.
If "show_indicators" is true and a notification has an action, an "(A)" will be
prepended to the notification format. Likewise, an "(U)" is preneded to
notifications with URLs. It is possible to interact with notifications that
have actions regardless of this setting, though it may not be obvious which
notifications HAVE actions.
The "context" keybinding is used to interact with these actions, by showing a
menu of possible actions. This feature requires "dmenu" or a dmenu drop-in
replacement present.
Alternatively, you can invoke an action with a middle click on the notification.
If there is exactly one associated action, or one is marked as default, that one
is invoked. If there are multiple, the context menu is shown. The same applies
to URLs when there are no actions.
=head1 TIME FORMAT
A time can be any decimal integer value suffixed with a time unit. If no unit
given, seconds ("s") is taken as default.
Time units understood by dunst are "ms", "s", "m", "h" and "d".
Example time: "1000ms" "10m"
=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 FILES
$XDG_CONFIG_HOME/dunst/dunstrc
-or-
$HOME/.config/dunst/dunstrc
=head1 AUTHORS
Written by Sascha Kruse <knopwob@googlemail.com>
=head1 REPORTING BUGS
Bugs and suggestions should be reported on GitHub at https://github.com/dunst-project/dunst/issues
=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)

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

@ -0,0 +1,13 @@
[Unit]
Description=Dunst notification daemon
Documentation=man:dunst(1)
PartOf=graphical-session.target
[Service]
Type=dbus
BusName=org.freedesktop.Notifications
ExecStart=##PREFIX##/bin/dunst
[Install]
WantedBy=default.target

@ -0,0 +1,334 @@
#include <glib.h>
#include <libnotify/notify.h>
#include <locale.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <gdk-pixbuf/gdk-pixbuf.h>
static gchar *appname = "dunstify";
static gchar *summary = NULL;
static gchar *body = NULL;
static NotifyUrgency urgency = NOTIFY_URGENCY_NORMAL;
static gchar *urgency_str = NULL;
static gchar **hint_strs = NULL;
static gchar **action_strs = NULL;
static gint timeout = NOTIFY_EXPIRES_DEFAULT;
static gchar *icon = NULL;
static gchar *raw_icon_path = NULL;
static gboolean capabilities = false;
static gboolean serverinfo = false;
static gboolean printid = false;
static guint32 replace_id = 0;
static guint32 close_id = 0;
static gboolean block = false;
static GOptionEntry entries[] =
{
{ "appname", 'a', 0, G_OPTION_ARG_STRING, &appname, "Name of your application", "NAME" },
{ "urgency", 'u', 0, G_OPTION_ARG_STRING, &urgency_str, "The urgency of this notification", "URG" },
{ "hints", 'h', 0, G_OPTION_ARG_STRING_ARRAY, &hint_strs, "User specified hints", "HINT" },
{ "action", 'A', 0, G_OPTION_ARG_STRING_ARRAY, &action_strs, "Actions the user can invoke", "ACTION" },
{ "timeout", 't', 0, G_OPTION_ARG_INT, &timeout, "The time until the notification expires", "TIMEOUT" },
{ "icon", 'i', 0, G_OPTION_ARG_STRING, &icon, "An Icon that should be displayed with the notification", "ICON" },
{ "raw_icon", 'I', 0, G_OPTION_ARG_STRING, &raw_icon_path, "Path to the icon to be sent as raw image data", "RAW_ICON"},
{ "capabilities", 'c', 0, G_OPTION_ARG_NONE, &capabilities, "Print the server capabilities and exit", NULL},
{ "serverinfo", 's', 0, G_OPTION_ARG_NONE, &serverinfo, "Print server information and exit", NULL},
{ "printid", 'p', 0, G_OPTION_ARG_NONE, &printid, "Print id, which can be used to update/replace this notification", NULL},
{ "replace", 'r', 0, G_OPTION_ARG_INT, &replace_id, "Set id of this notification.", "ID"},
{ "close", 'C', 0, G_OPTION_ARG_INT, &close_id, "Set id of this notification.", "ID"},
{ "block", 'b', 0, G_OPTION_ARG_NONE, &block, "Block until notification is closed and print close reason", NULL},
{ NULL }
};
void die(int exit_value)
{
if (notify_is_initted())
notify_uninit();
exit(exit_value);
}
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", (char *)iter->data);
}
}
}
void print_serverinfo(void)
{
char *name;
char *vendor;
char *version;
char *spec_version;
if (!notify_get_server_info(&name, &vendor, &version, &spec_version)) {
g_printerr("Unable to get server information");
exit(1);
}
g_print("name:%s\nvendor:%s\nversion:%s\nspec_version:%s\n", name,
vendor,
version,
spec_version);
}
void parse_commandline(int argc, char *argv[])
{
GError *error = NULL;
GOptionContext *context;
context = g_option_context_new("- Dunstify");
g_option_context_add_main_entries(context, entries, NULL);
if (!g_option_context_parse(context, &argc, &argv, &error)){
g_printerr("Invalid commandline: %s\n", error->message);
exit(1);
}
g_option_context_free(context);
if (capabilities) {
print_capabilities();
die(0);
}
if (serverinfo) {
print_serverinfo();
die(0);
}
if (argc < 2 && close_id < 1) {
g_printerr("I need at least a summary\n");
die(1);
} else if (argc < 2) {
summary = g_strdup("These are not the summaries you are looking for");
} else {
summary = g_strdup(argv[1]);
}
if (argc > 2) {
body = g_strdup(argv[2]);
}
if (urgency_str) {
switch (urgency_str[0]) {
case 'l':
case 'L':
case '0':
urgency = NOTIFY_URGENCY_LOW;
break;
case 'n':
case 'N':
case '1':
urgency = NOTIFY_URGENCY_NORMAL;
break;
case 'c':
case 'C':
case '2':
urgency = NOTIFY_URGENCY_CRITICAL;
break;
default:
g_printerr("Unknown urgency: %s\n", urgency_str);
g_printerr("Assuming normal urgency\n");
break;
}
}
}
typedef struct _NotifyNotificationPrivate
{
guint32 id;
char *app_name;
char *summary;
char *body;
/* NULL to use icon data. Anything else to have server lookup icon */
char *icon_name;
/*
* -1 = use server default
* 0 = never timeout
* > 0 = Number of milliseconds before we timeout
*/
gint timeout;
GSList *actions;
GHashTable *action_map;
GHashTable *hints;
gboolean has_nondefault_actions;
gboolean updates_pending;
gulong proxy_signal_handler;
gint closed_reason;
} knickers;
int get_id(NotifyNotification *n)
{
knickers *kn = n->priv;
/* I'm sorry for taking a peek */
return kn->id;
}
void put_id(NotifyNotification *n, guint32 id)
{
knickers *kn = n->priv;
/* And know I'm putting stuff into
* your knickers. I'm sorry.
* I'm so sorry.
* */
kn->id = id;
}
void actioned(NotifyNotification *n, char *a, gpointer foo)
{
notify_notification_close(n, NULL);
g_print("%s\n", a);
die(0);
}
void closed(NotifyNotification *n, gpointer foo)
{
g_print("%d\n", notify_notification_get_closed_reason(n));
die(0);
}
void add_action(NotifyNotification *n, char *str)
{
char *action = str;
char *label = strchr(str, ',');
if (!label || *(label+1) == '\0') {
g_printerr("Malformed action. Excpected \"action,label\", got \"%s\"", str);
return;
}
*label = '\0';
label++;
notify_notification_add_action(n, action, label, actioned, NULL, NULL);
}
void add_hint(NotifyNotification *n, char *str)
{
char *type = 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 = strchr(name, ':');
if (!value || *(value+1) == '\0') {
g_printerr("Malformed hint. Expected \"type:name:value\", got \"%s\"", str);
return;
}
*value = '\0';
value++;
if (strcmp(type, "int") == 0)
notify_notification_set_hint_int32(n, name, atoi(value));
else if (strcmp(type, "double") == 0)
notify_notification_set_hint_double(n, name, atof(value));
else if (strcmp(type, "string") == 0)
notify_notification_set_hint_string(n, name, value);
else if (strcmp(type, "byte") == 0) {
gint h_byte = g_ascii_strtoull(value, NULL, 10);
if (h_byte < 0 || h_byte > 0xFF)
g_printerr("Not a byte: \"%s\"", value);
else
notify_notification_set_hint_byte(n, name, (guchar) h_byte);
} else
g_printerr("Malformed hint. Expected a type of int, double, string or byte, got %s\n", type);
}
int main(int argc, char *argv[])
{
setlocale(LC_ALL, "");
#if !GLIB_CHECK_VERSION(2,35,0)
g_type_init();
#endif
parse_commandline(argc, argv);
if (!notify_init(appname)) {
g_printerr("Unable to initialize libnotify\n");
die(1);
}
NotifyNotification *n;
n = notify_notification_new(summary, body, icon);
notify_notification_set_timeout(n, timeout);
notify_notification_set_urgency(n, urgency);
GError *err = NULL;
if (raw_icon_path) {
GdkPixbuf *raw_icon = gdk_pixbuf_new_from_file(raw_icon_path, &err);
if(err) {
g_printerr("Unable to get raw icon: %s\n", err->message);
die(1);
}
notify_notification_set_image_from_pixbuf(n, raw_icon);
}
if (close_id > 0) {
put_id(n, close_id);
notify_notification_close(n, &err);
if (err) {
g_printerr("Unable to close notification: %s\n", err->message);
die(1);
}
die(0);
}
if (replace_id > 0) {
put_id(n, replace_id);
}
GMainLoop *l = NULL;
if (block || action_strs) {
l = g_main_loop_new(NULL, false);
g_signal_connect(n, "closed", G_CALLBACK(closed), NULL);
}
if (action_strs)
for (int i = 0; action_strs[i]; i++) {
add_action(n, action_strs[i]);
}
if (hint_strs)
for (int i = 0; hint_strs[i]; i++) {
add_hint(n, hint_strs[i]);
}
notify_notification_show(n, &err);
if (err) {
g_printerr("Unable to send notification: %s\n", err->message);
die(1);
}
if (printid)
g_print("%d\n", get_id(n));
if (block || action_strs)
g_main_loop_run(l);
g_object_unref(G_OBJECT (n));
die(0);
}
/* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */

@ -0,0 +1,326 @@
[global]
### Display ###
# Which monitor should the notifications be displayed on.
monitor = 0
# Display notification on focused monitor. Possible modes are:
# mouse: follow mouse pointer
# keyboard: follow window with keyboard focus
# none: don't follow anything
#
# "keyboard" needs a window manager that exports the
# _NET_ACTIVE_WINDOW property.
# This should be the case for almost all modern window managers.
#
# If this option is set to mouse or keyboard, the monitor option
# will be ignored.
follow = mouse
# The geometry of the window:
# [{width}]x{height}[+/-{x}+/-{y}]
# The geometry of the message window.
# The height is measured in number of notifications 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.
# The width can be negative. In this case the actual width is the
# screen width minus the width defined in within the geometry option.
geometry = "300x5-30+20"
# Show how many messages are currently hidden (because of geometry).
indicate_hidden = yes
# Shrink window if it's smaller than the width. Will be ignored if
# width is 0.
shrink = no
# The transparency of the window. Range: [0; 100].
# This option will only work if a compositing window manager is
# present (e.g. xcompmgr, compiz, etc.).
transparency = 0
# The height of the entire notification. If the height is smaller
# than the font height and padding combined, it will be raised
# to the font height and padding.
notification_height = 0
# Draw a line of "separator_height" pixel height between two
# notifications.
# Set to 0 to disable.
separator_height = 2
# Padding between text and separator.
padding = 8
# Horizontal padding.
horizontal_padding = 8
# Defines width in pixels of frame around the notification window.
# Set to 0 to disable.
frame_width = 3
# Defines color of the frame around the notification window.
frame_color = "#aaaaaa"
# Define a color for the separator.
# possible values are:
# * auto: dunst tries to find a color fitting to the background;
# * foreground: use the same color as the foreground;
# * frame: use the same color as the frame;
# * anything else will be interpreted as a X color.
separator_color = frame
# Sort messages by urgency.
sort = yes
# Don't remove messages, if the user is idle (no mouse or keyboard input)
# for longer than idle_threshold seconds.
# Set to 0 to disable.
# Transient notifications ignore this setting.
idle_threshold = 120
### Text ###
font = Monospace 8
# The spacing between lines. If the height is smaller than the
# font height, it will get raised to the font height.
line_height = 0
# Possible values are:
# full: Allow a small subset of html markup in notifications:
# <b>bold</b>
# <i>italic</i>
# <s>strikethrough</s>
# <u>underline</u>
#
# For a complete reference see
# <http://developer.gnome.org/pango/stable/PangoMarkupFormat.html>.
#
# strip: This setting is provided for compatibility with some broken
# clients that send markup even though it's not enabled on the
# server. Dunst will try to strip the markup but the parsing is
# simplistic so using this option outside of matching rules for
# specific applications *IS GREATLY DISCOURAGED*.
#
# no: Disable markup parsing, incoming notifications will be treated as
# plain text. Dunst will not advertise that it has the body-markup
# capability if this is set as a global setting.
#
# It's important to note that markup inside the format option will be parsed
# regardless of what this is set to.
markup = full
# The format of the message. Possible variables are:
# %a appname
# %s summary
# %b body
# %i iconname (including its path)
# %I iconname (without its path)
# %p progress value if set ([ 0%] to [100%]) or nothing
# %n progress value if set without any extra characters
# %% Literal %
# Markup is allowed
format = "<b>%s</b>\n%b"
# Alignment of message text.
# Possible values are "left", "center" and "right".
alignment = left
# Show age of message if message is older than show_age_threshold
# seconds.
# Set to -1 to disable.
show_age_threshold = 60
# Split notifications into multiple lines if they don't fit into
# geometry.
word_wrap = yes
# When word_wrap is set to no, specify where to ellipsize long lines.
# Possible values are "start", "middle" and "end".
ellipsize = middle
# Ignore newlines '\n' in notifications.
ignore_newline = no
# Merge multiple notifications with the same content
stack_duplicates = true
# Hide the count of merged notifications with the same content
hide_duplicate_count = false
# Display indicators for URLs (U) and actions (A).
show_indicators = yes
### Icons ###
# 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_path = /usr/share/icons/gnome/16x16/status/:/usr/share/icons/gnome/16x16/devices/
### History ###
# Should a notification popped up from history be sticky or timeout
# as if it would normally do.
sticky_history = yes
# Maximum amount of notifications kept in history
history_length = 20
### Misc/Advanced ###
# dmenu path.
dmenu = /usr/bin/dmenu -p dunst:
# Browser for opening urls in context menu.
browser = /usr/bin/firefox -new-tab
# Always run rule-defined scripts, even if the notification is suppressed
always_run_script = true
# Define the title of the windows spawned by dunst
title = Dunst
# Define the class of the windows spawned by dunst
class = Dunst
# Print a notification on startup.
# This is mainly for error detection, since dbus (re-)starts dunst
# automatically after a crash.
startup_notification = false
### Legacy
# Use the Xinerama extension instead of RandR for multi-monitor support.
# This setting is provided for compatibility with older nVidia drivers that
# do not support RandR and using it on systems that support RandR is highly
# discouraged.
#
# By enabling this setting dunst will not be able to detect when a monitor
# is connected or disconnected which might break follow mode if the screen
# layout changes.
force_xinerama = false
# Experimental features that may or may not work correctly. Do not expect them
# to have a consistent behaviour across releases.
[experimental]
# Calculate the dpi to use on a per-monitor basis.
# If this setting is enabled the Xft.dpi value will be ignored and instead
# dunst will attempt to calculate an appropriate dpi value for each monitor
# using the resolution and physical size. This might be useful in setups
# where there are multiple screens with very different dpi values.
per_monitor_dpi = false
[shortcuts]
# Shortcuts are specified as [modifier+][modifier+]...key
# Available modifiers are "ctrl", "mod1" (the alt-key), "mod2",
# "mod3" and "mod4" (windows-key).
# Xev might be helpful to find names for keys.
# Close notification.
close = ctrl+space
# Close all notifications.
close_all = ctrl+shift+space
# Redisplay last message(s).
# On the US keyboard layout "grave" is normally above TAB and left
# of "1". Make sure this key actually exists on your keyboard layout,
# e.g. check output of 'xmodmap -pke'
history = ctrl+grave
# Context menu.
context = ctrl+shift+period
[urgency_low]
# IMPORTANT: colors have to be defined in quotation marks.
# Otherwise the "#" and following would be interpreted as a comment.
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.
# Messages can be matched by "appname", "summary", "body", "icon", "category",
# "msg_urgency" and you can override the "timeout", "urgency", "foreground",
# "background", "new_icon" and "format".
# Shell-like globbing will get expanded.
#
# SCRIPTING
# You can specify a script that gets run when the rule matches by
# setting the "script" option.
# The script will be called as follows:
# script appname summary body icon urgency
# where urgency can be "LOW", "NORMAL" or "CRITICAL".
#
# NOTE: if you don't want a notification to be displayed, set the format
# to "".
# NOTE: It might be helpful to run dunst -print in a terminal in order
# to find fitting options for rules.
#[espeak]
# summary = "*"
# script = dunst_espeak.sh
#[script-test]
# summary = "*script*"
# script = dunst_test.sh
#[ignore]
# # This notification will not be displayed
# summary = "foobar"
# format = ""
#[history-ignore]
# # This notification will not be saved in history
# summary = "foobar"
# history_ignore = yes
#[signed_on]
# appname = Pidgin
# summary = "*signed on*"
# urgency = low
#
#[signed_off]
# appname = Pidgin
# summary = *signed off*
# urgency = low
#
#[says]
# appname = Pidgin
# summary = *says*
# urgency = critical
#
#[twitter]
# appname = Pidgin
# summary = *twitter.com*
# urgency = normal
#
# vim: ft=cfg

@ -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: */

@ -0,0 +1,4 @@
[D-BUS Service]
Name=org.freedesktop.Notifications
Exec=##PREFIX##/bin/dunst
SystemdService=dunst.service

@ -0,0 +1,492 @@
/* copyright 2013 Sascha Kruse and contributors (see LICENSE for licensing information) */
#include "dbus.h"
#include <gio/gio.h>
#include <glib.h>
#include <stdio.h>
#include <stdlib.h>
#include "dunst.h"
#include "notification.h"
#include "queues.h"
#include "settings.h"
#include "utils.h"
GDBusConnection *dbus_conn;
static GDBusNodeInfo *introspection_data = NULL;
static const char *introspection_xml =
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
"<node name=\"/org/freedesktop/Notifications\">"
" <interface name=\"org.freedesktop.Notifications\">"
" <method name=\"GetCapabilities\">"
" <arg direction=\"out\" name=\"capabilities\" type=\"as\"/>"
" </method>"
" <method name=\"Notify\">"
" <arg direction=\"in\" name=\"app_name\" type=\"s\"/>"
" <arg direction=\"in\" name=\"replaces_id\" type=\"u\"/>"
" <arg direction=\"in\" name=\"app_icon\" type=\"s\"/>"
" <arg direction=\"in\" name=\"summary\" type=\"s\"/>"
" <arg direction=\"in\" name=\"body\" type=\"s\"/>"
" <arg direction=\"in\" name=\"actions\" type=\"as\"/>"
" <arg direction=\"in\" name=\"hints\" type=\"a{sv}\"/>"
" <arg direction=\"in\" name=\"expire_timeout\" type=\"i\"/>"
" <arg direction=\"out\" name=\"id\" type=\"u\"/>"
" </method>"
" <method name=\"CloseNotification\">"
" <arg direction=\"in\" name=\"id\" type=\"u\"/>"
" </method>"
" <method name=\"GetServerInformation\">"
" <arg direction=\"out\" name=\"name\" type=\"s\"/>"
" <arg direction=\"out\" name=\"vendor\" type=\"s\"/>"
" <arg direction=\"out\" name=\"version\" type=\"s\"/>"
" <arg direction=\"out\" name=\"spec_version\" type=\"s\"/>"
" </method>"
" <signal name=\"NotificationClosed\">"
" <arg name=\"id\" type=\"u\"/>"
" <arg name=\"reason\" type=\"u\"/>"
" </signal>"
" <signal name=\"ActionInvoked\">"
" <arg name=\"id\" type=\"u\"/>"
" <arg name=\"action_key\" type=\"s\"/>"
" </signal>"
" </interface>"
"</node>";
static void on_get_capabilities(GDBusConnection *connection,
const gchar *sender,
const GVariant *parameters,
GDBusMethodInvocation *invocation);
static void on_notify(GDBusConnection *connection,
const gchar *sender,
GVariant *parameters,
GDBusMethodInvocation *invocation);
static void on_close_notification(GDBusConnection *connection,
const gchar *sender,
GVariant *parameters,
GDBusMethodInvocation *invocation);
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,
const gchar *object_path,
const gchar *interface_name,
const gchar *method_name,
GVariant *parameters,
GDBusMethodInvocation *invocation,
gpointer user_data)
{
if (g_strcmp0(method_name, "GetCapabilities") == 0) {
on_get_capabilities(connection, sender, parameters, invocation);
} else if (g_strcmp0(method_name, "Notify") == 0) {
on_notify(connection, sender, parameters, invocation);
} else if (g_strcmp0(method_name, "CloseNotification") == 0) {
on_close_notification(connection, sender, parameters, invocation);
} else if (g_strcmp0(method_name, "GetServerInformation") == 0) {
on_get_server_information(connection, sender, parameters, invocation);
} else {
fprintf(stderr, "WARNING: sender: %s; unknown method_name: %s\n", sender,
method_name);
}
}
static void on_get_capabilities(GDBusConnection *connection,
const gchar *sender,
const GVariant *parameters,
GDBusMethodInvocation *invocation)
{
GVariantBuilder *builder;
GVariant *value;
builder = g_variant_builder_new(G_VARIANT_TYPE("as"));
g_variant_builder_add(builder, "s", "actions");
g_variant_builder_add(builder, "s", "body");
g_variant_builder_add(builder, "s", "body-hyperlinks");
if (settings.markup != MARKUP_NO)
g_variant_builder_add(builder, "s", "body-markup");
value = g_variant_new("(as)", builder);
g_variant_builder_unref(builder);
g_dbus_method_invocation_return_value(invocation, value);
g_dbus_connection_flush(connection, NULL, NULL, NULL);
}
static void on_notify(GDBusConnection *connection,
const gchar *sender,
GVariant *parameters,
GDBusMethodInvocation *invocation)
{
gchar *appname = NULL;
guint replaces_id = 0;
gchar *icon = NULL;
gchar *summary = NULL;
gchar *body = NULL;
Actions *actions = g_malloc0(sizeof(Actions));
gint timeout = -1;
/* hints */
gint urgency = 1;
gint progress = -1;
gboolean transient = 0;
gchar *fgcolor = NULL;
gchar *bgcolor = NULL;
gchar *category = NULL;
RawImage *raw_icon = NULL;
{
GVariantIter *iter = g_variant_iter_new(parameters);
GVariant *content;
GVariant *dict_value;
int idx = 0;
while ((content = g_variant_iter_next_value(iter))) {
switch (idx) {
case 0:
if (g_variant_is_of_type(content, G_VARIANT_TYPE_STRING))
appname = g_variant_dup_string(content, NULL);
break;
case 1:
if (g_variant_is_of_type(content, G_VARIANT_TYPE_UINT32))
replaces_id = g_variant_get_uint32(content);
break;
case 2:
if (g_variant_is_of_type(content, G_VARIANT_TYPE_STRING))
icon = g_variant_dup_string(content, NULL);
break;
case 3:
if (g_variant_is_of_type(content, G_VARIANT_TYPE_STRING))
summary = g_variant_dup_string(content, NULL);
break;
case 4:
if (g_variant_is_of_type(content, G_VARIANT_TYPE_STRING))
body = g_variant_dup_string(content, NULL);
break;
case 5:
if (g_variant_is_of_type(content, G_VARIANT_TYPE_STRING_ARRAY))
actions->actions = g_variant_dup_strv(content, &(actions->count));
break;
case 6:
if (g_variant_is_of_type(content, G_VARIANT_TYPE_DICTIONARY)) {
dict_value = g_variant_lookup_value(content, "urgency", G_VARIANT_TYPE_BYTE);
if (dict_value) {
urgency = g_variant_get_byte(dict_value);
g_variant_unref(dict_value);
}
dict_value = g_variant_lookup_value(content, "fgcolor", G_VARIANT_TYPE_STRING);
if (dict_value) {
fgcolor = g_variant_dup_string(dict_value, NULL);
g_variant_unref(dict_value);
}
dict_value = g_variant_lookup_value(content, "bgcolor", G_VARIANT_TYPE_STRING);
if (dict_value) {
bgcolor = g_variant_dup_string(dict_value, NULL);
g_variant_unref(dict_value);
}
dict_value = g_variant_lookup_value(content, "category", G_VARIANT_TYPE_STRING);
if (dict_value) {
category = g_variant_dup_string(dict_value, NULL);
g_variant_unref(dict_value);
}
dict_value = g_variant_lookup_value(content, "image-path", G_VARIANT_TYPE_STRING);
if (dict_value) {
g_free(icon);
icon = g_variant_dup_string(dict_value, NULL);
g_variant_unref(dict_value);
}
dict_value = g_variant_lookup_value(content, "image-data", G_VARIANT_TYPE("(iiibiiay)"));
if (!dict_value)
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);
g_variant_unref(dict_value);
}
/* Check for transient hints
*
* According to the spec, the transient hint should be boolean.
* But notify-send does not support hints of type 'boolean'.
* So let's check for int and boolean until notify-send is fixed.
*/
if((dict_value = g_variant_lookup_value(content, "transient", G_VARIANT_TYPE_BOOLEAN))) {
transient = g_variant_get_boolean(dict_value);
g_variant_unref(dict_value);
} else if((dict_value = g_variant_lookup_value(content, "transient", G_VARIANT_TYPE_UINT32))) {
transient = g_variant_get_uint32(dict_value) > 0;
g_variant_unref(dict_value);
} else if((dict_value = g_variant_lookup_value(content, "transient", G_VARIANT_TYPE_INT32))) {
transient = g_variant_get_int32(dict_value) > 0;
g_variant_unref(dict_value);
}
if((dict_value = g_variant_lookup_value(content, "value", G_VARIANT_TYPE_INT32))) {
progress = g_variant_get_int32(dict_value);
g_variant_unref(dict_value);
} else if((dict_value = g_variant_lookup_value(content, "value", G_VARIANT_TYPE_UINT32))) {
progress = g_variant_get_uint32(dict_value);
g_variant_unref(dict_value);
}
}
break;
case 7:
if (g_variant_is_of_type(content, G_VARIANT_TYPE_INT32))
timeout = g_variant_get_int32(content);
break;
}
g_variant_unref(content);
idx++;
}
g_variant_iter_free(iter);
}
fflush(stdout);
notification *n = notification_create();
n->appname = appname;
n->summary = summary;
n->body = body;
n->icon = icon;
n->raw_icon = raw_icon;
n->timeout = timeout < 0 ? -1 : timeout * 1000;
n->markup = settings.markup;
n->progress = (progress < 0 || progress > 100) ? -1 : progress;
n->urgency = urgency;
n->category = category;
n->dbus_client = g_strdup(sender);
n->transient = transient;
if (actions->count < 1) {
actions_free(actions);
actions = NULL;
}
n->actions = actions;
for (int i = 0; i < ColLast; i++) {
n->color_strings[i] = NULL;
}
n->color_strings[ColFG] = fgcolor;
n->color_strings[ColBG] = bgcolor;
notification_init(n);
int id = queues_notification_insert(n, replaces_id);
GVariant *reply = g_variant_new("(u)", id);
g_dbus_method_invocation_return_value(invocation, reply);
g_dbus_connection_flush(connection, NULL, NULL, NULL);
// The message got discarded
if (id == 0) {
notification_closed(n, 2);
notification_free(n);
}
wake_up();
}
static void on_close_notification(GDBusConnection *connection,
const gchar *sender,
GVariant *parameters,
GDBusMethodInvocation *invocation)
{
guint32 id;
g_variant_get(parameters, "(u)", &id);
queues_notification_close_id(id, REASON_SIG);
wake_up();
g_dbus_method_invocation_return_value(invocation, NULL);
g_dbus_connection_flush(connection, NULL, NULL, NULL);
}
static void on_get_server_information(GDBusConnection *connection,
const gchar *sender,
const GVariant *parameters,
GDBusMethodInvocation *invocation)
{
GVariant *value;
value = g_variant_new("(ssss)", "dunst", "knopwob", VERSION, "1.2");
g_dbus_method_invocation_return_value(invocation, value);
g_dbus_connection_flush(connection, NULL, NULL, NULL);
}
void notification_closed(notification *n, enum reason reason)
{
if (reason < REASON_MIN || REASON_MAX < reason) {
fprintf(stderr, "ERROR: Closing notification with reason '%d' not supported. "
"Closing it with reason '%d'.\n", reason, REASON_UNDEF);
reason = REASON_UNDEF;
}
if (!dbus_conn) {
fprintf(stderr, "ERROR: Tried to close notification but dbus connection not set!\n");
return;
}
GVariant *body = g_variant_new("(uu)", n->id, reason);
GError *err = NULL;
g_dbus_connection_emit_signal(dbus_conn,
n->dbus_client,
"/org/freedesktop/Notifications",
"org.freedesktop.Notifications",
"NotificationClosed",
body,
&err);
if (err) {
fprintf(stderr, "Unable to close notification: %s\n", err->message);
g_error_free(err);
}
}
void action_invoked(notification *n, const char *identifier)
{
GVariant *body = g_variant_new("(us)", n->id, identifier);
GError *err = NULL;
g_dbus_connection_emit_signal(dbus_conn,
n->dbus_client,
"/org/freedesktop/Notifications",
"org.freedesktop.Notifications",
"ActionInvoked",
body,
&err);
if (err) {
fprintf(stderr, "Unable to invoke action: %s\n", err->message);
g_error_free(err);
}
}
static const GDBusInterfaceVTable interface_vtable = {
handle_method_call
};
static void on_bus_acquired(GDBusConnection *connection,
const gchar *name,
gpointer user_data)
{
guint registration_id;
GError *err = NULL;
registration_id = g_dbus_connection_register_object(connection,
"/org/freedesktop/Notifications",
introspection_data->interfaces[0],
&interface_vtable,
NULL,
NULL,
&err);
if (registration_id == 0) {
fprintf(stderr, "Unable to register dbus connection: %s\n", err->message);
exit(1);
}
}
static void on_name_acquired(GDBusConnection *connection,
const gchar *name,
gpointer user_data)
{
dbus_conn = connection;
}
static void on_name_lost(GDBusConnection *connection,
const gchar *name,
gpointer user_data)
{
fprintf(stderr, "Name Lost. Is Another notification daemon running?\n");
exit(1);
}
static RawImage *get_raw_image_from_data_hint(GVariant *icon_data)
{
RawImage *image = g_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));
g_free(image);
g_variant_unref(data_variant);
return NULL;
}
image->data = (guchar *) g_memdup(g_variant_get_data(data_variant),
g_variant_get_size(data_variant));
g_variant_unref(data_variant);
return image;
}
int initdbus(void)
{
guint owner_id;
#if !GLIB_CHECK_VERSION(2,35,0)
g_type_init();
#endif
introspection_data = g_dbus_node_info_new_for_xml(introspection_xml,
NULL);
owner_id = g_bus_own_name(G_BUS_TYPE_SESSION,
"org.freedesktop.Notifications",
G_BUS_NAME_OWNER_FLAGS_NONE,
on_bus_acquired,
on_name_acquired,
on_name_lost,
NULL,
NULL);
return owner_id;
}
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 tabstop=8 shiftwidth=8 expandtab textwidth=0: */

@ -0,0 +1,24 @@
/* copyright 2013 Sascha Kruse and contributors (see LICENSE for licensing information) */
#ifndef DUNST_DBUS_H
#define DUNST_DBUS_H
#include "notification.h"
enum reason {
REASON_MIN = 1,
REASON_TIME = 1,
REASON_USER = 2,
REASON_SIG = 3,
REASON_UNDEF = 4,
REASON_MAX = 4,
};
int initdbus(void);
void dbus_tear_down(int id);
/* void dbus_poll(int timeout); */
void notification_closed(notification *n, enum reason reason);
void action_invoked(notification *n, const char *identifier);
#endif
/* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */

@ -0,0 +1,232 @@
/* copyright 2012 - 2013 Sascha Kruse and contributors (see LICENSE for licensing information) */
#define XLIB_ILLEGAL_ACCESS
#include "dunst.h"
#include <X11/Xlib.h>
#include <glib-unix.h>
#include <glib.h>
#include <signal.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include "dbus.h"
#include "menu.h"
#include "notification.h"
#include "option_parser.h"
#include "queues.h"
#include "settings.h"
#include "x11/screen.h"
#include "x11/x.h"
#ifndef VERSION
#define VERSION "version info needed"
#endif
#define MSG 1
#define INFO 2
#define DEBUG 3
typedef struct _x11_source {
GSource source;
Display *dpy;
Window w;
} x11_source_t;
/* index of colors fit to urgency level */
GMainLoop *mainloop = NULL;
GSList *rules = NULL;
/* misc funtions */
void wake_up(void)
{
run(NULL);
}
gboolean run(void *data)
{
queues_check_timeouts(x_is_idle());
queues_update();
static int timeout_cnt = 0;
static gint64 next_timeout = 0;
if (data && timeout_cnt > 0) {
timeout_cnt--;
}
if (queues_length_displayed() > 0 && !xctx.visible) {
x_win_show();
}
if (xctx.visible && queues_length_displayed() == 0) {
x_win_hide();
}
if (xctx.visible) {
x_win_draw();
}
if (xctx.visible) {
gint64 now = g_get_monotonic_time();
gint64 sleep = queues_get_next_datachange(now);
gint64 timeout_at = now + sleep;
if (sleep >= 0) {
if (timeout_cnt == 0 || timeout_at < next_timeout) {
g_timeout_add(sleep/1000, run, mainloop);
next_timeout = timeout_at;
timeout_cnt++;
}
}
}
/* always return false to delete timers */
return false;
}
gboolean pause_signal(gpointer data)
{
queues_pause_on();
wake_up();
return G_SOURCE_CONTINUE;
}
gboolean unpause_signal(gpointer data)
{
queues_pause_off();
wake_up();
return G_SOURCE_CONTINUE;
}
gboolean quit_signal(gpointer data)
{
g_main_loop_quit(mainloop);
return G_SOURCE_CONTINUE;
}
static void teardown(void)
{
regex_teardown();
teardown_queues();
x_free();
}
int dunst_main(int argc, char *argv[])
{
queues_init();
cmdline_load(argc, argv);
if (cmdline_get_bool("-v/-version", false, "Print version")
|| cmdline_get_bool("--version", false, "Print version")) {
print_version();
}
char *cmdline_config_path;
cmdline_config_path =
cmdline_get_string("-conf/-config", NULL,
"Path to configuration file");
load_settings(cmdline_config_path);
if (cmdline_get_bool("-h/-help", false, "Print help")
|| cmdline_get_bool("--help", false, "Print help")) {
usage(EXIT_SUCCESS);
}
int owner_id = initdbus();
x_setup();
if (settings.startup_notification) {
notification *n = notification_create();
n->appname = g_strdup("dunst");
n->summary = g_strdup("startup");
n->body = g_strdup("dunst is up and running");
n->progress = -1;
n->timeout = 10 * G_USEC_PER_SEC;
n->markup = MARKUP_NO;
n->urgency = URG_LOW;
notification_init(n);
queues_notification_insert(n, 0);
// we do not call wakeup now, wake_up does not work here yet
}
mainloop = g_main_loop_new(NULL, FALSE);
GPollFD dpy_pollfd = { xctx.dpy->fd,
G_IO_IN | G_IO_HUP | G_IO_ERR, 0
};
GSourceFuncs x11_source_funcs = {
x_mainloop_fd_prepare,
x_mainloop_fd_check,
x_mainloop_fd_dispatch,
NULL,
NULL,
NULL
};
GSource *x11_source =
g_source_new(&x11_source_funcs, sizeof(x11_source_t));
((x11_source_t *) x11_source)->dpy = xctx.dpy;
((x11_source_t *) x11_source)->w = xctx.win;
g_source_add_poll(x11_source, &dpy_pollfd);
g_source_attach(x11_source, 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;
}
void usage(int exit_status)
{
puts("usage:\n");
const char *us = cmdline_create_usage();
puts(us);
exit(exit_status);
}
void print_version(void)
{
printf
("Dunst - A customizable and lightweight notification-daemon %s\n",
VERSION);
exit(EXIT_SUCCESS);
}
/* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */

@ -0,0 +1,37 @@
/* copyright 2013 Sascha Kruse and contributors (see LICENSE for licensing information) */
#ifndef DUNST_DUNST_H
#define DUNST_DUNST_H
#include <glib.h>
#include <stdbool.h>
#include <stdio.h>
#include "notification.h"
#define PERR(msg, errnum) printf("(%d) %s : %s\n", __LINE__, (msg), (strerror(errnum)))
#define ColLast 3
#define ColFrame 2
#define ColFG 1
#define ColBG 0
extern GSList *rules;
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 usage(int exit_status);
void print_version(void);
char *extract_urls(const char *str);
void context_menu(void);
void wake_up(void);
void pause_signal_handler(int sig);
#endif
/* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */

@ -0,0 +1,109 @@
/* copyright 2013 Sascha Kruse and contributors (see LICENSE for licensing information) */
#include "markup.h"
#include <assert.h>
#include <stdbool.h>
#include "settings.h"
#include "utils.h"
static char *markup_quote(char *str)
{
assert(str != NULL);
str = string_replace_all("&", "&amp;", str);
str = string_replace_all("\"", "&quot;", str);
str = string_replace_all("'", "&apos;", str);
str = string_replace_all("<", "&lt;", str);
str = string_replace_all(">", "&gt;", str);
return str;
}
static char *markup_unquote(char *str)
{
assert(str != NULL);
str = string_replace_all("&quot;", "\"", str);
str = string_replace_all("&apos;", "'", str);
str = string_replace_all("&lt;", "<", str);
str = string_replace_all("&gt;", ">", str);
str = string_replace_all("&amp;", "&", str);
return str;
}
static char *markup_br2nl(char *str)
{
assert(str != NULL);
str = string_replace_all("<br>", "\n", str);
str = string_replace_all("<br/>", "\n", str);
str = string_replace_all("<br />", "\n", str);
return str;
}
/*
* Strip any markup from text; turn it in to plain text.
*
* For well-formed markup, the following two commands should be
* roughly equivalent:
*
* out = markup_strip(in);
* pango_parse_markup(in, -1, 0, NULL, &out, NULL, NULL);
*
* However, `pango_parse_markup()` balks at invalid markup;
* `markup_strip()` shouldn't care if there is invalid markup.
*/
char *markup_strip(char *str)
{
if (str == NULL) {
return NULL;
}
/* strip all tags */
string_strip_delimited(str, '<', '>');
/* unquote the remainder */
str = markup_unquote(str);
return str;
}
/*
* Transform the string in accordance with `markup_mode` and
* `settings.ignore_newline`
*/
char *markup_transform(char *str, enum markup_mode markup_mode)
{
if (str == NULL) {
return NULL;
}
switch (markup_mode) {
case MARKUP_NULL:
/* `assert(false)`, but with a meaningful error message */
assert(markup_mode != MARKUP_NULL);
break;
case MARKUP_NO:
str = markup_quote(str);
break;
case MARKUP_STRIP:
str = markup_br2nl(str);
str = markup_strip(str);
str = markup_quote(str);
break;
case MARKUP_FULL:
str = markup_br2nl(str);
break;
}
if (settings.ignore_newline) {
str = string_replace_all("\n", " ", str);
}
return str;
}
/* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */

@ -0,0 +1,11 @@
/* copyright 2013 Sascha Kruse and contributors (see LICENSE for licensing information) */
#ifndef DUNST_MARKUP_H
#define DUNST_MARKUP_H
#include "settings.h"
char *markup_strip(char *str);
char *markup_transform(char *str, enum markup_mode markup_mode);
#endif
/* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */

@ -0,0 +1,262 @@
/* copyright 2013 Sascha Kruse and contributors (see LICENSE for licensing information) */
#include "menu.h"
#include <errno.h>
#include <glib.h>
#include <regex.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/wait.h>
#include <unistd.h>
#include "dbus.h"
#include "dunst.h"
#include "notification.h"
#include "queues.h"
#include "settings.h"
#include "utils.h"
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)
{
char *urls = NULL;
if (!regex_init())
return NULL;
const char *p = to_match;
regmatch_t m;
while (1) {
int nomatch = regexec(&cregex, p, 1, &m, 0);
if (nomatch) {
return urls;
}
int start;
int finish;
if (m.rm_so == -1) {
break;
}
start = m.rm_so + (p - to_match);
finish = m.rm_eo + (p - to_match);
char *match = g_strndup(to_match + start, finish - start);
urls = string_append(urls, match, "\n");
g_free(match);
p += m.rm_eo;
}
return urls;
}
/*
* Open url in browser.
*
*/
void open_browser(const char *in)
{
// remove prefix and test url
char *url = extract_urls(in);
if (!url)
return;
int browser_pid1 = fork();
if (browser_pid1) {
g_free(url);
int status;
waitpid(browser_pid1, &status, 0);
} else {
int browser_pid2 = fork();
if (browser_pid2) {
exit(0);
} else {
char *browser_cmd =
string_append(settings.browser, url, " ");
char **cmd = g_strsplit(browser_cmd, " ", 0);
execvp(cmd[0], cmd);
}
}
}
/*
* Notify the corresponding client
* that an action has been invoked
*/
void invoke_action(const char *action)
{
notification *invoked = NULL;
char *action_identifier = NULL;
char *appname_begin = strchr(action, '[');
if (!appname_begin) {
printf("invalid action: %s\n", action);
return;
}
appname_begin++;
int appname_len = strlen(appname_begin) - 1; // remove ]
int action_len = strlen(action) - appname_len - 3; // remove space, [, ]
for (const GList *iter = queues_get_displayed(); iter;
iter = iter->next) {
notification *n = iter->data;
if (g_str_has_prefix(appname_begin, n->appname) && strlen(n->appname) == appname_len) {
if (!n->actions)
continue;
for (int i = 0; i < n->actions->count; i += 2) {
char *a_identifier = n->actions->actions[i];
char *name = n->actions->actions[i + 1];
if (g_str_has_prefix(action, name) && strlen(name) == action_len) {
invoked = n;
action_identifier = a_identifier;
break;
}
}
}
}
if (invoked && action_identifier) {
action_invoked(invoked, action_identifier);
}
}
/*
* Dispatch whatever has been returned
* by the menu.
*/
void dispatch_menu_result(const char *input)
{
char *in = g_strdup(input);
g_strstrip(in);
if (in[0] == '#') {
invoke_action(in + 1);
} else {
open_browser(in);
}
g_free(in);
}
/*
* Open the context menu that let's the user
* select urls/actions/etc
*/
void context_menu(void)
{
if (settings.dmenu_cmd == NULL) {
fprintf(stderr, "dmenu command not set properly. Cowardly refusing to open the context menu.\n");
return;
}
char *dmenu_input = NULL;
for (const GList *iter = queues_get_displayed(); iter;
iter = iter->next) {
notification *n = iter->data;
if (n->urls)
dmenu_input = string_append(dmenu_input, n->urls, "\n");
if (n->actions)
dmenu_input =
string_append(dmenu_input, n->actions->dmenu_str,
"\n");
}
if (!dmenu_input)
return;
char buf[1024] = {0};
int child_io[2];
int parent_io[2];
if (pipe(child_io) != 0) {
PERR("pipe()", errno);
g_free(dmenu_input);
return;
}
if (pipe(parent_io) != 0) {
PERR("pipe()", errno);
g_free(dmenu_input);
return;
}
int pid = fork();
if (pid == 0) {
close(child_io[1]);
close(parent_io[0]);
close(0);
if (dup(child_io[0]) == -1) {
PERR("dup()", errno);
exit(EXIT_FAILURE);
}
close(1);
if (dup(parent_io[1]) == -1) {
PERR("dup()", errno);
exit(EXIT_FAILURE);
}
execvp(settings.dmenu_cmd[0], settings.dmenu_cmd);
} else {
close(child_io[0]);
close(parent_io[1]);
size_t wlen = strlen(dmenu_input);
if (write(child_io[1], dmenu_input, wlen) != wlen) {
PERR("write()", errno);
}
close(child_io[1]);
size_t len = read(parent_io[0], buf, 1023);
waitpid(pid, NULL, 0);
if (len == 0) {
g_free(dmenu_input);
return;
}
}
close(parent_io[0]);
dispatch_menu_result(buf);
g_free(dmenu_input);
}
/* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */

@ -0,0 +1,11 @@
/* copyright 2013 Sascha Kruse and contributors (see LICENSE for licensing information) */
#ifndef DUNST_MENU_H
#define DUNST_MENU_H
char *extract_urls(const char *to_match);
void open_browser(const char *in);
void invoke_action(const char *action);
void regex_teardown(void);
#endif
/* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */

@ -0,0 +1,580 @@
/* copyright 2013 Sascha Kruse and contributors (see LICENSE for licensing information) */
#include "notification.h"
#include <assert.h>
#include <errno.h>
#include <glib.h>
#include <libgen.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/wait.h>
#include <unistd.h>
#include "dbus.h"
#include "dunst.h"
#include "markup.h"
#include "menu.h"
#include "queues.h"
#include "rules.h"
#include "settings.h"
#include "utils.h"
#include "x11/x.h"
/*
* print a human readable representation
* of the given notification to stdout.
*/
void notification_print(notification *n)
{
printf("{\n");
printf("\tappname: '%s'\n", n->appname);
printf("\tsummary: '%s'\n", n->summary);
printf("\tbody: '%s'\n", n->body);
printf("\ticon: '%s'\n", n->icon);
printf("\traw_icon set: %s\n", (n->raw_icon ? "true" : "false"));
printf("\tcategory: %s\n", n->category);
printf("\ttimeout: %ld\n", n->timeout/1000);
printf("\turgency: %s\n", notification_urgency_to_string(n->urgency));
printf("\ttransient: %d\n", n->transient);
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");
printf("\t{\n");
printf("\t\t%s\n", n->urls);
printf("\t}\n");
}
if (n->actions) {
printf("\tactions:\n");
printf("\t{\n");
for (int i = 0; i < n->actions->count; i += 2) {
printf("\t\t[%s,%s]\n", n->actions->actions[i],
n->actions->actions[i + 1]);
}
printf("\t}\n");
printf("\tactions_dmenu: %s\n", n->actions->dmenu_str);
}
printf("\tscript: %s\n", n->script);
printf("}\n");
}
/*
* Run the script associated with the
* given notification.
*/
void notification_run_script(notification *n)
{
if (!n->script || strlen(n->script) < 1)
return;
char *appname = n->appname ? n->appname : "";
char *summary = n->summary ? n->summary : "";
char *body = n->body ? n->body : "";
char *icon = n->icon ? n->icon : "";
const char *urgency = notification_urgency_to_string(n->urgency);
int pid1 = fork();
if (pid1) {
int status;
waitpid(pid1, &status, 0);
} else {
int pid2 = fork();
if (pid2) {
exit(0);
} else {
int ret = execlp(n->script,
n->script,
appname,
summary,
body,
icon,
urgency,
(char *)NULL);
if (ret != 0) {
PERR("Unable to run script", errno);
exit(EXIT_FAILURE);
}
}
}
}
/*
* Helper function to convert an urgency to a string
*/
const char *notification_urgency_to_string(enum urgency urgency)
{
switch (urgency) {
case URG_NONE:
return "NONE";
case URG_LOW:
return "LOW";
case URG_NORM:
return "NORMAL";
case URG_CRIT:
return "CRITICAL";
default:
return "UNDEF";
}
}
/*
* Helper function to compare to given
* notifications.
*/
int notification_cmp(const void *va, const void *vb)
{
notification *a = (notification *) va;
notification *b = (notification *) vb;
if (!settings.sort)
return 1;
if (a->urgency != b->urgency) {
return b->urgency - a->urgency;
} else {
return a->id - b->id;
}
}
/*
* 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);
}
int notification_is_duplicate(const notification *a, const notification *b)
{
//Comparing raw icons is not supported, assume they are not identical
if (settings.icon_position != icons_off
&& (a->raw_icon != NULL || b->raw_icon != NULL))
return false;
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 actions element
* @a: (nullable): Pointer to #Actions
*/
void actions_free(Actions *a)
{
if (!a)
return;
g_strfreev(a->actions);
g_free(a->dmenu_str);
g_free(a);
}
/*
* Free a #RawImage
* @i: (nullable): pointer to #RawImage
*/
void rawimage_free(RawImage *i)
{
if (!i)
return;
g_free(i->data);
g_free(i);
}
/*
* Free the memory used by the given notification.
*/
void notification_free(notification *n)
{
assert(n != NULL);
g_free(n->appname);
g_free(n->summary);
g_free(n->body);
g_free(n->icon);
g_free(n->msg);
g_free(n->dbus_client);
g_free(n->category);
g_free(n->text_to_render);
g_free(n->urls);
actions_free(n->actions);
rawimage_free(n->raw_icon);
g_free(n);
}
/*
* Replace the two chars where **needle points
* with a quoted "replacement", according to the markup settings.
*
* The needle is a double pointer and gets updated upon return
* to point to the first char, which occurs after replacement.
*
*/
void notification_replace_single_field(char **haystack,
char **needle,
const char *replacement,
enum markup_mode markup_mode)
{
assert(*needle[0] == '%');
// needle has to point into haystack (but not on the last char)
assert(*needle >= *haystack);
assert(*needle - *haystack < strlen(*haystack) - 1);
int pos = *needle - *haystack;
char *input = markup_transform(g_strdup(replacement), markup_mode);
*haystack = string_replace_at(*haystack, pos, 2, input);
// point the needle to the next char
// which was originally in haystack
*needle = *haystack + pos + strlen(input);
g_free(input);
}
char *notification_extract_markup_urls(char **str_ptr)
{
char *start, *end, *replace_buf, *str, *urls = NULL, *url, *index_buf;
int linkno = 1;
str = *str_ptr;
while ((start = strstr(str, "<a href")) != NULL) {
end = strstr(start, ">");
if (end != NULL) {
replace_buf = g_strndup(start, end - start + 1);
url = extract_urls(replace_buf);
if (url != NULL) {
str = string_replace(replace_buf, "[", str);
index_buf = g_strdup_printf("[#%d]", linkno++);
if (urls == NULL) {
urls = g_strconcat(index_buf, " ", url, NULL);
} else {
char *tmp = urls;
urls = g_strconcat(tmp, "\n", index_buf, " ", url, NULL);
g_free(tmp);
}
index_buf[0] = ' ';
str = string_replace("</a>", index_buf, str);
g_free(index_buf);
g_free(url);
} else {
str = string_replace(replace_buf, "", str);
str = string_replace("</a>", "", str);
}
g_free(replace_buf);
} else {
break;
}
}
*str_ptr = str;
return urls;
}
/*
* Create notification struct and initialise everything to NULL,
* this function is guaranteed to return a valid pointer.
*/
notification *notification_create(void)
{
return g_malloc0(sizeof(notification));
}
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
*
* n should be a pointer to a notification allocated with
* notification_create, it is undefined behaviour to pass a notification
* allocated some other way.
*/
void notification_init(notification *n)
{
assert(n != NULL);
//Prevent undefined behaviour by initialising required fields
notification_init_defaults(n);
n->script = NULL;
n->text_to_render = NULL;
n->format = settings.format;
rule_apply_all(n);
if (n->icon != NULL && strlen(n->icon) <= 0) {
g_free(n->icon);
n->icon = NULL;
}
if (n->raw_icon == NULL && n->icon == NULL) {
n->icon = g_strdup(settings.icons[n->urgency]);
}
n->urls = notification_extract_markup_urls(&(n->body));
n->msg = string_replace_all("\\n", "\n", g_strdup(n->format));
/* replace all formatter */
for(char *substr = strchr(n->msg, '%');
substr;
substr = strchr(substr, '%')) {
char pg[16];
char *icon_tmp;
switch(substr[1]) {
case 'a':
notification_replace_single_field(
&n->msg,
&substr,
n->appname,
MARKUP_NO);
break;
case 's':
notification_replace_single_field(
&n->msg,
&substr,
n->summary,
n->markup);
break;
case 'b':
notification_replace_single_field(
&n->msg,
&substr,
n->body,
n->markup);
break;
case 'I':
icon_tmp = g_strdup(n->icon);
notification_replace_single_field(
&n->msg,
&substr,
icon_tmp ? basename(icon_tmp) : "",
MARKUP_NO);
g_free(icon_tmp);
break;
case 'i':
notification_replace_single_field(
&n->msg,
&substr,
n->icon ? n->icon : "",
MARKUP_NO);
break;
case 'p':
if (n->progress != -1)
sprintf(pg, "[%3d%%]", n->progress);
notification_replace_single_field(
&n->msg,
&substr,
n->progress != -1 ? pg : "",
MARKUP_NO);
break;
case 'n':
if (n->progress != -1)
sprintf(pg, "%d", n->progress);
notification_replace_single_field(
&n->msg,
&substr,
n->progress != -1 ? pg : "",
MARKUP_NO);
break;
case '%':
notification_replace_single_field(
&n->msg,
&substr,
"%",
MARKUP_NO);
break;
case '\0':
fprintf(stderr, "WARNING: format_string has trailing %% character."
"To escape it use %%%%.");
break;
default:
fprintf(stderr, "WARNING: format_string %%%c"
" is unknown\n", substr[1]);
// shift substr pointer forward,
// as we can't interpret the format string
substr++;
break;
}
}
n->msg = g_strchomp(n->msg);
/* truncate overlong messages */
if (strlen(n->msg) > DUNST_NOTIF_MAX_CHARS) {
char *buffer = g_malloc(DUNST_NOTIF_MAX_CHARS);
strncpy(buffer, n->msg, DUNST_NOTIF_MAX_CHARS);
buffer[DUNST_NOTIF_MAX_CHARS-1] = '\0';
g_free(n->msg);
n->msg = buffer;
}
n->dup_count = 0;
/* urgency > URG_CRIT -> array out of range */
if (n->urgency < URG_MIN)
n->urgency = URG_LOW;
if (n->urgency > URG_MAX)
n->urgency = URG_CRIT;
if (!n->color_strings[ColFG]) {
n->color_strings[ColFG] = xctx.color_strings[ColFG][n->urgency];
}
if (!n->color_strings[ColBG]) {
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 < 0 ? settings.timeouts[n->urgency] : n->timeout;
n->start = 0;
n->timestamp = g_get_monotonic_time();
n->redisplayed = false;
n->first_render = true;
char *tmp = g_strconcat(n->summary, " ", n->body, NULL);
char *tmp_urls = extract_urls(tmp);
n->urls = string_append(n->urls, tmp_urls, "\n");
g_free(tmp_urls);
if (n->actions) {
n->actions->dmenu_str = NULL;
for (int i = 0; i < n->actions->count; i += 2) {
char *human_readable = n->actions->actions[i + 1];
string_replace_char('[', '(', human_readable); // kill square brackets
string_replace_char(']', ')', human_readable);
char *act_str = g_strdup_printf("#%s [%s]", human_readable, n->appname);
if (act_str) {
n->actions->dmenu_str = string_append(n->actions->dmenu_str, act_str, "\n");
g_free(act_str);
}
}
}
g_free(tmp);
}
void notification_update_text_to_render(notification *n)
{
g_free(n->text_to_render);
n->text_to_render = NULL;
char *buf = NULL;
char *msg = g_strchomp(n->msg);
/* print dup_count and msg */
if ((n->dup_count > 0 && !settings.hide_duplicate_count)
&& (n->actions || n->urls) && settings.show_indicators) {
buf = g_strdup_printf("(%d%s%s) %s",
n->dup_count,
n->actions ? "A" : "",
n->urls ? "U" : "", msg);
} else if ((n->actions || n->urls) && settings.show_indicators) {
buf = g_strdup_printf("(%s%s) %s",
n->actions ? "A" : "",
n->urls ? "U" : "", msg);
} else if (n->dup_count > 0 && !settings.hide_duplicate_count) {
buf = g_strdup_printf("(%d) %s", n->dup_count, msg);
} else {
buf = g_strdup(msg);
}
/* print age */
gint64 hours, minutes, seconds;
gint64 t_delta = g_get_monotonic_time() - n->timestamp;
if (settings.show_age_threshold >= 0
&& t_delta >= settings.show_age_threshold) {
hours = t_delta / G_USEC_PER_SEC / 3600;
minutes = t_delta / G_USEC_PER_SEC / 60 % 60;
seconds = t_delta / G_USEC_PER_SEC % 60;
char *new_buf;
if (hours > 0) {
new_buf =
g_strdup_printf("%s (%ldh %ldm %lds old)", buf, hours,
minutes, seconds);
} else if (minutes > 0) {
new_buf =
g_strdup_printf("%s (%ldm %lds old)", buf, minutes,
seconds);
} else {
new_buf = g_strdup_printf("%s (%lds old)", buf, seconds);
}
g_free(buf);
buf = new_buf;
}
n->text_to_render = buf;
}
/*
* If the notification has exactly one action, or one is marked as default,
* invoke it. If there are multiple and no default, open the context menu. If
* there are no actions, proceed similarly with urls.
*/
void notification_do_action(notification *n)
{
if (n->actions) {
if (n->actions->count == 2) {
action_invoked(n, n->actions->actions[0]);
return;
}
for (int i = 0; i < n->actions->count; i += 2) {
if (strcmp(n->actions->actions[i], "default") == 0) {
action_invoked(n, n->actions->actions[i]);
return;
}
}
context_menu();
} else if (n->urls) {
if (strstr(n->urls, "\n") == NULL)
open_browser(n->urls);
else
context_menu();
}
}
/* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */

@ -0,0 +1,87 @@
/* copyright 2013 Sascha Kruse and contributors (see LICENSE for licensing information) */
#ifndef DUNST_NOTIFICATION_H
#define DUNST_NOTIFICATION_H
#include <glib.h>
#include <stdbool.h>
#include "settings.h"
#define DUNST_NOTIF_MAX_CHARS 5000
enum urgency {
URG_NONE = -1,
URG_MIN = 0,
URG_LOW = 0,
URG_NORM = 1,
URG_CRIT = 2,
URG_MAX = 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;
gsize count;
} Actions;
typedef struct _notification {
char *appname;
char *summary;
char *body;
char *icon;
RawImage *raw_icon;
char *msg; /* formatted message */
char *category;
char *text_to_render;
const char *format;
char *dbus_client;
gint64 start;
gint64 timestamp;
gint64 timeout;
enum urgency urgency;
enum markup_mode markup;
bool redisplayed; /* has been displayed before? */
int id;
int dup_count;
int displayed_height;
const char *color_strings[3];
bool first_render;
bool transient;
int progress; /* percentage (-1: undefined) */
int history_ignore;
const char *script;
char *urls;
Actions *actions;
} notification;
notification *notification_create(void);
void notification_init(notification *n);
void actions_free(Actions *a);
void rawimage_free(RawImage *i);
void notification_free(notification *n);
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);
void notification_print(notification *n);
void notification_replace_single_field(char **haystack,
char **needle,
const char *replacement,
enum markup_mode markup_mode);
void notification_update_text_to_render(notification *n);
void notification_do_action(notification *n);
const char *notification_urgency_to_string(enum urgency urgency);
#endif
/* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */

@ -0,0 +1,565 @@
/* copyright 2013 Sascha Kruse and contributors (see LICENSE for licensing information) */
#include "option_parser.h"
#include <glib.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "utils.h"
typedef struct _entry_t {
char *key;
char *value;
} entry_t;
typedef struct _section_t {
char *name;
int entry_count;
entry_t *entries;
} section_t;
static int section_count = 0;
static section_t *sections;
static section_t *new_section(const char *name);
static section_t *get_section(const char *name);
static void add_entry(const char *section_name, const char *key, const char *value);
static const char *get_value(const char *section, const char *key);
static char *clean_value(const char *value);
static int cmdline_argc;
static char **cmdline_argv;
static char *usage_str = NULL;
static void cmdline_usage_append(const char *key, const char *type, const char *description);
static int cmdline_find_option(const char *key);
section_t *new_section(const char *name)
{
for (int i = 0; i < section_count; i++) {
if (!strcmp(name, sections[i].name)) {
die("Duplicated section in dunstrc detected.\n", -1);
}
}
section_count++;
sections = g_realloc(sections, sizeof(section_t) * section_count);
sections[section_count - 1].name = g_strdup(name);
sections[section_count - 1].entries = NULL;
sections[section_count - 1].entry_count = 0;
return &sections[section_count - 1];
}
void free_ini(void)
{
for (int i = 0; i < section_count; i++) {
for (int j = 0; j < sections[i].entry_count; j++) {
g_free(sections[i].entries[j].key);
g_free(sections[i].entries[j].value);
}
g_free(sections[i].entries);
g_free(sections[i].name);
}
g_free(sections);
section_count = 0;
sections = NULL;
}
section_t *get_section(const char *name)
{
for (int i = 0; i < section_count; i++) {
if (strcmp(sections[i].name, name) == 0)
return &sections[i];
}
return NULL;
}
void add_entry(const char *section_name, const char *key, const char *value)
{
section_t *s = get_section(section_name);
if (s == NULL) {
s = new_section(section_name);
}
s->entry_count++;
int len = s->entry_count;
s->entries = g_realloc(s->entries, sizeof(entry_t) * len);
s->entries[s->entry_count - 1].key = g_strdup(key);
s->entries[s->entry_count - 1].value = clean_value(value);
}
const char *get_value(const char *section, const char *key)
{
section_t *s = get_section(section);
if (!s) {
return NULL;
}
for (int i = 0; i < s->entry_count; i++) {
if (strcmp(s->entries[i].key, key) == 0) {
return s->entries[i].value;
}
}
return NULL;
}
char *ini_get_path(const char *section, const char *key, const char *def)
{
return string_to_path(ini_get_string(section, key, def));
}
char *ini_get_string(const char *section, const char *key, const char *def)
{
const char *value = get_value(section, key);
if (value)
return g_strdup(value);
return def ? g_strdup(def) : NULL;
}
gint64 ini_get_time(const char *section, const char *key, gint64 def)
{
const char *timestring = get_value(section, key);
gint64 val = def;
if (timestring) {
val = string_to_time(timestring);
}
return val;
}
int ini_get_int(const char *section, const char *key, int def)
{
const char *value = get_value(section, key);
if (value == NULL)
return def;
else
return atoi(value);
}
double ini_get_double(const char *section, const char *key, double def)
{
const char *value = get_value(section, key);
if (value == NULL)
return def;
else
return atof(value);
}
bool ini_is_set(const char *ini_section, const char *ini_key)
{
return get_value(ini_section, ini_key) != NULL;
}
const char *next_section(const char *section)
{
if (section_count == 0)
return NULL;
if (section == NULL) {
return sections[0].name;
}
for (int i = 0; i < section_count; i++) {
if (strcmp(section, sections[i].name) == 0) {
if (i + 1 >= section_count)
return NULL;
else
return sections[i + 1].name;
}
}
return NULL;
}
int ini_get_bool(const char *section, const char *key, int def)
{
const char *value = get_value(section, key);
if (value == NULL)
return def;
else {
switch (value[0]) {
case 'y':
case 'Y':
case 't':
case 'T':
case '1':
return true;
case 'n':
case 'N':
case 'f':
case 'F':
case '0':
return false;
default:
return def;
}
}
}
char *clean_value(const char *value)
{
char *s;
if (value[0] == '"')
s = g_strdup(value + 1);
else
s = g_strdup(value);
if (s[strlen(s) - 1] == '"')
s[strlen(s) - 1] = '\0';
return s;
}
int load_ini_file(FILE *fp)
{
if (!fp)
return 1;
char *line = NULL;
size_t line_len = 0;
int line_num = 0;
char *current_section = NULL;
while (getline(&line, &line_len, fp) != -1) {
line_num++;
char *start = g_strstrip(line);
if (*start == ';' || *start == '#' || strlen(start) == 0)
continue;
if (*start == '[') {
char *end = strchr(start + 1, ']');
if (!end) {
fprintf(stderr,
"Warning: invalid config file at line %d\n",
line_num);
fprintf(stderr, "Missing ']'\n");
continue;
}
*end = '\0';
g_free(current_section);
current_section = (g_strdup(start + 1));
new_section(current_section);
continue;
}
char *equal = strchr(start + 1, '=');
if (!equal) {
fprintf(stderr,
"Warning: invalid config file at line %d\n",
line_num);
fprintf(stderr, "Missing '='\n");
continue;
}
*equal = '\0';
char *key = g_strstrip(start);
char *value = g_strstrip(equal + 1);
char *quote = strchr(value, '"');
if (quote) {
char *closing_quote = strchr(quote + 1, '"');
if (!closing_quote) {
fprintf(stderr,
"Warning: invalid config file at line %d\n",
line_num);
fprintf(stderr, "Missing '\"'\n");
continue;
}
} else {
char *comment = strpbrk(value, "#;");
if (comment)
*comment = '\0';
}
value = g_strstrip(value);
if (!current_section) {
fprintf(stderr,
"Warning: invalid config file at line %d\n",
line_num);
fprintf(stderr, "Key value pair without a section\n");
continue;
}
add_entry(current_section, key, value);
}
free(line);
g_free(current_section);
return 0;
}
void cmdline_load(int argc, char *argv[])
{
cmdline_argc = argc;
cmdline_argv = argv;
}
int cmdline_find_option(const char *key)
{
if (!key) {
return -1;
}
char *key1 = g_strdup(key);
char *key2 = strchr(key1, '/');
if (key2) {
*key2 = '\0';
key2++;
}
/* look for first key */
for (int i = 0; i < cmdline_argc; i++) {
if (strcmp(key1, cmdline_argv[i]) == 0) {
g_free(key1);
return i;
}
}
/* look for second key if one was specified */
if (key2) {
for (int i = 0; i < cmdline_argc; i++) {
if (strcmp(key2, cmdline_argv[i]) == 0) {
g_free(key1);
return i;
}
}
}
g_free(key1);
return -1;
}
static const char *cmdline_get_value(const char *key)
{
int idx = cmdline_find_option(key);
if (idx < 0) {
return NULL;
}
if (idx + 1 >= cmdline_argc) {
/* the argument is missing */
fprintf(stderr, "Warning: %s, missing argument. Ignoring\n",
key);
return NULL;
}
return cmdline_argv[idx + 1];
}
char *cmdline_get_string(const char *key, const char *def, const char *description)
{
cmdline_usage_append(key, "string", description);
const char *str = cmdline_get_value(key);
if (str)
return g_strdup(str);
if (def == NULL)
return NULL;
else
return g_strdup(def);
}
char *cmdline_get_path(const char *key, const char *def, const char *description)
{
cmdline_usage_append(key, "string", description);
const char *str = cmdline_get_value(key);
if (str)
return string_to_path(g_strdup(str));
else
return string_to_path(g_strdup(def));
}
gint64 cmdline_get_time(const char *key, gint64 def, const char *description)
{
cmdline_usage_append(key, "time", description);
const char *timestring = cmdline_get_value(key);
gint64 val = def;
if (timestring) {
val = string_to_time(timestring);
}
return val;
}
int cmdline_get_int(const char *key, int def, const char *description)
{
cmdline_usage_append(key, "int", description);
const char *str = cmdline_get_value(key);
if (str == NULL)
return def;
else
return atoi(str);
}
double cmdline_get_double(const char *key, double def, const char *description)
{
cmdline_usage_append(key, "double", description);
const char *str = cmdline_get_value(key);
if (str == NULL)
return def;
else
return atof(str);
}
int cmdline_get_bool(const char *key, int def, const char *description)
{
cmdline_usage_append(key, "", description);
int idx = cmdline_find_option(key);
if (idx > 0)
return true;
else
return def;
}
bool cmdline_is_set(const char *key)
{
return cmdline_get_value(key) != NULL;
}
char *option_get_path(const char *ini_section,
const char *ini_key,
const char *cmdline_key,
const char *def,
const char *description)
{
char *val = NULL;
if (cmdline_key) {
val = cmdline_get_path(cmdline_key, NULL, description);
}
if (val) {
return val;
} else {
return ini_get_path(ini_section, ini_key, def);
}
}
char *option_get_string(const char *ini_section,
const char *ini_key,
const char *cmdline_key,
const char *def,
const char *description)
{
char *val = NULL;
if (cmdline_key) {
val = cmdline_get_string(cmdline_key, NULL, description);
}
if (val) {
return val;
} else {
return ini_get_string(ini_section, ini_key, def);
}
}
gint64 option_get_time(const char *ini_section,
const char *ini_key,
const char *cmdline_key,
gint64 def,
const char *description)
{
gint64 ini_val = ini_get_time(ini_section, ini_key, def);
return cmdline_get_time(cmdline_key, ini_val, description);
}
int option_get_int(const char *ini_section,
const char *ini_key,
const char *cmdline_key,
int def,
const char *description)
{
/* *str is only used to check wether the cmdline option is actually set. */
const char *str = cmdline_get_value(cmdline_key);
/* we call cmdline_get_int even when the option isn't set in order to
* add the usage info */
int val = cmdline_get_int(cmdline_key, def, description);
if (!str)
return ini_get_int(ini_section, ini_key, def);
else
return val;
}
double option_get_double(const char *ini_section,
const char *ini_key,
const char *cmdline_key,
double def,
const char *description)
{
const char *str = cmdline_get_value(cmdline_key);
double val = cmdline_get_double(cmdline_key, def, description);
if (!str)
return ini_get_double(ini_section, ini_key, def);
else
return val;
}
int option_get_bool(const char *ini_section,
const char *ini_key,
const char *cmdline_key,
int def,
const char *description)
{
int val = false;
if (cmdline_key)
val = cmdline_get_bool(cmdline_key, false, description);
if (cmdline_key && val) {
/* this can only be true if the value has been set,
* so we can return */
return true;
}
return ini_get_bool(ini_section, ini_key, def);
}
void cmdline_usage_append(const char *key, const char *type, const char *description)
{
char *key_type;
if (type && strlen(type) > 0)
key_type = g_strdup_printf("%s (%s)", key, type);
else
key_type = g_strdup(key);
if (!usage_str) {
usage_str =
g_strdup_printf("%-40s - %s\n", key_type, description);
g_free(key_type);
return;
}
char *tmp;
tmp =
g_strdup_printf("%s%-40s - %s\n", usage_str, key_type, description);
g_free(key_type);
g_free(usage_str);
usage_str = tmp;
}
const char *cmdline_create_usage(void)
{
return usage_str;
}
/* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */

@ -0,0 +1,67 @@
/* copyright 2013 Sascha Kruse and contributors (see LICENSE for licensing information) */
#ifndef DUNST_OPTION_PARSER_H
#define DUNST_OPTION_PARSER_H
#include <glib.h>
#include <stdbool.h>
#include <stdio.h>
int load_ini_file(FILE *);
char *ini_get_path(const char *section, const char *key, const char *def);
char *ini_get_string(const char *section, const char *key, const char *def);
gint64 ini_get_time(const char *section, const char *key, gint64 def);
int ini_get_int(const char *section, const char *key, int def);
double ini_get_double(const char *section, const char *key, double def);
int ini_get_bool(const char *section, const char *key, int def);
bool ini_is_set(const char *ini_section, const char *ini_key);
void free_ini(void);
void cmdline_load(int argc, char *argv[]);
/* for all cmdline_get_* key can be either "-key" or "-key/-longkey" */
char *cmdline_get_string(const char *key, const char *def, const char *description);
char *cmdline_get_path(const char *key, const char *def, const char *description);
int cmdline_get_int(const char *key, int def, const char *description);
double cmdline_get_double(const char *key, double def, const char *description);
int cmdline_get_bool(const char *key, int def, const char *description);
bool cmdline_is_set(const char *key);
const char *cmdline_create_usage(void);
char *option_get_string(const char *ini_section,
const char *ini_key,
const char *cmdline_key,
const char *def,
const char *description);
char *option_get_path(const char *ini_section,
const char *ini_key,
const char *cmdline_key,
const char *def,
const char *description);
gint64 option_get_time(const char *ini_section,
const char *ini_key,
const char *cmdline_key,
gint64 def,
const char *description);
int option_get_int(const char *ini_section,
const char *ini_key,
const char *cmdline_key,
int def,
const char *description);
double option_get_double(const char *ini_section,
const char *ini_key,
const char *cmdline_key,
double def,
const char *description);
int option_get_bool(const char *ini_section,
const char *ini_key,
const char *cmdline_key,
int def,
const char *description);
/* returns the next known section.
* if section == NULL returns first section.
* returns NULL if no more sections are available
*/
const char *next_section(const char *section);
#endif
/* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */

@ -0,0 +1,384 @@
/* copyright 2013 Sascha Kruse and contributors (see LICENSE for licensing information) */
#include "queues.h"
#include <assert.h>
#include <glib.h>
#include <stdio.h>
#include <string.h>
#include "notification.h"
#include "settings.h"
/* notification lists */
static GQueue *waiting = NULL; /* all new notifications get into here */
static GQueue *displayed = NULL; /* currently displayed notifications */
static GQueue *history = NULL; /* history of displayed notifications */
unsigned int displayed_limit = 0;
int next_notification_id = 1;
bool pause_displayed = false;
static bool queues_stack_duplicate(notification *n);
void queues_init(void)
{
history = g_queue_new();
displayed = g_queue_new();
waiting = g_queue_new();
}
void queues_displayed_limit(unsigned int limit)
{
displayed_limit = limit;
}
/* misc getter functions */
const GList *queues_get_displayed()
{
return g_queue_peek_head_link(displayed);
}
unsigned int queues_length_waiting()
{
return waiting->length;
}
unsigned int queues_length_displayed()
{
return displayed->length;
}
unsigned int queues_length_history()
{
return history->length;
}
int queues_notification_insert(notification *n, int replaces_id)
{
/* do not display the message, if the message is empty */
if (strlen(n->msg) == 0) {
if (settings.always_run_script) {
notification_run_script(n);
}
printf("skipping notification: %s %s\n", n->body, n->summary);
return 0;
}
/* Do not insert the message if it's a command */
if (strcmp("DUNST_COMMAND_PAUSE", n->summary) == 0) {
pause_displayed = true;
return 0;
}
if (strcmp("DUNST_COMMAND_RESUME", n->summary) == 0) {
pause_displayed = false;
return 0;
}
if (replaces_id == 0) {
n->id = ++next_notification_id;
if (!settings.stack_duplicates || !queues_stack_duplicate(n))
g_queue_insert_sorted(waiting, n, notification_cmp_data, NULL);
} else {
n->id = replaces_id;
if (!queues_notification_replace_id(n))
g_queue_insert_sorted(waiting, n, notification_cmp_data, NULL);
}
if (settings.print_notifications)
notification_print(n);
return n->id;
}
/*
* Replaces duplicate notification and stacks it
*
* Returns %true, if notification got stacked
* Returns %false, if notification did not get stacked
*/
static bool queues_stack_duplicate(notification *n)
{
for (GList *iter = g_queue_peek_head_link(displayed); iter;
iter = iter->next) {
notification *orig = iter->data;
if (notification_is_duplicate(orig, n)) {
/* If the progress differs, probably notify-send was used to update the notification
* So only count it as a duplicate, if the progress was not the same.
* */
if (orig->progress == n->progress) {
orig->dup_count++;
} else {
orig->progress = n->progress;
}
iter->data = n;
n->start = g_get_monotonic_time();
n->dup_count = orig->dup_count;
notification_closed(orig, 1);
notification_free(orig);
return true;
}
}
for (GList *iter = g_queue_peek_head_link(waiting); iter;
iter = iter->next) {
notification *orig = iter->data;
if (notification_is_duplicate(orig, n)) {
/* If the progress differs, probably notify-send was used to update the notification
* So only count it as a duplicate, if the progress was not the same.
* */
if (orig->progress == n->progress) {
orig->dup_count++;
} else {
orig->progress = n->progress;
}
iter->data = n;
n->dup_count = orig->dup_count;
notification_closed(orig, 1);
notification_free(orig);
return true;
}
}
return false;
}
bool queues_notification_replace_id(notification *new)
{
for (GList *iter = g_queue_peek_head_link(displayed);
iter;
iter = iter->next) {
notification *old = iter->data;
if (old->id == new->id) {
iter->data = new;
new->start = g_get_monotonic_time();
new->dup_count = old->dup_count;
notification_run_script(new);
notification_free(old);
return true;
}
}
for (GList *iter = g_queue_peek_head_link(waiting);
iter;
iter = iter->next) {
notification *old = iter->data;
if (old->id == new->id) {
iter->data = new;
new->dup_count = old->dup_count;
notification_free(old);
return true;
}
}
return false;
}
int queues_notification_close_id(int id, enum reason reason)
{
notification *target = NULL;
for (GList *iter = g_queue_peek_head_link(displayed); iter;
iter = iter->next) {
notification *n = iter->data;
if (n->id == id) {
g_queue_remove(displayed, n);
target = n;
break;
}
}
for (GList *iter = g_queue_peek_head_link(waiting); iter;
iter = iter->next) {
notification *n = iter->data;
if (n->id == id) {
assert(target == NULL);
g_queue_remove(waiting, n);
target = n;
break;
}
}
if (target) {
notification_closed(target, reason);
queues_history_push(target);
}
return reason;
}
int queues_notification_close(notification *n, enum reason reason)
{
assert(n != NULL);
return queues_notification_close_id(n->id, reason);
}
void queues_history_pop(void)
{
if (g_queue_is_empty(history))
return;
notification *n = g_queue_pop_tail(history);
n->redisplayed = true;
n->start = 0;
n->timeout = settings.sticky_history ? 0 : n->timeout;
g_queue_push_head(waiting, n);
}
void queues_history_push(notification *n)
{
if (!n->history_ignore) {
if (settings.history_length > 0 && history->length >= settings.history_length) {
notification *to_free = g_queue_pop_head(history);
notification_free(to_free);
}
g_queue_push_tail(history, n);
} else {
notification_free(n);
}
}
void queues_history_push_all(void)
{
while (displayed->length > 0) {
queues_notification_close(g_queue_peek_head_link(displayed)->data, REASON_USER);
}
while (waiting->length > 0) {
queues_notification_close(g_queue_peek_head_link(waiting)->data, REASON_USER);
}
}
void queues_check_timeouts(bool idle)
{
/* nothing to do */
if (displayed->length == 0)
return;
GList *iter = g_queue_peek_head_link(displayed);
while (iter) {
notification *n = iter->data;
/*
* Update iter to the next item before we either exit the
* current iteration of the loop or potentially delete the
* notification which would invalidate the pointer.
*/
iter = iter->next;
/* don't timeout when user is idle */
if (idle && !n->transient) {
n->start = g_get_monotonic_time();
continue;
}
/* skip hidden and sticky messages */
if (n->start == 0 || n->timeout == 0) {
continue;
}
/* remove old message */
if (g_get_monotonic_time() - n->start > n->timeout) {
queues_notification_close(n, REASON_TIME);
}
}
}
void queues_update()
{
if (pause_displayed) {
while (displayed->length > 0) {
g_queue_insert_sorted(
waiting, g_queue_pop_head(displayed), notification_cmp_data, NULL);
}
return;
}
/* move notifications from queue to displayed */
while (waiting->length > 0) {
if (displayed_limit > 0 && displayed->length >= displayed_limit) {
/* the list is full */
break;
}
notification *n = g_queue_pop_head(waiting);
if (!n)
return;
n->start = g_get_monotonic_time();
if (!n->redisplayed && n->script) {
notification_run_script(n);
}
g_queue_insert_sorted(displayed, n, notification_cmp_data, NULL);
}
}
gint64 queues_get_next_datachange(gint64 time)
{
gint64 sleep = G_MAXINT64;
for (GList *iter = g_queue_peek_head_link(displayed); iter;
iter = iter->next) {
notification *n = iter->data;
gint64 ttl = n->timeout - (time - n->start);
if (n->timeout > 0) {
if (ttl > 0)
sleep = MIN(sleep, ttl);
else
// while we're processing, the notification already timed out
return 0;
}
if (settings.show_age_threshold >= 0) {
gint64 age = time - n->timestamp;
if (age > settings.show_age_threshold)
// sleep exactly until the next shift of the second happens
sleep = MIN(sleep, ((G_USEC_PER_SEC) - (age % (G_USEC_PER_SEC))));
else if (ttl > settings.show_age_threshold)
sleep = MIN(sleep, settings.show_age_threshold);
}
}
return sleep != G_MAXINT64 ? sleep : -1;
}
void queues_pause_on(void)
{
pause_displayed = true;
}
void queues_pause_off(void)
{
pause_displayed = false;
}
bool queues_pause_status(void)
{
return pause_displayed;
}
static void teardown_notification(gpointer data)
{
notification *n = data;
notification_free(n);
}
void teardown_queues(void)
{
g_queue_free_full(history, teardown_notification);
g_queue_free_full(displayed, teardown_notification);
g_queue_free_full(waiting, teardown_notification);
}
/* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */

@ -0,0 +1,133 @@
/* copyright 2013 Sascha Kruse and contributors (see LICENSE for licensing information) */
#ifndef DUNST_QUEUE_H
#define DUNST_QUEUE_H
#include "dbus.h"
#include "notification.h"
/*
* Initialise neccessary queues
*/
void queues_init(void);
/*
* Set maximum notification count to display
* and store in displayed queue
*/
void queues_displayed_limit(unsigned int limit);
/*
* Return read only list of notifications
*/
const GList *queues_get_displayed();
/*
* Returns the current amount of notifications,
* which are shown, waiting or already in history
*/
unsigned int queues_length_waiting();
unsigned int queues_length_displayed();
unsigned int queues_length_history();
/*
* Insert a fully initialized notification into queues
* Respects stack_duplicates, and notification replacement
*
* If replaces_id != 0, n replaces notification with id replaces_id
* If replaces_id == 0, n gets occupies a new position
*
* Returns the assigned notification id
* If returned id == 0, the message was dismissed
*/
int queues_notification_insert(notification *n, int replaces_id);
/*
* Replace the notification which matches the id field of
* the new notification. The given notification is inserted
* right in the same position as the old notification.
*
* Returns true, if a matching notification has been found
* and is replaced. Else false.
*/
bool queues_notification_replace_id(notification *new);
/*
* Close the notification that has n->id == id
*
* Sends a signal and pushes it automatically to history.
*
* After closing, call wake_up to synchronize the queues with the UI
* (which closes the notification on screen)
*/
int queues_notification_close_id(int id, enum reason reason);
/* Close the given notification. SEE queues_notification_close_id.
*
* @n: (transfer full): The notification to close
* @reason: The reason to close
* */
int queues_notification_close(notification *n, enum reason reason);
/*
* Pushed the latest notification of history to the displayed queue
* and removes it from history
*/
void queues_history_pop(void);
/*
* Push a single notification to history
* The given notification has to be removed its queue
*
* @n: (transfer full): The notification to push to history
*/
void queues_history_push(notification *n);
/*
* Push all waiting and displayed notifications to history
*/
void queues_history_push_all(void);
/*
* Check timeout of each notification and close it, if neccessary
*/
void queues_check_timeouts(bool idle);
/*
* Move inserted notifications from waiting queue to displayed queue
* and show them. In displayed queue, the amount of elements is limited
* to the amount set via queues_displayed_limit
*/
void queues_update();
/*
* Return the distance to the next event in the queue,
* which forces an update visible to the user
*
* This may be:
*
* - notification hits timeout
* - notification's age second changes
* - notification's age threshold is hit
*/
gint64 queues_get_next_datachange(gint64 time);
/*
* Pause queue-management of dunst
* pause_on = paused (no notifications displayed)
* pause_off = running
*
* Calling update_lists is neccessary
*/
void queues_pause_on(void);
void queues_pause_off(void);
bool queues_pause_status(void);
/*
* Remove all notifications from all lists
* and free the notifications
*/
void teardown_queues(void);
#endif
/* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */

@ -0,0 +1,91 @@
/* copyright 2013 Sascha Kruse and contributors (see LICENSE for licensing information) */
#include "rules.h"
#include <fnmatch.h>
#include <glib.h>
#include "dunst.h"
/*
* Apply rule to notification.
*/
void rule_apply(rule_t *r, notification *n)
{
if (r->timeout != -1)
n->timeout = r->timeout;
if (r->urgency != URG_NONE)
n->urgency = r->urgency;
if (r->history_ignore != -1)
n->history_ignore = r->history_ignore;
if (r->set_transient != -1)
n->transient = r->set_transient;
if (r->markup != MARKUP_NULL)
n->markup = r->markup;
if (r->new_icon) {
g_free(n->icon);
n->icon = g_strdup(r->new_icon);
rawimage_free(n->raw_icon);
n->raw_icon = NULL;
}
if (r->fg)
n->color_strings[ColFG] = r->fg;
if (r->bg)
n->color_strings[ColBG] = r->bg;
if (r->format)
n->format = r->format;
if (r->script)
n->script = r->script;
}
/*
* Check all rules if they match n and apply.
*/
void rule_apply_all(notification *n)
{
for (GSList *iter = rules; iter; iter = iter->next) {
rule_t *r = iter->data;
if (rule_matches_notification(r, n)) {
rule_apply(r, n);
}
}
}
/*
* Initialize rule with default values.
*/
void rule_init(rule_t *r)
{
r->name = NULL;
r->appname = NULL;
r->summary = NULL;
r->body = NULL;
r->icon = NULL;
r->category = NULL;
r->msg_urgency = URG_NONE;
r->timeout = -1;
r->urgency = URG_NONE;
r->markup = MARKUP_NULL;
r->new_icon = NULL;
r->history_ignore = false;
r->match_transient = -1;
r->set_transient = -1;
r->fg = NULL;
r->bg = NULL;
r->format = NULL;
}
/*
* Check whether rule should be applied to n.
*/
bool rule_matches_notification(rule_t *r, notification *n)
{
return ((!r->appname || !fnmatch(r->appname, n->appname, 0))
&& (!r->summary || !fnmatch(r->summary, n->summary, 0))
&& (!r->body || !fnmatch(r->body, n->body, 0))
&& (!r->icon || !fnmatch(r->icon, n->icon, 0))
&& (!r->category || !fnmatch(r->category, n->category, 0))
&& (r->match_transient == -1 || (r->match_transient == n->transient))
&& (r->msg_urgency == URG_NONE || r->msg_urgency == n->urgency));
}
/* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */

@ -0,0 +1,43 @@
/* copyright 2013 Sascha Kruse and contributors (see LICENSE for licensing information) */
#ifndef DUNST_RULES_H
#define DUNST_RULES_H
#include <glib.h>
#include <stdbool.h>
#include "notification.h"
#include "settings.h"
typedef struct _rule_t {
char *name;
/* filters */
char *appname;
char *summary;
char *body;
char *icon;
char *category;
int msg_urgency;
/* actions */
gint64 timeout;
enum urgency urgency;
enum markup_mode markup;
int history_ignore;
int match_transient;
int set_transient;
char *new_icon;
char *fg;
char *bg;
const char *format;
const char *script;
} rule_t;
extern GSList *rules;
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);
#endif
/* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */

@ -0,0 +1,697 @@
/* copyright 2013 Sascha Kruse and contributors (see LICENSE for licensing information) */
#include "settings.h"
#include <glib.h>
#include <stdio.h>
#include <string.h>
#ifndef STATIC_CONFIG
#include <basedir.h>
#include <basedir_fs.h>
#endif
#include "rules.h" // put before config.h to fix missing include
#include "config.h"
#include "dunst.h"
#include "notification.h"
#include "option_parser.h"
#include "utils.h"
settings_t settings;
static void parse_follow_mode(const char *mode)
{
if (strcmp(mode, "mouse") == 0)
settings.f_mode = FOLLOW_MOUSE;
else if (strcmp(mode, "keyboard") == 0)
settings.f_mode = FOLLOW_KEYBOARD;
else if (strcmp(mode, "none") == 0)
settings.f_mode = FOLLOW_NONE;
else {
fprintf(stderr, "Warning: unknown follow mode: \"%s\"\n", mode);
settings.f_mode = FOLLOW_NONE;
}
}
static enum markup_mode parse_markup_mode(const char *mode)
{
if (strcmp(mode, "strip") == 0) {
return MARKUP_STRIP;
} else if (strcmp(mode, "no") == 0) {
return MARKUP_NO;
} else if (strcmp(mode, "full") == 0 || strcmp(mode, "yes") == 0) {
return MARKUP_FULL;
} else {
fprintf(stderr, "Warning: unknown markup mode: \"%s\"\n", mode);
return MARKUP_NO;
}
}
static enum urgency ini_get_urgency(const char *section, const char *key, const int def)
{
int ret = def;
char *urg = ini_get_string(section, key, "");
if (strlen(urg) > 0) {
if (strcmp(urg, "low") == 0)
ret = URG_LOW;
else if (strcmp(urg, "normal") == 0)
ret = URG_NORM;
else if (strcmp(urg, "critical") == 0)
ret = URG_CRIT;
else
fprintf(stderr,
"unknown urgency: %s, ignoring\n",
urg);
}
g_free(urg);
return ret;
}
void load_settings(char *cmdline_config_path)
{
#ifndef STATIC_CONFIG
xdgHandle xdg;
FILE *config_file = NULL;
xdgInitHandle(&xdg);
if (cmdline_config_path != NULL) {
config_file = fopen(cmdline_config_path, "r");
}
if (config_file == NULL) {
config_file = xdgConfigOpen("dunst/dunstrc", "r", &xdg);
}
if (config_file == NULL) {
/* Fall back to just "dunstrc", which was used before 2013-06-23
* (before v0.2). */
config_file = xdgConfigOpen("dunstrc", "r", &xdg);
if (config_file == NULL) {
puts("no dunstrc found -> skipping\n");
xdgWipeHandle(&xdg);
}
}
load_ini_file(config_file);
#else
fprintf(stderr, "Warning: dunstrc parsing disabled. "
"Using STATIC_CONFIG is deprecated behavior.\n");
#endif
settings.per_monitor_dpi = option_get_bool(
"experimental",
"per_monitor_dpi", NULL, false,
""
);
settings.force_xinerama = option_get_bool(
"global",
"force_xinerama", "-force_xinerama", false,
"Force the use of the Xinerama extension"
);
settings.font = option_get_string(
"global",
"font", "-font/-fn", defaults.font,
"The font dunst should use."
);
{
// Check if allow_markup set
if (ini_is_set("global", "allow_markup")) {
bool allow_markup = option_get_bool(
"global",
"allow_markup", NULL, false,
"Allow markup in notifications"
);
settings.markup = (allow_markup ? MARKUP_FULL : MARKUP_STRIP);
fprintf(stderr, "Warning: 'allow_markup' is deprecated, please use 'markup' instead.\n");
}
char *c = option_get_string(
"global",
"markup", "-markup", NULL,
"Specify how markup should be handled"
);
//Use markup if set
//Use default if settings.markup not set yet
// (=>c empty&&!allow_markup)
if (c) {
settings.markup = parse_markup_mode(c);
} else if (!settings.markup) {
settings.markup = defaults.markup;
}
g_free(c);
}
settings.format = option_get_string(
"global",
"format", "-format", defaults.format,
"The format template for the notifications"
);
settings.sort = option_get_bool(
"global",
"sort", "-sort", defaults.sort,
"Sort notifications by urgency and date?"
);
settings.indicate_hidden = option_get_bool(
"global",
"indicate_hidden", "-indicate_hidden", defaults.indicate_hidden,
"Show how many notificaitons are hidden?"
);
settings.word_wrap = option_get_bool(
"global",
"word_wrap", "-word_wrap", defaults.word_wrap,
"Truncating long lines or do word wrap"
);
{
char *c = option_get_string(
"global",
"ellipsize", "-ellipsize", "",
"Ellipsize truncated lines on the start/middle/end"
);
if (strlen(c) == 0) {
settings.ellipsize = defaults.ellipsize;
} else if (strcmp(c, "start") == 0) {
settings.ellipsize = start;
} else if (strcmp(c, "middle") == 0) {
settings.ellipsize = middle;
} else if (strcmp(c, "end") == 0) {
settings.ellipsize = end;
} else {
fprintf(stderr, "Warning: unknown ellipsize value: \"%s\"\n", c);
settings.ellipsize = defaults.ellipsize;
}
g_free(c);
}
settings.ignore_newline = option_get_bool(
"global",
"ignore_newline", "-ignore_newline", defaults.ignore_newline,
"Ignore newline characters in notifications"
);
settings.idle_threshold = option_get_time(
"global",
"idle_threshold", "-idle_threshold", defaults.idle_threshold,
"Don't timeout notifications if user is longer idle than threshold"
);
settings.monitor = option_get_int(
"global",
"monitor", "-mon/-monitor", defaults.monitor,
"On which monitor should the notifications be displayed"
);
{
char *c = option_get_string(
"global",
"follow", "-follow", "",
"Follow mouse, keyboard or none?"
);
if (strlen(c) > 0) {
parse_follow_mode(c);
g_free(c);
}
}
settings.title = option_get_string(
"global",
"title", "-t/-title", defaults.title,
"Define the title of windows spawned by dunst."
);
settings.class = option_get_string(
"global",
"class", "-c/-class", defaults.class,
"Define the class of windows spawned by dunst."
);
settings.geom = option_get_string(
"global",
"geometry", "-geom/-geometry", defaults.geom,
"Geometry for the window"
);
settings.shrink = option_get_bool(
"global",
"shrink", "-shrink", defaults.shrink,
"Shrink window if it's smaller than the width"
);
settings.line_height = option_get_int(
"global",
"line_height", "-lh/-line_height", defaults.line_height,
"Add spacing between lines of text"
);
settings.notification_height = option_get_int(
"global",
"notification_height", "-nh/-notification_height", defaults.notification_height,
"Define height of the window"
);
{
char *c = option_get_string(
"global",
"alignment", "-align/-alignment", "",
"Text alignment left/center/right"
);
if (strlen(c) > 0) {
if (strcmp(c, "left") == 0)
settings.align = left;
else if (strcmp(c, "center") == 0)
settings.align = center;
else if (strcmp(c, "right") == 0)
settings.align = right;
else
fprintf(stderr,
"Warning: unknown alignment\n");
g_free(c);
}
}
settings.show_age_threshold = option_get_time(
"global",
"show_age_threshold", "-show_age_threshold", defaults.show_age_threshold,
"When should the age of the notification be displayed?"
);
settings.hide_duplicate_count = option_get_bool(
"global",
"hide_duplicate_count", "-hide_duplicate_count", false,
"Hide the count of merged notifications with the same content"
);
settings.sticky_history = option_get_bool(
"global",
"sticky_history", "-sticky_history", defaults.sticky_history,
"Don't timeout notifications popped up from history"
);
settings.history_length = option_get_int(
"global",
"history_length", "-history_length", defaults.history_length,
"Max amount of notifications kept in history"
);
settings.show_indicators = option_get_bool(
"global",
"show_indicators", "-show_indicators", defaults.show_indicators,
"Show indicators for actions \"(A)\" and URLs \"(U)\""
);
settings.separator_height = option_get_int(
"global",
"separator_height", "-sep_height/-separator_height", defaults.separator_height,
"height of the separator line"
);
settings.padding = option_get_int(
"global",
"padding", "-padding", defaults.padding,
"Padding between text and separator"
);
settings.h_padding = option_get_int(
"global",
"horizontal_padding", "-horizontal_padding", defaults.h_padding,
"horizontal padding"
);
settings.transparency = option_get_int(
"global",
"transparency", "-transparency", defaults.transparency,
"Transparency. range 0-100"
);
{
char *c = option_get_string(
"global",
"separator_color", "-sep_color/-separator_color", "",
"Color of the separator line (or 'auto')"
);
if (strlen(c) > 0) {
if (strcmp(c, "auto") == 0)
settings.sep_color = AUTO;
else if (strcmp(c, "foreground") == 0)
settings.sep_color = FOREGROUND;
else if (strcmp(c, "frame") == 0)
settings.sep_color = FRAME;
else {
settings.sep_color = CUSTOM;
settings.sep_custom_color_str = g_strdup(c);
}
}
g_free(c);
}
settings.stack_duplicates = option_get_bool(
"global",
"stack_duplicates", "-stack_duplicates", true,
"Merge multiple notifications with the same content"
);
settings.startup_notification = option_get_bool(
"global",
"startup_notification", "-startup_notification", false,
"print notification on startup"
);
settings.dmenu = option_get_path(
"global",
"dmenu", "-dmenu", defaults.dmenu,
"path to dmenu"
);
{
GError *error = NULL;
if (!g_shell_parse_argv(settings.dmenu, NULL, &settings.dmenu_cmd, &error)) {
fprintf(stderr, "Unable to parse dmenu command: %s\n", error->message);
fprintf(stderr, "dmenu functionality will be disabled.\n");
g_error_free(error);
settings.dmenu_cmd = NULL;
}
}
settings.browser = option_get_path(
"global",
"browser", "-browser", defaults.browser,
"path to browser"
);
{
char *c = option_get_string(
"global",
"icon_position", "-icon_position", "off",
"Align icons left/right/off"
);
if (strlen(c) > 0) {
if (strcmp(c, "left") == 0)
settings.icon_position = icons_left;
else if (strcmp(c, "right") == 0)
settings.icon_position = icons_right;
else if (strcmp(c, "off") == 0)
settings.icon_position = icons_off;
else
fprintf(stderr,
"Warning: unknown icon position: %s\n", c);
g_free(c);
}
}
settings.max_icon_size = option_get_int(
"global",
"max_icon_size", "-max_icon_size", defaults.max_icon_size,
"Scale larger icons down to this size, set to 0 to disable"
);
// If the deprecated icon_folders option is used,
// read it and generate its usage string.
if (ini_is_set("global", "icon_folders") || cmdline_is_set("-icon_folders")) {
settings.icon_path = option_get_string(
"global",
"icon_folders", "-icon_folders", defaults.icon_path,
"folders to default icons (deprecated, please use 'icon_path' instead)"
);
fprintf(stderr, "Warning: 'icon_folders' is deprecated, please use 'icon_path' instead.\n");
}
// Read value and generate usage string for icon_path.
// If icon_path is set, override icon_folder.
// if not, but icon_folder is set, use that instead of the compile time default.
settings.icon_path = option_get_string(
"global",
"icon_path", "-icon_path",
settings.icon_path ? settings.icon_path : defaults.icon_path,
"paths to default icons"
);
{
// Backwards compatibility with the legacy 'frame' section.
if (ini_is_set("frame", "width")) {
settings.frame_width = option_get_int(
"frame",
"width", NULL, defaults.frame_width,
"Width of frame around the window"
);
fprintf(stderr, "Warning: The frame section is deprecated, width has been renamed to frame_width and moved to the global section.\n");
}
settings.frame_width = option_get_int(
"global",
"frame_width", "-frame_width",
settings.frame_width ? settings.frame_width : defaults.frame_width,
"Width of frame around the window"
);
if (ini_is_set("frame", "color")) {
settings.frame_color = option_get_string(
"frame",
"color", NULL, defaults.frame_color,
"Color of the frame around the window"
);
fprintf(stderr, "Warning: The frame section is deprecated, color has been renamed to frame_color and moved to the global section.\n");
}
settings.frame_color = option_get_string(
"global",
"frame_color", "-frame_color",
settings.frame_color ? settings.frame_color : defaults.frame_color,
"Color of the frame around the window"
);
}
settings.lowbgcolor = option_get_string(
"urgency_low",
"background", "-lb", defaults.lowbgcolor,
"Background color for notifications with low urgency"
);
settings.lowfgcolor = option_get_string(
"urgency_low",
"foreground", "-lf", defaults.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[URG_LOW] = option_get_time(
"urgency_low",
"timeout", "-lto", defaults.timeouts[URG_LOW],
"Timeout for notifications with low urgency"
);
settings.icons[URG_LOW] = option_get_string(
"urgency_low",
"icon", "-li", defaults.icons[URG_LOW],
"Icon for notifications with low urgency"
);
settings.normbgcolor = option_get_string(
"urgency_normal",
"background", "-nb", defaults.normbgcolor,
"Background color for notifications with normal urgency"
);
settings.normfgcolor = option_get_string(
"urgency_normal",
"foreground", "-nf", defaults.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[URG_NORM] = option_get_time(
"urgency_normal",
"timeout", "-nto", defaults.timeouts[URG_NORM],
"Timeout for notifications with normal urgency"
);
settings.icons[URG_NORM] = option_get_string(
"urgency_normal",
"icon", "-ni", defaults.icons[URG_NORM],
"Icon for notifications with normal urgency"
);
settings.critbgcolor = option_get_string(
"urgency_critical",
"background", "-cb", defaults.critbgcolor,
"Background color for notifications with critical urgency"
);
settings.critfgcolor = option_get_string(
"urgency_critical",
"foreground", "-cf", defaults.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[URG_CRIT] = option_get_time(
"urgency_critical",
"timeout", "-cto", defaults.timeouts[URG_CRIT],
"Timeout for notifications with critical urgency"
);
settings.icons[URG_CRIT] = option_get_string(
"urgency_critical",
"icon", "-ci", defaults.icons[URG_CRIT],
"Icon for notifications with critical urgency"
);
settings.close_ks.str = option_get_string(
"shortcuts",
"close", "-key", defaults.close_ks.str,
"Shortcut for closing one notification"
);
settings.close_all_ks.str = option_get_string(
"shortcuts",
"close_all", "-all_key", defaults.close_all_ks.str,
"Shortcut for closing all notifications"
);
settings.history_ks.str = option_get_string(
"shortcuts",
"history", "-history_key", defaults.history_ks.str,
"Shortcut to pop the last notification from history"
);
settings.context_ks.str = option_get_string(
"shortcuts",
"context", "-context_key", defaults.context_ks.str,
"Shortcut for context menu"
);
settings.print_notifications = cmdline_get_bool(
"-print", false,
"Print notifications to cmdline (DEBUG)"
);
settings.always_run_script = option_get_bool(
"global",
"always_run_script", "-always_run_script", true,
"Always run rule-defined scripts, even if the notification is suppressed with format = \"\"."
);
{
char *c = option_get_string(
"global",
"centering", "-centering", "off",
"Align notifications on screen off/horizontal/vertical/both"
);
if (strcmp(c, "off") == 0)
settings.centering = CENTERING_OFF;
else if (strcmp(c, "horizontal") == 0)
settings.centering = CENTERING_HORIZONTAL;
else if (strcmp(c, "vertical") == 0)
settings.centering = CENTERING_VERTICAL;
else if (strcmp(c, "both") == 0)
settings.centering = CENTERING_BOTH;
else
fprintf(stderr,
"Warning: unknown centering option: %s\n", c);
g_free(c);
}
/* push hardcoded default rules into rules list */
for (int i = 0; i < G_N_ELEMENTS(default_rules); i++) {
rules = g_slist_insert(rules, &(default_rules[i]), -1);
}
const char *cur_section = NULL;
for (;;) {
cur_section = next_section(cur_section);
if (!cur_section)
break;
if (strcmp(cur_section, "global") == 0
|| strcmp(cur_section, "frame") == 0
|| strcmp(cur_section, "experimental") == 0
|| strcmp(cur_section, "shortcuts") == 0
|| strcmp(cur_section, "urgency_low") == 0
|| strcmp(cur_section, "urgency_normal") == 0
|| strcmp(cur_section, "urgency_critical") == 0)
continue;
/* check for existing rule with same name */
rule_t *r = NULL;
for (GSList *iter = rules; iter; iter = iter->next) {
rule_t *match = iter->data;
if (match->name &&
strcmp(match->name, cur_section) == 0)
r = match;
}
if (r == NULL) {
r = g_malloc(sizeof(rule_t));
rule_init(r);
rules = g_slist_insert(rules, r, -1);
}
r->name = g_strdup(cur_section);
r->appname = ini_get_string(cur_section, "appname", r->appname);
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_time(cur_section, "timeout", r->timeout);
{
char *c = ini_get_string(
cur_section,
"markup", NULL
);
if (c != NULL) {
r->markup = parse_markup_mode(c);
g_free(c);
}
}
r->urgency = ini_get_urgency(cur_section, "urgency", r->urgency);
r->msg_urgency = ini_get_urgency(cur_section, "msg_urgency", r->msg_urgency);
r->fg = ini_get_string(cur_section, "foreground", r->fg);
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->history_ignore = ini_get_bool(cur_section, "history_ignore", r->history_ignore);
r->match_transient = ini_get_bool(cur_section, "match_transient", r->match_transient);
r->set_transient = ini_get_bool(cur_section, "set_transient", r->set_transient);
r->script = ini_get_path(cur_section, "script", NULL);
}
#ifndef STATIC_CONFIG
if (config_file) {
fclose(config_file);
free_ini();
xdgWipeHandle(&xdg);
}
#endif
}
/* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */

@ -0,0 +1,84 @@
/* copyright 2013 Sascha Kruse and contributors (see LICENSE for licensing information) */
#ifndef DUNST_SETTINGS_H
#define DUNST_SETTINGS_H
#include <stdbool.h>
#include "x11/x.h"
enum alignment { left, center, right };
enum ellipsize { start, middle, end };
enum icon_position_t { icons_left, icons_right, icons_off };
enum separator_color { FOREGROUND, AUTO, FRAME, CUSTOM };
enum follow_mode { FOLLOW_NONE, FOLLOW_MOUSE, FOLLOW_KEYBOARD };
enum markup_mode { MARKUP_NULL, MARKUP_NO, MARKUP_STRIP, MARKUP_FULL };
enum centering { CENTERING_OFF, CENTERING_HORIZONTAL, CENTERING_VERTICAL, CENTERING_BOTH };
typedef struct _settings {
bool print_notifications;
bool per_monitor_dpi;
enum markup_mode markup;
bool stack_duplicates;
bool hide_duplicate_count;
char *font;
char *normbgcolor;
char *normfgcolor;
char *normframecolor;
char *critbgcolor;
char *critfgcolor;
char *critframecolor;
char *lowbgcolor;
char *lowfgcolor;
char *lowframecolor;
char *format;
gint64 timeouts[3];
char *icons[3];
unsigned int transparency;
char *geom;
enum centering centering;
char *title;
char *class;
int shrink;
int sort;
int indicate_hidden;
gint64 idle_threshold;
gint64 show_age_threshold;
enum alignment align;
int sticky_history;
int history_length;
int show_indicators;
int word_wrap;
enum ellipsize ellipsize;
int ignore_newline;
int line_height;
int notification_height;
int separator_height;
int padding;
int h_padding;
enum separator_color sep_color;
char *sep_custom_color_str;
int frame_width;
char *frame_color;
int startup_notification;
int monitor;
char *dmenu;
char **dmenu_cmd;
char *browser;
enum icon_position_t icon_position;
int max_icon_size;
char *icon_path;
enum follow_mode f_mode;
bool always_run_script;
keyboard_shortcut close_ks;
keyboard_shortcut close_all_ks;
keyboard_shortcut history_ks;
keyboard_shortcut context_ks;
bool force_xinerama;
} settings_t;
extern settings_t settings;
void load_settings(char *cmdline_config_path);
#endif
/* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */

@ -0,0 +1,173 @@
/* copyright 2013 Sascha Kruse and contributors (see LICENSE for licensing information) */
#include "utils.h"
#include <assert.h>
#include <errno.h>
#include <glib.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.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 *string_replace_at(char *buf, int pos, int len, const char *repl)
{
char *tmp;
int size, buf_len, repl_len;
buf_len = strlen(buf);
repl_len = strlen(repl);
size = (buf_len - len) + repl_len + 1;
if (repl_len <= len) {
tmp = buf;
} else {
tmp = g_malloc(size);
}
memcpy(tmp, buf, pos);
memcpy(tmp + pos, repl, repl_len);
memmove(tmp + pos + repl_len, buf + pos + len, buf_len - (pos + len) + 1);
if (tmp != buf) {
g_free(buf);
}
return tmp;
}
char *string_replace(const char *needle, const char *replacement, char *haystack)
{
char *start;
start = strstr(haystack, needle);
if (start == NULL) {
return haystack;
}
return string_replace_at(haystack, (start - haystack), strlen(needle), replacement);
}
char *string_replace_all(const char *needle, const char *replacement, char *haystack)
{
char *start;
int needle_pos;
int needle_len, repl_len;
needle_len = strlen(needle);
if (needle_len == 0) {
return haystack;
}
start = strstr(haystack, needle);
repl_len = strlen(replacement);
while (start != NULL) {
needle_pos = start - haystack;
haystack = string_replace_at(haystack, needle_pos, needle_len, replacement);
start = strstr(haystack + needle_pos + repl_len, needle);
}
return haystack;
}
char *string_append(char *a, const char *b, const char *sep)
{
if (!a || *a == '\0') {
g_free(a);
return g_strdup(b);
}
if (!b || *b == '\0')
return a;
char *new;
if (!sep)
new = g_strconcat(a, b, NULL);
else
new = g_strconcat(a, sep, b, NULL);
g_free(a);
return new;
}
void string_strip_delimited(char *str, char a, char b)
{
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];
}
}
str[iwrite] = 0;
}
char *string_to_path(char *string)
{
if (string && 0 == strncmp(string, "~/", 2)) {
char *home = g_strconcat(getenv("HOME"), "/", NULL);
string = string_replace("~/", home, string);
g_free(home);
}
return string;
}
gint64 string_to_time(const char *string)
{
assert(string);
errno = 0;
char *endptr;
gint64 val = strtoll(string, &endptr, 10);
if (errno != 0) {
fprintf(stderr, "ERROR: Time: '%s': %s.\n", string, strerror(errno));
return 0;
} else if (string == endptr) {
fprintf(stderr, "ERROR: Time: No digits found.\n");
return 0;
} else if (errno != 0 && val == 0) {
fprintf(stderr, "ERROR: Time: '%s' unknown error.\n", string);
return 0;
} else if (errno == 0 && !*endptr) {
return val * G_USEC_PER_SEC;
}
// endptr may point to a separating space
while (*endptr == ' ')
endptr++;
if (0 == strncmp(endptr, "ms", 2))
return val * 1000;
else if (0 == strncmp(endptr, "s", 1))
return val * G_USEC_PER_SEC;
else if (0 == strncmp(endptr, "m", 1))
return val * G_USEC_PER_SEC * 60;
else if (0 == strncmp(endptr, "h", 1))
return val * G_USEC_PER_SEC * 60 * 60;
else if (0 == strncmp(endptr, "d", 1))
return val * G_USEC_PER_SEC * 60 * 60 * 24;
else
return 0;
}
void die(char *text, int exit_value)
{
fputs(text, stderr);
exit(exit_value);
}
/* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */

@ -0,0 +1,34 @@
/* copyright 2013 Sascha Kruse and contributors (see LICENSE for licensing information) */
#ifndef DUNST_UTILS_H
#define DUNST_UTILS_H
#include <glib.h>
/* replace all occurrences of the character needle with the character replacement in haystack */
char *string_replace_char(char needle, char replacement, char *haystack);
/* replace all occurrences of needle with replacement in haystack */
char *string_replace_all(const char *needle, const char *replacement, char *haystack);
/* replace <len> characters with <repl> at position <pos> of the string <buf> */
char *string_replace_at(char *buf, int pos, int len, const char *repl);
/* replace needle with replacement in haystack */
char *string_replace(const char *needle, const char *replacement, char *haystack);
char *string_append(char *a, const char *b, const char *sep);
/* 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);
/* replace tilde and path-specific values with its equivalents */
char *string_to_path(char *string);
/* convert time units (ms, s, m) to internal gint64 microseconds */
gint64 string_to_time(const char *string);
#endif
/* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */

@ -0,0 +1,332 @@
#include "screen.h"
#include <X11/X.h>
#include <X11/Xatom.h>
#include <X11/Xlib.h>
#include <X11/Xresource.h>
#include <X11/extensions/Xinerama.h>
#include <X11/extensions/Xrandr.h>
#include <X11/extensions/randr.h>
#include <assert.h>
#include <glib.h>
#include <locale.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "src/settings.h"
#include "x.h"
screen_info *screens;
int screens_len;
bool dunst_follow_errored = false;
int randr_event_base = 0;
static int randr_major_version = 0;
static int randr_minor_version = 0;
void randr_init();
void randr_update();
void xinerama_update();
void screen_update_fallback();
static void x_follow_setup_error_handler(void);
static int x_follow_tear_down_error_handler(void);
static int FollowXErrorHandler(Display *display, XErrorEvent *e);
static Window get_focused_window(void);
static double get_xft_dpi_value()
{
static double dpi = -1;
//Only run this once, we don't expect dpi changes during runtime
if (dpi <= -1) {
XrmInitialize();
char *xRMS = XResourceManagerString(xctx.dpy);
if (xRMS == NULL) {
dpi = 0;
return 0;
}
XrmDatabase xDB = XrmGetStringDatabase(xRMS);
char *xrmType;
XrmValue xrmValue;
if (XrmGetResource(xDB, "Xft.dpi", "Xft.dpi", &xrmType, &xrmValue)) {
dpi = strtod(xrmValue.addr, NULL);
} else {
dpi = 0;
}
XrmDestroyDatabase(xDB);
}
return dpi;
}
void init_screens()
{
if (!settings.force_xinerama) {
randr_init();
randr_update();
} else {
xinerama_update();
}
}
void alloc_screen_ar(int n)
{
assert(n > 0);
if (n <= screens_len) return;
screens = g_realloc(screens, n * sizeof(screen_info));
memset(screens, 0, n * sizeof(screen_info));
screens_len = n;
}
void randr_init()
{
int randr_error_base = 0;
if (!XRRQueryExtension(xctx.dpy, &randr_event_base, &randr_error_base)) {
fprintf(stderr, "Could not initialize the RandR extension, falling back to single monitor mode.\n");
return;
}
XRRQueryVersion(xctx.dpy, &randr_major_version, &randr_minor_version);
XRRSelectInput(xctx.dpy, RootWindow(xctx.dpy, DefaultScreen(xctx.dpy)), RRScreenChangeNotifyMask);
}
void randr_update()
{
if (randr_major_version < 1
|| (randr_major_version == 1 && randr_minor_version < 5)) {
fprintf(stderr, "Server RandR version too low (%i.%i). Falling back to single monitor mode\n",
randr_major_version,
randr_minor_version);
screen_update_fallback();
return;
}
int n = 0;
XRRMonitorInfo *m = XRRGetMonitors(xctx.dpy, RootWindow(xctx.dpy, DefaultScreen(xctx.dpy)), true, &n);
if (n < 1) {
fprintf(stderr, "Get monitors reported %i monitors, falling back to single monitor mode\n", n);
screen_update_fallback();
return;
}
assert(m);
alloc_screen_ar(n);
for (int i = 0; i < n; i++) {
screens[i].dim.x = m[i].x;
screens[i].dim.y = m[i].y;
screens[i].dim.w = m[i].width;
screens[i].dim.h = m[i].height;
screens[i].dim.mmh = m[i].mheight;
}
XRRFreeMonitors(m);
}
static int autodetect_dpi(screen_info *scr)
{
return (double)scr->dim.h * 25.4 / (double)scr->dim.mmh;
}
void screen_check_event(XEvent event)
{
if (event.type == randr_event_base + RRScreenChangeNotify)
randr_update();
}
void xinerama_update()
{
int n;
XineramaScreenInfo *info = XineramaQueryScreens(xctx.dpy, &n);
if (!info) {
fprintf(stderr, "(Xinerama) Could not get screen info, falling back to single monitor mode\n");
screen_update_fallback();
return;
}
alloc_screen_ar(n);
for (int i = 0; i < n; i++) {
screens[i].dim.x = info[i].x_org;
screens[i].dim.y = info[i].y_org;
screens[i].dim.h = info[i].height;
screens[i].dim.w = info[i].width;
}
XFree(info);
}
void screen_update_fallback()
{
alloc_screen_ar(1);
int screen;
if (settings.monitor >= 0)
screen = settings.monitor;
else
screen = DefaultScreen(xctx.dpy);
screens[0].dim.w = DisplayWidth(xctx.dpy, screen);
screens[0].dim.h = DisplayHeight(xctx.dpy, screen);
}
/*
* Select the screen on which the Window
* should be displayed.
*/
screen_info *get_active_screen()
{
int ret = 0;
if (settings.monitor > 0 && settings.monitor < screens_len) {
ret = settings.monitor;
goto sc_cleanup;
}
x_follow_setup_error_handler();
if (settings.f_mode == FOLLOW_NONE) {
ret = XDefaultScreen(xctx.dpy);
goto sc_cleanup;
} else {
int x, y;
assert(settings.f_mode == FOLLOW_MOUSE
|| settings.f_mode == FOLLOW_KEYBOARD);
Window root =
RootWindow(xctx.dpy, DefaultScreen(xctx.dpy));
if (settings.f_mode == FOLLOW_MOUSE) {
int dummy;
unsigned int dummy_ui;
Window dummy_win;
XQueryPointer(xctx.dpy,
root,
&dummy_win,
&dummy_win,
&x,
&y,
&dummy,
&dummy,
&dummy_ui);
}
if (settings.f_mode == FOLLOW_KEYBOARD) {
Window focused = get_focused_window();
if (focused == 0) {
/* something went wrong. Fallback to default */
ret = XDefaultScreen(xctx.dpy);
goto sc_cleanup;
}
Window child_return;
XTranslateCoordinates(xctx.dpy, focused, root,
0, 0, &x, &y, &child_return);
}
for (int i = 0; i < screens_len; i++) {
if (INRECT(x, y, screens[i].dim.x, screens[i].dim.y,
screens[i].dim.w, screens[i].dim.h)) {
ret = i;
}
}
if (ret > 0)
goto sc_cleanup;
/* something seems to be wrong. Fallback to default */
ret = XDefaultScreen(xctx.dpy);
goto sc_cleanup;
}
sc_cleanup:
x_follow_tear_down_error_handler();
assert(screens);
assert(ret >= 0 && ret < screens_len);
return &screens[ret];
}
double get_dpi_for_screen(screen_info *scr)
{
double dpi = 0;
if ((!settings.force_xinerama && settings.per_monitor_dpi &&
(dpi = autodetect_dpi(scr))))
return dpi;
else if ((dpi = get_xft_dpi_value()))
return dpi;
else
return 96;
}
/*
* Return the window that currently has
* the keyboard focus.
*/
static Window get_focused_window(void)
{
Window focused = 0;
Atom type;
int format;
unsigned long nitems, bytes_after;
unsigned char *prop_return = NULL;
Window root = RootWindow(xctx.dpy, DefaultScreen(xctx.dpy));
Atom netactivewindow =
XInternAtom(xctx.dpy, "_NET_ACTIVE_WINDOW", false);
XGetWindowProperty(xctx.dpy,
root,
netactivewindow,
0L,
sizeof(Window),
false,
XA_WINDOW,
&type,
&format,
&nitems,
&bytes_after,
&prop_return);
if (prop_return) {
focused = *(Window *)prop_return;
XFree(prop_return);
}
return focused;
}
static void x_follow_setup_error_handler(void)
{
dunst_follow_errored = false;
XFlush(xctx.dpy);
XSetErrorHandler(FollowXErrorHandler);
}
static int x_follow_tear_down_error_handler(void)
{
XFlush(xctx.dpy);
XSync(xctx.dpy, false);
XSetErrorHandler(NULL);
return dunst_follow_errored;
}
static int FollowXErrorHandler(Display *display, XErrorEvent *e)
{
dunst_follow_errored = true;
char err_buf[BUFSIZ];
XGetErrorText(display, e->error_code, err_buf, BUFSIZ);
fputs(err_buf, stderr);
fputs("\n", stderr);
return 0;
}
/* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */

@ -0,0 +1,31 @@
/* copyright 2013 Sascha Kruse and contributors (see LICENSE for licensing information) */
#ifndef DUNST_SCREEN_H
#define DUNST_SCREEN_H
#include <X11/Xlib.h>
#define INRECT(x,y,rx,ry,rw,rh) ((x) >= (rx) && (x) < (rx)+(rw) && (y) >= (ry) && (y) < (ry)+(rh))
typedef struct _dimension_t {
int x;
int y;
unsigned int h;
unsigned int mmh;
unsigned int w;
int mask;
int negative_width;
} dimension_t;
typedef struct _screen_info {
int scr;
dimension_t dim;
} screen_info;
void init_screens();
void screen_check_event(XEvent event);
screen_info *get_active_screen();
double get_dpi_for_screen(screen_info *scr);
#endif
/* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */

File diff suppressed because it is too large Load Diff

@ -0,0 +1,67 @@
/* copyright 2013 Sascha Kruse and contributors (see LICENSE for licensing information) */
#ifndef DUNST_X_H
#define DUNST_X_H
#include <X11/X.h>
#include <X11/Xlib.h>
#include <X11/extensions/scrnsaver.h>
#include <glib.h>
#include <stdbool.h>
#include "screen.h"
#define BUTTONMASK (ButtonPressMask|ButtonReleaseMask)
#define FONT_HEIGHT_BORDER 2
#define DEFFONT "Monospace-11"
typedef struct _keyboard_shortcut {
const char *str;
KeyCode code;
KeySym sym;
KeySym mask;
bool is_valid;
} keyboard_shortcut;
typedef struct _xctx {
Atom utf8;
Display *dpy;
Window win;
bool visible;
dimension_t geometry;
const char *color_strings[3][3];
XScreenSaverInfo *screensaver_info;
dimension_t window_dim;
unsigned long sep_custom_col;
} xctx_t;
typedef struct _color_t {
double r;
double g;
double b;
} color_t;
extern xctx_t xctx;
/* window */
void x_win_draw(void);
void x_win_hide(void);
void x_win_show(void);
/* shortcut */
void x_shortcut_init(keyboard_shortcut *shortcut);
void x_shortcut_ungrab(keyboard_shortcut *ks);
int x_shortcut_grab(keyboard_shortcut *ks);
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);
gboolean x_mainloop_fd_check(GSource *source);
gboolean x_mainloop_fd_prepare(GSource *source, gint *timeout);
#endif
/* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */

@ -0,0 +1,41 @@
#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""
[path]
expand_tilde = ~/.path/to/tilde
[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

@ -0,0 +1,49 @@
[global]
font = Monospace 8
allow_markup = yes
format = "<b>%s</b>\n%b"
sort = yes
indicate_hidden = yes
alignment = left
show_age_threshold = 60
word_wrap = yes
ignore_newline = no
geometry = "300x5-30+20"
transparency = 0
idle_threshold = 120
monitor = 0
follow = mouse
sticky_history = yes
line_height = 0
separator_height = 2
padding = 8
horizontal_padding = 8
separator_color = frame
startup_notification = false
dmenu = /usr/bin/dmenu -p dunst
browser = /usr/bin/firefox -new-tab
[frame]
width = 3
color = "#aaaaaa"
[shortcuts]
close = ctrl+space
close_all = ctrl+shift+space
history = ctrl+grave
context = ctrl+shift+period
[urgency_low]
background = "#222222"
foreground = "#888888"
timeout = 10
[urgency_normal]
background = "#285577"
foreground = "#ffffff"
timeout = 10
[urgency_critical]
background = "#900000"
foreground = "#ffffff"
timeout = 0

@ -0,0 +1,49 @@
[global]
font = Monospace 8
allow_markup = yes
format = "<b>%s</b>\n%b"
sort = yes
indicate_hidden = yes
alignment = left
show_age_threshold = 60
word_wrap = yes
ignore_newline = yes
geometry = "200x0-30+20"
transparency = 0
idle_threshold = 120
monitor = 0
follow = mouse
sticky_history = yes
line_height = 0
separator_height = 2
padding = 8
horizontal_padding = 8
separator_color = frame
startup_notification = false
dmenu = /usr/bin/dmenu -p dunst
browser = /usr/bin/firefox -new-tab
[frame]
width = 3
color = "#aaaaaa"
[shortcuts]
close = ctrl+space
close_all = ctrl+shift+space
history = ctrl+grave
context = ctrl+shift+period
[urgency_low]
background = "#222222"
foreground = "#888888"
timeout = 10
[urgency_normal]
background = "#285577"
foreground = "#ffffff"
timeout = 10
[urgency_critical]
background = "#900000"
foreground = "#ffffff"
timeout = 0

@ -0,0 +1,49 @@
[global]
font = Monospace 8
allow_markup = yes
format = "<b>%s</b>\n%b"
sort = yes
indicate_hidden = yes
alignment = left
show_age_threshold = 60
word_wrap = no
ignore_newline = yes
geometry = "250x0-30+20"
transparency = 0
idle_threshold = 120
monitor = 0
follow = mouse
sticky_history = yes
line_height = 0
separator_height = 2
padding = 8
horizontal_padding = 8
separator_color = frame
startup_notification = false
dmenu = /usr/bin/dmenu -p dunst
browser = /usr/bin/firefox -new-tab
[frame]
width = 3
color = "#aaaaaa"
[shortcuts]
close = ctrl+space
close_all = ctrl+shift+space
history = ctrl+grave
context = ctrl+shift+period
[urgency_low]
background = "#222222"
foreground = "#888888"
timeout = 10
[urgency_normal]
background = "#285577"
foreground = "#ffffff"
timeout = 10
[urgency_critical]
background = "#900000"
foreground = "#ffffff"
timeout = 0

@ -0,0 +1,49 @@
[global]
font = Monospace 8
allow_markup = yes
format = "%s\n%b"
sort = yes
indicate_hidden = yes
alignment = left
show_age_threshold = 60
word_wrap = yes
ignore_newline = no
geometry = "300x5-30+20"
transparency = 0
idle_threshold = 120
monitor = 0
follow = mouse
sticky_history = yes
line_height = 0
separator_height = 2
padding = 8
horizontal_padding = 8
separator_color = frame
startup_notification = false
dmenu = /usr/bin/dmenu -p dunst
browser = /usr/bin/firefox -new-tab
[frame]
width = 3
color = "#aaaaaa"
[shortcuts]
close = ctrl+space
close_all = ctrl+shift+space
history = ctrl+grave
context = ctrl+shift+period
[urgency_low]
background = "#222222"
foreground = "#888888"
timeout = 10
[urgency_normal]
background = "#285577"
foreground = "#ffffff"
timeout = 10
[urgency_critical]
background = "#900000"
foreground = "#ffffff"
timeout = 0

@ -0,0 +1,49 @@
[global]
font = Monospace 8
allow_markup = no
format = "<b>%s</b>\n<i>%b</i>"
sort = yes
indicate_hidden = yes
alignment = left
show_age_threshold = 60
word_wrap = yes
ignore_newline = no
geometry = "300x5-30+20"
transparency = 0
idle_threshold = 120
monitor = 0
follow = mouse
sticky_history = yes
line_height = 0
separator_height = 2
padding = 8
horizontal_padding = 8
separator_color = frame
startup_notification = false
dmenu = /usr/bin/dmenu -p dunst
browser = /usr/bin/firefox -new-tab
[frame]
width = 3
color = "#aaaaaa"
[shortcuts]
close = ctrl+space
close_all = ctrl+shift+space
history = ctrl+grave
context = ctrl+shift+period
[urgency_low]
background = "#222222"
foreground = "#888888"
timeout = 10
[urgency_normal]
background = "#285577"
foreground = "#ffffff"
timeout = 10
[urgency_critical]
background = "#900000"
foreground = "#ffffff"
timeout = 0

@ -0,0 +1,49 @@
[global]
font = Monospace-10
allow_markup = yes
format = "%s\n%b"
sort = yes
indicate_hidden = yes
alignment = left
show_age_threshold = 60
word_wrap = no
ignore_newline = no
geometry = "300x5-30+20"
transparency = 0
idle_threshold = 120
monitor = 0
follow = mouse
sticky_history = yes
line_height = 0
separator_height = 2;
padding = 8
horizontal_padding = 8
separator_color = frame
startup_notification = false
dmenu = /usr/bin/dmenu -p dunst:
browser = /usr/bin/firefox -new-tab
[frame]
width = 3
color = "#aaaaaa"
[shortcuts]
close = ctrl+space
close_all = ctrl+shift+space
history = ctrl+grave
context = ctrl+shift+period
[urgency_low]
background = "#222222"
foreground = "#888888"
timeout = 10
[urgency_normal]
background = "#285577"
foreground = "#ffffff"
timeout = 10
[urgency_critical]
background = "#900000"
foreground = "#ffffff"
timeout = 0

@ -0,0 +1,53 @@
[global]
font = Monospace 8
allow_markup = yes
format = "<b>%s</b>\n%b"
sort = yes
indicate_hidden = yes
alignment = left
show_age_threshold = 60
word_wrap = yes
ignore_newline = no
geometry = "300x5-30+20"
transparency = 0
idle_threshold = 120
monitor = 0
follow = mouse
sticky_history = yes
line_height = 0
separator_height = 2
padding = 8
horizontal_padding = 8
separator_color = frame
startup_notification = false
dmenu = /usr/bin/dmenu -p dunst
browser = /usr/bin/firefox -new-tab
[frame]
width = 3
color = "#aaaaaa"
[shortcuts]
close = ctrl+space
close_all = ctrl+shift+space
history = ctrl+grave
context = ctrl+shift+period
[urgency_low]
background = "#222222"
foreground = "#888888"
timeout = 10
[urgency_normal]
background = "#285577"
foreground = "#ffffff"
timeout = 10
[urgency_critical]
background = "#900000"
foreground = "#ffffff"
timeout = 0
[script test]
summary = trigger
script = script_test.sh

@ -0,0 +1,49 @@
[global]
font = Monospace 8
allow_markup = yes
format = "<b>%s</b>\n%b"
sort = yes
indicate_hidden = yes
alignment = left
show_age_threshold = 2
word_wrap = yes
ignore_newline = no
geometry = "300x5-30+20"
transparency = 0
idle_threshold = 120
monitor = 0
follow = mouse
sticky_history = yes
line_height = 0
separator_height = 2
padding = 8
horizontal_padding = 8
separator_color = frame
startup_notification = false
dmenu = /usr/bin/dmenu -p dunst
browser = /usr/bin/firefox -new-tab
[frame]
width = 3
color = "#aaaaaa"
[shortcuts]
close = ctrl+space
close_all = ctrl+shift+space
history = ctrl+grave
context = ctrl+shift+period
[urgency_low]
background = "#222222"
foreground = "#888888"
timeout = 10
[urgency_normal]
background = "#285577"
foreground = "#ffffff"
timeout = 10
[urgency_critical]
background = "#900000"
foreground = "#ffffff"
timeout = 0

@ -0,0 +1,3 @@
#!/bin/bash
notify-send "Success" "ooooh yeah"

@ -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

File diff suppressed because it is too large Load Diff

@ -0,0 +1,57 @@
#include "greatest.h"
#include <stdbool.h>
#include <glib.h>
#include "src/markup.h"
TEST test_markup_strip(void)
{
char *ptr;
ASSERT_STR_EQ("&quot;", (ptr=markup_strip(g_strdup("&amp;quot;"))));
g_free(ptr);
ASSERT_STR_EQ("&apos;", (ptr=markup_strip(g_strdup("&amp;apos;"))));
g_free(ptr);
ASSERT_STR_EQ("&lt;", (ptr=markup_strip(g_strdup("&amp;lt;"))));
g_free(ptr);
ASSERT_STR_EQ("&gt;", (ptr=markup_strip(g_strdup("&amp;gt;"))));
g_free(ptr);
ASSERT_STR_EQ("&amp;", (ptr=markup_strip(g_strdup("&amp;amp;"))));
g_free(ptr);
ASSERT_STR_EQ(">A ", (ptr=markup_strip(g_strdup(">A <img> <string"))));
g_free(ptr);
PASS();
}
TEST test_markup_transform(void)
{
char *ptr;
settings.ignore_newline = false;
ASSERT_STR_EQ("&lt;i&gt;foo&lt;/i&gt;&lt;br&gt;bar\nbaz", (ptr=markup_transform(g_strdup("<i>foo</i><br>bar\nbaz"), MARKUP_NO)));
g_free(ptr);
ASSERT_STR_EQ("foo\nbar\nbaz", (ptr=markup_transform(g_strdup("<i>foo</i><br>bar\nbaz"), MARKUP_STRIP)));
g_free(ptr);
ASSERT_STR_EQ("<i>foo</i>\nbar\nbaz", (ptr=markup_transform(g_strdup("<i>foo</i><br>bar\nbaz"), MARKUP_FULL)));
g_free(ptr);
settings.ignore_newline = true;
ASSERT_STR_EQ("&lt;i&gt;foo&lt;/i&gt;&lt;br&gt;bar baz", (ptr=markup_transform(g_strdup("<i>foo</i><br>bar\nbaz"), MARKUP_NO)));
g_free(ptr);
ASSERT_STR_EQ("foo bar baz", (ptr=markup_transform(g_strdup("<i>foo</i><br>bar\nbaz"), MARKUP_STRIP)));
g_free(ptr);
ASSERT_STR_EQ("<i>foo</i> bar baz", (ptr=markup_transform(g_strdup("<i>foo</i><br>bar\nbaz"), MARKUP_FULL)));
g_free(ptr);
PASS();
}
SUITE(suite_markup)
{
RUN_TEST(test_markup_strip);
RUN_TEST(test_markup_transform);
}
/* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */

@ -0,0 +1,125 @@
#include "greatest.h"
#include "src/notification.h"
#include "src/option_parser.h"
#include "src/settings.h"
#include <glib.h>
TEST test_notification_is_duplicate_field(char **field, notification *a,
notification *b)
{
ASSERT(notification_is_duplicate(a, b));
char *tmp = *field;
(*field) = "Something different";
ASSERT_FALSE(notification_is_duplicate(a, b));
(*field) = tmp;
PASS();
}
TEST test_notification_is_duplicate(void *notifications)
{
notification **n = (notification**)notifications;
notification *a = n[0];
notification *b = n[1];
ASSERT(notification_is_duplicate(a, b));
CHECK_CALL(test_notification_is_duplicate_field(&(b->appname), a, b));
CHECK_CALL(test_notification_is_duplicate_field(&(b->summary), a, b));
CHECK_CALL(test_notification_is_duplicate_field(&(b->body), a, b));
ASSERT(notification_is_duplicate(a, b));
char *tmp = b->icon;
enum icon_position_t icon_setting_tmp = settings.icon_position;
b->icon = "Test1";
settings.icon_position = icons_off;
ASSERT(notification_is_duplicate(a, b));
//Setting pointer to a random value since we are checking for null
b->raw_icon = (RawImage*)0xff;
ASSERT(notification_is_duplicate(a, b));
b->raw_icon = NULL;
settings.icon_position = icons_left;
ASSERT_FALSE(notification_is_duplicate(a, b));
b->raw_icon = (RawImage*)0xff;
ASSERT_FALSE(notification_is_duplicate(a, b));
b->raw_icon = NULL;
settings.icon_position = icons_right;
ASSERT_FALSE(notification_is_duplicate(a, b));
b->raw_icon = (RawImage*)0xff;
ASSERT_FALSE(notification_is_duplicate(a, b));
b->raw_icon = NULL;
b->icon = tmp;
settings.icon_position = icon_setting_tmp;
ASSERT(notification_is_duplicate(a, b));
b->urgency = URG_LOW;
ASSERT_FALSE(notification_is_duplicate(a, b));
b->urgency = URG_NORM;
ASSERT(notification_is_duplicate(a, b));
b->urgency = URG_CRIT;
ASSERT_FALSE(notification_is_duplicate(a, b));
PASS();
}
TEST test_notification_replace_single_field(void)
{
char *str = g_malloc(128 * sizeof(char));
char *substr = NULL;
strcpy(str, "Markup %a preserved");
substr = strchr(str, '%');
notification_replace_single_field(&str, &substr, "and &amp; <i>is</i>", MARKUP_FULL);
ASSERT_STR_EQ("Markup and &amp; <i>is</i> preserved", str);
ASSERT_EQ(26, substr - str);
strcpy(str, "Markup %a escaped");
substr = strchr(str, '%');
notification_replace_single_field(&str, &substr, "and & <i>is</i>", MARKUP_NO);
ASSERT_STR_EQ("Markup and &amp; &lt;i&gt;is&lt;/i&gt; escaped", str);
ASSERT_EQ(38, substr - str);
strcpy(str, "Markup %a");
substr = strchr(str, '%');
notification_replace_single_field(&str, &substr, "<i>is removed</i> and & escaped", MARKUP_STRIP);
ASSERT_STR_EQ("Markup is removed and &amp; escaped", str);
ASSERT_EQ(35, substr - str);
g_free(str);
PASS();
}
SUITE(suite_notification)
{
cmdline_load(0, NULL);
load_settings("data/dunstrc.default");
notification *a = notification_create();
a->appname = "Test";
a->summary = "Summary";
a->body = "Body";
a->icon = "Icon";
a->urgency = URG_NORM;
notification *b = notification_create();
memcpy(b, a, sizeof(*b));
//2 equal notifications to be passed for duplicate checking,
notification *n[2] = {a, b};
RUN_TEST1(test_notification_is_duplicate, (void*) n);
g_free(a);
g_free(b);
RUN_TEST(test_notification_replace_single_field);
}
/* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */

@ -0,0 +1,319 @@
#include "greatest.h"
#include <stdbool.h>
#include <glib.h>
#include "src/option_parser.h"
TEST test_next_section(void)
{
const char *section = NULL;
ASSERT_STR_EQ("bool", (section = next_section(section)));
ASSERT_STR_EQ("string", (section = next_section(section)));
ASSERT_STR_EQ("path", (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";
char *ptr;
ASSERT_STR_EQ("A simple string", (ptr = ini_get_string(string_section, "simple", "")));
free(ptr);
ASSERT_STR_EQ("A quoted string", (ptr = ini_get_string(string_section, "quoted", "")));
free(ptr);
ASSERT_STR_EQ("A string \"with quotes\"", (ptr = ini_get_string(string_section, "quoted_with_quotes", "")));
free(ptr);
ASSERT_STR_EQ("default value", (ptr = ini_get_string(string_section, "nonexistent", "default value")));
free(ptr);
PASS();
}
TEST test_ini_get_path(void)
{
char *section = "path";
char *ptr, *exp;
char *home = getenv("HOME");
// return default, if nonexistent key
ASSERT_EQ(NULL, (ptr = ini_get_path(section, "nonexistent", NULL)));
ASSERT_STR_EQ("default", (ptr = ini_get_path(section, "nonexistent", "default")));
g_free(ptr);
// return path with replaced home
ASSERT_STR_EQ((exp = g_strconcat(home, "/.path/to/tilde", NULL)),
(ptr = ini_get_path(section, "expand_tilde", NULL)));
g_free(ptr);
g_free(exp);
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_path(void)
{
char *ptr, *exp;
char *home = getenv("HOME");
// return default, if nonexistent key
ASSERT_EQ(NULL, (ptr = cmdline_get_path("-nonexistent", NULL, "desc")));
ASSERT_STR_EQ("default", (ptr = cmdline_get_path("-nonexistent", "default", "desc")));
g_free(ptr);
// return path with replaced home
ASSERT_STR_EQ((exp = g_strconcat(home, "/path/from/cmdline", NULL)),
(ptr = cmdline_get_path("-path", NULL, "desc")));
g_free(ptr);
g_free(exp);
PASS();
}
TEST test_cmdline_get_string(void)
{
char *ptr;
ASSERT_STR_EQ("A simple string from the cmdline", (ptr =cmdline_get_string("-string", "", "")));
free(ptr);
ASSERT_STR_EQ("Single_word_string", (ptr = cmdline_get_string("-str/-s", "", "")));
free(ptr);
ASSERT_STR_EQ("Default", (ptr = cmdline_get_string("-nonexistent", "Default", "")));
free(ptr);
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)
{
g_free(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");
const char *usage = cmdline_create_usage();
ASSERT(strstr(usage, "-msgstring/-ms"));
ASSERT(strstr(usage, "A string to test usage creation"));
ASSERT(strstr(usage, "-msgint/-mi"));
ASSERT(strstr(usage, "An int to test usage creation"));
ASSERT(strstr(usage, "-msgdouble/-md"));
ASSERT(strstr(usage, "A double to test usage creation"));
ASSERT(strstr(usage, "-msgbool/-mb"));
ASSERT(strstr(usage, "A bool to test usage creation"));
PASS();
}
TEST test_option_get_string(void)
{
char *string_section = "string";
char *ptr;
ASSERT_STR_EQ("A simple string", (ptr =option_get_string(string_section, "simple", "-nonexistent", "", "")));
free(ptr);
ASSERT_STR_EQ("Single_word_string", (ptr = option_get_string(string_section, "simple", "-str/-s", "", "")));
free(ptr);
ASSERT_STR_EQ("A simple string from the cmdline", (ptr = option_get_string(string_section, "simple", "-string", "", "")));
free(ptr);
ASSERT_STR_EQ("A simple string from the cmdline", (ptr = option_get_string(string_section, "simple", "-string/-s", "", "")));
free(ptr);
ASSERT_STR_EQ("Single_word_string", (ptr = option_get_string(string_section, "simple", "-s", "", "")));
free(ptr);
ASSERT_STR_EQ("Default", (ptr = option_get_string(string_section, "nonexistent", "-nonexistent", "Default", "")));
free(ptr);
PASS();
}
TEST test_option_get_path(void)
{
char *section = "path";
char *ptr, *exp;
char *home = getenv("HOME");
// invalid ini, invalid cmdline
ASSERT_EQ(NULL, (ptr = option_get_path(section, "nonexistent", "-nonexistent", NULL, "desc")));
ASSERT_STR_EQ("default", (ptr = option_get_path(section, "nonexistent", "-nonexistent", "default", "desc")));
free(ptr);
// valid ini, invalid cmdline
ASSERT_STR_EQ((exp = g_strconcat(home, "/.path/to/tilde", NULL)),
(ptr = option_get_path(section, "expand_tilde", "-nonexistent", NULL, "desc")));
g_free(exp);
g_free(ptr);
// valid ini, valid cmdline
ASSERT_STR_EQ((exp = g_strconcat(home, "/path/from/cmdline", NULL)),
(ptr = option_get_path(section, "expand_tilde", "-path", NULL, "desc")));
g_free(exp);
g_free(ptr);
// invalid ini, valid cmdline
ASSERT_STR_EQ((exp = g_strconcat(home, "/path/from/cmdline", NULL)),
(ptr = option_get_path(section, "nonexistent", "-path", NULL, "desc")));
g_free(exp);
g_free(ptr);
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_path);
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 "
"-path ~/path/from/cmdline "
"-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_path);
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_path);
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: */

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

@ -0,0 +1,185 @@
#include "greatest.h"
#include "src/utils.h"
#include <glib.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)
{
char *exp;
ASSERT_STR_EQ("text_sep_bit", (exp = string_append(g_strdup("text"), "bit", "_sep_")));
g_free(exp);
ASSERT_STR_EQ("textbit", (exp = string_append(g_strdup("text"), "bit", NULL)));
g_free(exp);
ASSERT_STR_EQ("textbit", (exp = string_append(g_strdup("text"), "bit", "")));
g_free(exp);
ASSERT_STR_EQ("text", (exp = string_append(g_strdup("text"), "", NULL)));
g_free(exp);
ASSERT_STR_EQ("text", (exp = string_append(g_strdup("text"), "", "_sep_")));
g_free(exp);
ASSERT_STR_EQ("b", (exp = string_append(g_strdup(""), "b", NULL)));
g_free(exp);
ASSERT_STR_EQ("b", (exp = string_append(NULL, "b", "_sep_")));
g_free(exp);
ASSERT_STR_EQ("a", (exp = string_append(g_strdup("a"), "", NULL)));
g_free(exp);
ASSERT_STR_EQ("a", (exp = string_append(g_strdup("a"), NULL, "_sep_")));
g_free(exp);
ASSERT_STR_EQ("", (exp = string_append(g_strdup(""), "", "_sep_")));
g_free(exp);
ASSERT_EQ(NULL, (exp = string_append(NULL, NULL, "_sep_")));
g_free(exp);
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();
}
TEST test_string_to_path(void)
{
char *ptr, *exp;
char *home = getenv("HOME");
exp = "/usr/local/bin/script";
ASSERT_STR_EQ(exp, (ptr = string_to_path(g_strdup(exp))));
free(ptr);
exp = "~path/with/wrong/tilde";
ASSERT_STR_EQ(exp, (ptr = string_to_path(g_strdup(exp))));
free(ptr);
ASSERT_STR_EQ((exp = g_strconcat(home, "/.path/with/tilde", NULL)),
(ptr = string_to_path(g_strdup("~/.path/with/tilde"))));
free(exp);
free(ptr);
ASSERT_STR_EQ((exp = g_strconcat(home, "/.path/with/tilde and some space", NULL)),
(ptr = string_to_path(g_strdup("~/.path/with/tilde and some space"))));
free(exp);
free(ptr);
PASS();
}
TEST test_string_to_time(void)
{
char *input[] = { "5000 ms", "5000ms", "100", "10s", "2m", "11h", "9d", " 5 ms ", NULL };
gint64 exp[] = { 5000, 5000, 100000, 10000, 120000, 39600000, 777600000, 5, 0};
int i = 0;
while (input[i]){
ASSERT_EQ_FMT(string_to_time(input[i]), exp[i]*1000, "%ld");
i++;
}
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_strip_delimited);
RUN_TEST(test_string_to_path);
RUN_TEST(test_string_to_time);
}
/* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */
Loading…
Cancel
Save