parent
abd783625b
commit
0df656e3da
|
@ -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 '&'
|
||||
|
||||
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)
|
After Width: | Height: | Size: 23 KiB |
Binary file not shown.
@ -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("&", "&", str);
|
||||
str = string_replace_all("\"", """, str);
|
||||
str = string_replace_all("'", "'", str);
|
||||
str = string_replace_all("<", "<", str);
|
||||
str = string_replace_all(">", ">", str);
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
static char *markup_unquote(char *str)
|
||||
{
|
||||
assert(str != NULL);
|
||||
|
||||
str = string_replace_all(""", "\"", str);
|
||||
str = string_replace_all("'", "'", str);
|
||||
str = string_replace_all("<", "<", str);
|
||||
str = string_replace_all(">", ">", str);
|
||||
str = string_replace_all("&", "&", 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 §ions[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 §ions[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 @@
|
||||
../../dunstrc
|
@ -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(""", (ptr=markup_strip(g_strdup("&quot;"))));
|
||||
g_free(ptr);
|
||||
ASSERT_STR_EQ("'", (ptr=markup_strip(g_strdup("&apos;"))));
|
||||
g_free(ptr);
|
||||
ASSERT_STR_EQ("<", (ptr=markup_strip(g_strdup("&lt;"))));
|
||||
g_free(ptr);
|
||||
ASSERT_STR_EQ(">", (ptr=markup_strip(g_strdup("&gt;"))));
|
||||
g_free(ptr);
|
||||
ASSERT_STR_EQ("&", (ptr=markup_strip(g_strdup("&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("<i>foo</i><br>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("<i>foo</i><br>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 & <i>is</i>", MARKUP_FULL);
|
||||
ASSERT_STR_EQ("Markup and & <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 & <i>is</i> 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 & 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…
Reference in new issue