Compare commits

..

1 Commits

Author SHA1 Message Date
Stanislav Seletskiy
9a62fed33d Add --centering option
-geometry option can be used to set relative offset against center.
2017-11-30 14:02:09 +02:00
118 changed files with 3299 additions and 18373 deletions

View File

@ -7,7 +7,7 @@ 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):
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 * -->

View File

@ -1,82 +0,0 @@
name: main
on:
push:
pull_request:
branches:
- master
jobs:
build:
strategy:
matrix:
CC:
- clang
- gcc
distro:
- alpine
- archlinux
- debian-stretch
- debian-buster
- fedora
- ubuntu-xenial
- ubuntu-bionic
- ubuntu-focal
env:
CC: ${{ matrix.CC }}
EXTRA_CFLAGS: "-Werror"
steps:
- uses: actions/checkout@v2
with:
# Clone the whole branch, we have to fetch tags later
fetch-depth: 0
# Fetch tags to determine proper version number inside git
- name: fetch tags
run: git fetch --tags
# We cannot pull tags with old distros, since there is no `.git`. See below.
if: "! (matrix.distro == 'ubuntu-bionic' || matrix.distro == 'ubuntu-xenial' || matrix.distro == 'debian-stretch')"
# The Github checkout Action doesn't support distros with git older than 2.18
# With git<2.18 it downloads the code via API and does not clone it via git :facepalm:
# To succeed the tests, we have to manually replace the VERSION macro
- name: fix version number for old distros
run: 'sed -i "s/-non-git/-ci-oldgit-$GITHUB_SHA/" Makefile'
if: " (matrix.distro == 'ubuntu-bionic' || matrix.distro == 'ubuntu-xenial' || matrix.distro == 'debian-stretch')"
- name: build
run: make -j all dunstify test/test
- name: test
run: make -j test
- name: installation
run: ./test/test-install.sh
- name: valgrind memleaks
run: |
make clean
make -j test-valgrind
- name: coverage
run: |
make clean
make -j test-coverage
- name: Generate coverage report
run: lcov -c -d . -o lcov.info
if: "matrix.CC == 'gcc'"
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v1
with:
token: ${{ secrets.CODECOV_TOKEN }}
flags: unittests
name: ${{ matrix.distro }}-${{ matrix.CC }}
fail_ci_if_error: true
if: "matrix.CC == 'gcc'"
runs-on: ubuntu-latest
container:
image: dunst/ci:${{ matrix.distro }}

22
.gitignore vendored
View File

@ -1,19 +1,9 @@
dunst
*.o
*.d
*.gcda
*.gcno
*.gcov
/lcov.info
core
vgcore.*
/docs/*.1
/docs/*.5
/docs/internal/coverage
/docs/internal/html
/dunst
/dunstify
/dunst.systemd.service
/org.knopwob.dunst.service
/test/test
dunst.1
org.knopwob.dunst.service
dunst.systemd.service
dunstify
test/test

27
.travis.yml Normal file
View File

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

View File

@ -1,86 +0,0 @@
# Ignore musls' weird error
{
musl_alpine_libc
Memcheck:Free
fun:free
obj:/lib/ld-musl-x86_64.so.1
}
# rsvg_error_handle_close got fixed in
# - GNOME/librsvg@7bf1014
# (2018-11-12, first tags: v2.45.0, v2.44.9)
# but the release has to seep into the distros
{
rsvg_error_handle_close
Memcheck:Leak
match-leak-kinds: definite
fun:malloc
fun:g_malloc
fun:g_slice_alloc
fun:g_error_new_valist
fun:g_set_error
obj:*/librsvg-2.so*
fun:rsvg_handle_close
obj:*/loaders/libpixbufloader-svg.so
fun:gdk_pixbuf_loader_close
fun:gdk_pixbuf_get_file_info
fun:get_pixbuf_from_file
...
}
# same as above, but as occurs in CI environment
{
rsvg_error_handle_close2
Memcheck:Leak
match-leak-kinds: definite
fun:malloc
fun:g_malloc
fun:g_slice_alloc
fun:g_error_new_valist
fun:g_set_error
obj:*/librsvg-2.so*
obj:*/librsvg-2.so*
obj:*/loaders/libpixbufloader-svg.so
obj:*/libgdk_pixbuf-2.0.so*
fun:gdk_pixbuf_loader_close
fun:gdk_pixbuf_get_file_info
fun:get_pixbuf_from_file
...
}
# Some new in ArchLinux
{
rsvg_rust_handle_close
Memcheck:Leak
match-leak-kinds: definite
fun:malloc
...
fun:rsvg_rust_handle_close
obj:*/loaders/libpixbufloader-svg.so
...
fun:gdk_pixbuf_new_from_file
...
}
# rsvg_error_writehandler got fixed in
# - GNOME/librsvg@7b4cc9b
# (2018-11-12, first tags: v2.45.0, v2.44.9)
# but the release has to seep into the distros
{
rsvg_error_writehandler
Memcheck:Leak
match-leak-kinds: definite
fun:malloc
fun:g_malloc
fun:g_slice_alloc
fun:g_error_new_valist
fun:g_set_error
obj:*/librsvg-2.so*
fun:rsvg_handle_write
obj:*/loaders/libpixbufloader-svg.so
obj:*/libgdk_pixbuf-2.0.so*
fun:gdk_pixbuf_loader_close
fun:gdk_pixbuf_get_file_info
fun:get_pixbuf_from_file
...
}

View File

@ -3,162 +3,18 @@
## Unreleased
### Added
### Changed
### Fixed
## 1.6.1 - 2021-02-21:
- `ellipsize` option to control how long lines should be ellipsized when `word_wrap` is set to `false`
### Fixed
- Incorrect version in Makefile
- `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.
## 1.6.0 - 2021-02-21:
### Added
- Wayland support. Dunst now runs natively on wayland. This fixes several bugs
with dunst on wayland and allows idle detection. (#781)
- A progress bar, useful for showing volume or brightness in notifications (#775)
- A script in contrib for using the progress bar (#791)
- `dunstctl count` for showing the number of notifications (#793)
- Expose environment variables info about the notification to scripts (#802)
- `text_icon_padding` for adding padding between the notification icon and text
(#810)
### Changed
- Dunst now installs a system-wide config in `/etc/dunst/dunstrc` (#798)
- Move part of the man page to dunst(5) (#799)
### Fixed
- `history_ignore` flag broken when using multiple rules (#747)
- Divide by zero in radius calculation (#750)
- Monitor setting overriding `follow_mode` (#755)
- Incorrect monitor usage when using multiple X11 screens (#762)
- Emit signal when `paused` property changes (#766)
- `dunstify` can pass empty appname to libnotify (#768)
- Incorrect handling of 'do_action, close' mouse action (#778)
# Removed
- `DUNST_COMMAND_{PAUSE,RESUME,TOGGLE}` (#830)
## 1.5.0 - 2020-07-23
### Added
- `min_icon_size` option to automatically scale up icons to a desired value (#674)
- `vertical_alignment` option to control the text/icon alignment within the notification (#684)
- Ability to configure multiple actions for each mouse event (#705)
- `dunstctl` command line control client (#651)
- RGBA support for all color strings (#717)
- Ability to run multiple scripts for each notification
- `ignore_dbusclose` setting (#732)
### Changed
- `dunstify` notification client is now installed by default (#701)
- Keyboard follow mode falls back to the monitor with the mouse if no window has keyboard focus (#708)
### Fixed
- Overflow when setting a >=40 minute timeout (#646)
- Unset configuration options not falling back to default values (#649)
- Crash when `$HOME` environment variable is unset (#693)
- Lack of antialiasing with round corners enabled (#713)
## 1.4.1 - 2019-07-03
### Fixed
- `max_icon_size` not working with dynamic width (#614)
- Failure to parse color strings with trailing comments in the config (#626)
- Negative width in geometry being ignored (#628)
- Incorrect handling of the argument terminator `--` in dunstify
- Crash when changing DPI while no notifications are displayed (#630)
- Fullscreen status change not being detected in some cases (#613)
## 1.4.0 - 2019-03-30
### Added
- Add support to override `frame_color` via rules (#498)
- Support for round corners (#420)
- Ability to reference `$HOME` in icon paths with `~/` (#520)
- Support to customize the mouse bindings (#530)
- Command to toggle pause status (#535)
- Ability to automatically replace similar notifications (like volume changes)
via `stack_tag` (#552)
- Comparison of raw icons for duplicate notifications (#571)
- Introduce new desktop-entry filter (#470)
- `fullscreen` rule to hide notifications when a fullscreen window is active (#472)
- Added `skip_display` rule option to skip initial notification display, and
include the notification in the history. (#590)
### Fixed
- Notification age not counting the time while the computer was suspended (#492)
- Dunst losing always-on-top status on a window manager restart (#160)
- Xpm icons not being recognized
- When new notifications arrive, but display is full, important notifications don't
have to wait for a timeout in a displayed notification (#541)
- Dunst hanging while the context menu is open (#456)
- Having & inside a notification breaking markup (#546)
- `<I> more` notifications don't occupy space anymore, if there is only a single
notification waiting to get displayed. The notification gets displayed directly (#467)
- Segfault when comparing icon name with a notification with a raw icon (#536)
- Icon size can no longer be larger than the notification when a fixed width is specified (#540)
### Changed
- Transient notifications no longer skip history by default (#508)
- The notification summary no longer accepts markup (#497)
### Removed
- Dependency on libxdg-basedir (#550)
## 1.3.2 - 2018-05-06
### Fixed
- Crash when trying to load an invalid or corrupt icon (#512)
## 1.3.1 - 2018-01-30
### Fixed
- Race condition resulting in the service files being empty (#488)
## 1.3.0 - 2018-01-05
### Added
- `ellipsize` option to control how long lines should be ellipsized when `word_wrap` is set to `false` (#374)
- A beginning tilde of a path is now expanded to the home of the current user (#351)
- The image-path hint is now respected, as GApplications send their icon only via this link (#447)
- The (legacy) image\_data hint is now respected (#353)
- If dunst can't acquire the DBus name, dunst prints the PID of the process holding the name (#458 #460)
- Increased accuracy of timeouts by using microseconds internally (#379 #291)
- Support for specifying timeout values in milliseconds, minutes, hours, or days. (#379)
- Support for HTML img tags (via context menu) (#428)
### Fixed
- `new_icon` rule being ignored on notifications that had a raw icon (#423)
- Format strings being replaced recursively in some cases (#322 #365)
- DBus related memory leaks (#397)
- Crash on X11 servers with RandR support less than 1.5. (#413 #364)
- Silently reading the default config file, if `-conf` did not specify a valid file (#452)
- Notification window flickering when a notification is replaced (#320 #415)
- Inaccurate timeout in some cases (#291 #379)
### Changed
- Transient hints are now handled (#343 #310)
## Changed
- transient hints are now handled
An additional rule option (`match_transient` and `set_transient`) is added
to optionally reset the transient setting
- HTML links are now referred to by their text in the context menu rather than numbers (#428)
- `icon_folders` setting renamed to `icon_path` (#170)
- `config.def.h` and `config.h` got merged (#371)
- The dependency on GTK3+ has been removed. Instead of GTK3+, dunst now
requires gdk-pixbuf which had been a transient dependency before. (#334
#376)
- The `_GNU_SOURCE` macros had been removed to make dunst portable to nonGNU systems (#403)
- Internal refactorings of the notification queue handling. (#411)
- Dunst does now install the systemd and dbus service files into their proper location given
by pkg-config. Use `SERVICEDIR_(DBUS|SYSTEMD)` params to overwrite them. (#463)
## 1.2.0 - 2017-07-12

View File

@ -1,50 +0,0 @@
# Important notes on the code
**You can generate an internal overview with doxygen. For this, use `make doc-doxygen` and you'll find an internal overview of all functions and symbols in `docs/internal/html`.**
# Comments
- Comment system is held similar to JavaDoc
- Use `@param` to describe all input parameters
- Use `@return` to describe the output value
- Use `@retval` to describe special return values (like `NULL`)
- Documentation comments should start with a double star (`/**`)
- Append `()` to function names and prepend variables with `#` to properly reference them in the docs
- Add comments to all functions and methods
- Markdown inside the comments is allowed and also desired
- Add the comments to the prototype. Doxygen will merge the protoype and implementation documentation anyways.
Except for **static** methods, add the documentation header to the implementation and *not to the prototype*.
- Member documentation should happen with `/**<` and should span to the right side of the member
- Test files that have the same name as a file in src/\* can include the
associated .c file. This is because they are being compiled INSTEAD of the src
file.
## Log messages
### Messages
- Keep your message in common format: `<problem>: <problematic value/description>`
- If you have to write text, single quote values in your sentence.
### Levels
For logging, there are printf-like macros `LOG_(E|C|W|M|I|D)`.
- `LOG_E` (ERROR):
- All messages, which lead to immediate abort and are caused by a programming error. The program needs patching and the error is not user recoverable.
- e.g.: Switching over an enum, `LOG_E` would go into the default case.
- `LOG_C` (CRITICAL):
- The program cannot continue to work. It is used in the wrong manner or some outer conditions are not met.
- e.g.: `-config` parameter value is unreadable file
- `LOG_W` (WARNING):
- Something is not in shape, but it's recoverable.
- e.g.: A value is not parsable in the config file, which will default.
- `LOG_M` (MESSAGE):
- Important info, which informs about the state.
- e.g.: An empty notification does get removed immediately.
- `LOG_I` (INFO):
- Mostly unneccessary info, but important to debug (as the user) some use cases.
- e.g.: print the notification contents after arriving
- `LOG_D` (DEBUG):
- Only important during development or tracing some bugs (as the developer).

244
Makefile
View File

@ -3,247 +3,103 @@
include config.mk
VERSION := "1.6.1-non-git"
ifneq ($(wildcard ./.git/),)
VERSION := $(shell ${GIT} describe --tags)
VERSION := "1.2.0-non-git"
ifneq ($(wildcard ./.git/.),)
VERSION := $(shell git describe --tags)
endif
ifeq (,${SYSTEMD})
# Check for systemctl to avoid discrepancies on systems, where
# systemd is installed, but systemd.pc is in another package
systemctl := $(shell command -v ${SYSTEMCTL} >/dev/null && echo systemctl)
ifeq (systemctl,${systemctl})
SYSTEMD := 1
else
SYSTEMD := 0
endif
endif
SERVICEDIR_DBUS ?= $(shell $(PKG_CONFIG) dbus-1 --variable=session_bus_services_dir)
SERVICEDIR_DBUS := ${SERVICEDIR_DBUS}
ifeq (,${SERVICEDIR_DBUS})
$(error "Failed to query $(PKG_CONFIG) for package 'dbus-1'!")
endif
ifneq (0,${SYSTEMD})
SERVICEDIR_SYSTEMD ?= $(shell $(PKG_CONFIG) systemd --variable=systemduserunitdir)
SERVICEDIR_SYSTEMD := ${SERVICEDIR_SYSTEMD}
ifeq (,${SERVICEDIR_SYSTEMD})
$(error "Failed to query $(PKG_CONFIG) for package 'systemd'!")
endif
endif
ifneq (0,${WAYLAND})
DATA_DIR_WAYLAND_PROTOCOLS ?= $(shell $(PKG_CONFIG) wayland-protocols --variable=pkgdatadir)
DATA_DIR_WAYLAND_PROTOCOLS := ${DATA_DIR_WAYLAND_PROTOCOLS}
ifeq (,${DATA_DIR_WAYLAND_PROTOCOLS})
$(warning "Failed to query $(PKG_CONFIG) for package 'wayland-protocols'!")
endif
endif
LIBS := $(shell $(PKG_CONFIG) --libs ${pkg_config_packs})
INCS := $(shell $(PKG_CONFIG) --cflags ${pkg_config_packs})
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!")
$(error "pkg-config failed!")
endif
endif
CFLAGS := ${DEFAULT_CPPFLAGS} ${CPPFLAGS} ${DEFAULT_CFLAGS} ${CFLAGS} ${INCS} -MMD -MP
LDFLAGS := ${DEFAULT_LDFLAGS} ${LDFLAGS} ${LIBS}
CFLAGS += -I. ${INCS}
LDFLAGS+= -L. ${LIBS}
ifeq (0,${WAYLAND})
# without wayland support
SRC := $(sort $(shell ${FIND} src/ -not \( -path src/wayland -prune \) -name '*.c'))
else
# with Wayland support
SRC := $(sort $(shell ${FIND} src/ -name '*.c'))
endif
SRC := $(sort $(shell find src/ -name '*.c'))
OBJ := ${SRC:.c=.o}
TEST_SRC := $(sort $(shell ${FIND} test/ -name '*.c'))
TEST_SRC := $(sort $(shell find test/ -name '*.c'))
TEST_OBJ := $(TEST_SRC:.c=.o)
DEPS := ${SRC:.c=.d} ${TEST_SRC:.c=.d}
.PHONY: all debug
all: doc dunst dunstify service
all: doc dunst service
debug: CFLAGS += ${CPPFLAGS_DEBUG} ${CFLAGS_DEBUG}
debug: CFLAGS += ${CFLAGS_DEBUG}
debug: LDFLAGS += ${LDFLAGS_DEBUG}
debug: CPPFLAGS += ${CPPFLAGS_DEBUG}
debug: all
-include $(DEPS)
${OBJ} ${TEST_OBJ}: Makefile config.mk
%.o: %.c
.c.o:
${CC} -o $@ -c $< ${CFLAGS}
${OBJ}: config.mk
dunst: ${OBJ} main.o
${CC} -o ${@} ${OBJ} main.o ${CFLAGS} ${LDFLAGS}
${CC} ${CFLAGS} -o $@ ${OBJ} main.o ${LDFLAGS}
dunstify: dunstify.o
${CC} -o ${@} dunstify.o ${CFLAGS} ${LDFLAGS}
${CC} ${CFLAGS} -o $@ dunstify.o ${LDFLAGS}
.PHONY: test test-valgrind test-coverage
test: test/test clean-coverage-run
# Make sure an error code is returned when the test fails
/usr/bin/env bash -c 'set -euo pipefail;\
./test/test -v | ./test/greenest.awk '
test-valgrind: test/test
${VALGRIND} \
--suppressions=.valgrind.suppressions \
--leak-check=full \
--show-leak-kinds=definite \
--errors-for-leak-kinds=definite \
--num-callers=40 \
--error-exitcode=123 \
./test/test -v
test-coverage: CFLAGS += -fprofile-arcs -ftest-coverage -O0
test-coverage: test
test-coverage-report: test-coverage
mkdir -p docs/internal/coverage
${GCOVR} \
-r . \
--exclude=test \
--html \
--html-details \
-o docs/internal/coverage/index.html
test/%.o: test/%.c src/%.c
${CC} -o $@ -c $< ${CFLAGS}
.PHONY: test
test: test/test
cd test && ./test
test/test: ${OBJ} ${TEST_OBJ}
${CC} -o ${@} ${TEST_OBJ} $(filter-out ${TEST_OBJ:test/%=src/%},${OBJ}) ${CFLAGS} ${LDFLAGS}
${CC} ${CFLAGS} -o $@ ${TEST_OBJ} ${OBJ} ${LDFLAGS}
.PHONY: doc doc-doxygen
doc: docs/dunst.1 docs/dunst.5 docs/dunstctl.1
.PHONY: doc
doc: docs/dunst.1
docs/dunst.1: docs/dunst.pod
pod2man --name=dunst -c "Dunst Reference" --section=1 --release=${VERSION} $< > $@
# Can't dedup this as we need to explicitly provide the name and title text to
# pod2man :(
docs/dunst.1: docs/dunst.1.pod
${POD2MAN} --name=dunst -c "Dunst Reference" --section=1 --release=${VERSION} $< > $@
docs/dunst.5: docs/dunst.5.pod
${POD2MAN} --name=dunst -c "Dunst Reference" --section=5 --release=${VERSION} $< > $@
docs/dunstctl.1: docs/dunstctl.pod
${POD2MAN} --name=dunstctl -c "dunstctl 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
doc-doxygen:
${DOXYGEN} docs/internal/Doxyfile
.PHONY: service service-dbus service-systemd wayland-protocols
service: service-dbus
service-dbus:
@${SED} "s|##PREFIX##|$(PREFIX)|" org.knopwob.dunst.service.in > org.knopwob.dunst.service
ifneq (0,${SYSTEMD})
service: service-systemd
service-systemd:
@${SED} "s|##PREFIX##|$(PREFIX)|" dunst.systemd.service.in > dunst.systemd.service
endif
ifneq (0,${WAYLAND})
wayland-protocols: src/wayland/protocols/wlr-layer-shell-unstable-v1.xml src/wayland/protocols/wlr-foreign-toplevel-management-unstable-v1.xml
# TODO: write this shorter
mkdir -p src/wayland/protocols
wayland-scanner private-code ${DATA_DIR_WAYLAND_PROTOCOLS}/stable/xdg-shell/xdg-shell.xml src/wayland/protocols/xdg-shell.h
wayland-scanner client-header ${DATA_DIR_WAYLAND_PROTOCOLS}/stable/xdg-shell/xdg-shell.xml src/wayland/protocols/xdg-shell-client-header.h
wayland-scanner client-header ${DATA_DIR_WAYLAND_PROTOCOLS}/unstable/xdg-output/xdg-output-unstable-v1.xml src/wayland/protocols/xdg-output-unstable-v1-client-header.h
wayland-scanner private-code ${DATA_DIR_WAYLAND_PROTOCOLS}/unstable/xdg-output/xdg-output-unstable-v1.xml src/wayland/protocols/xdg-output-unstable-v1.h
wayland-scanner client-header src/wayland/protocols/wlr-layer-shell-unstable-v1.xml src/wayland/protocols/wlr-layer-shell-unstable-v1-client-header.h
wayland-scanner private-code src/wayland/protocols/wlr-layer-shell-unstable-v1.xml src/wayland/protocols/wlr-layer-shell-unstable-v1.h
wayland-scanner client-header src/wayland/protocols/idle.xml src/wayland/protocols/idle-client-header.h
wayland-scanner private-code src/wayland/protocols/idle.xml src/wayland/protocols/idle.h
wayland-scanner client-header src/wayland/protocols/wlr-foreign-toplevel-management-unstable-v1.xml src/wayland/protocols/wlr-foreign-toplevel-management-unstable-v1-client-header.h
wayland-scanner private-code src/wayland/protocols/wlr-foreign-toplevel-management-unstable-v1.xml src/wayland/protocols/wlr-foreign-toplevel-management-unstable-v1.h
endif
.PHONY: clean clean-dunst clean-dunstify clean-doc clean-tests clean-coverage clean-coverage-run clean-wayland-protocols
clean: clean-dunst clean-dunstify clean-doc clean-tests clean-coverage clean-coverage-run
.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 main.d ${DEPS}
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.d
rm -f dunstify
clean-doc:
rm -f docs/dunst.1
rm -f docs/dunst.5
rm -f docs/dunstctl.1
rm -fr docs/internal/html
rm -fr docs/internal/coverage
clean-tests:
rm -f test/test test/*.o test/*.d
rm -f test/test test/*.o
clean-coverage: clean-coverage-run
${FIND} . -type f -name '*.gcno' -delete
${FIND} . -type f -name '*.gcna' -delete
# Cleans the coverage data before every run to not double count any lines
clean-coverage-run:
${FIND} . -type f -name '*.gcov' -delete
${FIND} . -type f -name '*.gcda' -delete
clean-wayland-protocols:
rm -f src/wayland/protocols/*.h
.PHONY: install install-dunst install-dunstctl install-doc \
install-service install-service-dbus install-service-systemd \
uninstall uninstall-dunstctl \
uninstall-service uninstall-service-dbus uninstall-service-systemd
install: install-dunst install-dunstctl install-doc install-service install-dunstify
.PHONY: install install-dunst install-doc install-service uninstall
install: install-dunst install-doc install-service
install-dunst: dunst doc
install -Dm755 dunst ${DESTDIR}${BINDIR}/dunst
install -Dm644 docs/dunst.1 ${DESTDIR}${MANPREFIX}/man1/dunst.1
install -Dm644 docs/dunst.5 ${DESTDIR}${MANPREFIX}/man5/dunst.5
install -Dm644 docs/dunstctl.1 ${DESTDIR}${MANPREFIX}/man1/dunstctl.1
install-dunstctl: dunstctl
install -Dm755 dunstctl ${DESTDIR}${BINDIR}/dunstctl
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:
install -Dm644 dunstrc ${DESTDIR}${SYSCONFDIR}/dunst/dunstrc
mkdir -p ${DESTDIR}${PREFIX}/share/dunst
install -m644 dunstrc ${DESTDIR}${PREFIX}/share/dunst
install-service: install-service-dbus
install-service-dbus: service-dbus
install -Dm644 org.knopwob.dunst.service ${DESTDIR}${SERVICEDIR_DBUS}/org.knopwob.dunst.service
ifneq (0,${SYSTEMD})
install-service: install-service-systemd
install-service-systemd: service-systemd
install -Dm644 dunst.systemd.service ${DESTDIR}${SERVICEDIR_SYSTEMD}/dunst.service
endif
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
install-dunstify: dunstify
install -Dm755 dunstify ${DESTDIR}${BINDIR}/dunstify
uninstall: uninstall-service uninstall-dunstctl
rm -f ${DESTDIR}${BINDIR}/dunst
rm -f ${DESTDIR}${BINDIR}/dunstify
uninstall:
rm -f ${DESTDIR}${PREFIX}/bin/dunst
rm -f ${DESTDIR}${MANPREFIX}/man1/dunst.1
rm -f ${DESTDIR}${MANPREFIX}/man5/dunst.5
rm -f ${DESTDIR}${MANPREFIX}/man1/dunstctl.1
rm -rf ${DESTDIR}${SYSCONFDIR}/dunst
uninstall-dunstctl:
rm -f ${DESTDIR}${BINDIR}/dunstctl
uninstall-service: uninstall-service-dbus
uninstall-service-dbus:
rm -f ${DESTDIR}${SERVICEDIR_DBUS}/org.knopwob.dunst.service
ifneq (0,${SYSTEMD})
uninstall-service: uninstall-service-systemd
uninstall-service-systemd:
rm -f ${DESTDIR}${SERVICEDIR_SYSTEMD}/dunst.service
endif
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

138
README.md
View File

@ -1,147 +1,51 @@
[![main](https://github.com/dunst-project/dunst/workflows/main/badge.svg)](https://github.com/dunst-project/dunst/actions?query=workflow%3Amain) [![codecov](https://codecov.io/gh/dunst-project/dunst/branch/master/graph/badge.svg)](https://codecov.io/gh/dunst-project/dunst)
[![Build Status](https://travis-ci.org/dunst-project/dunst.svg?branch=master)](https://travis-ci.org/dunst-project/dunst)
# Dunst
## Dunst
<i>A highly configurable and lightweight notification daemon.</i>
![music](contrib/screenshots/music.png)
## Table of Contents
* [Features](#features)
* [Building](#building)
* [Documentation](#documentation)
* [Wiki][wiki]
* [Description](#description)
* [Compiling](#compiling)
* [Copyright](#copyright)
# Features
## Description
## ⚙️ Highly customizable
Dunst is a highly configurable and lightweight notification daemon.
Customize fonts, icons, timeouts, and more. Are you unhappy with the default
shortcuts and colors? No worries, you can change these all with a simple
configuration file tweak.
_click the images to see the dunstrc_
## Compiling
<a href="https://gist.github.com/NNBnh/5f6e601a6a82a6ed43b1959698758141">
<img alt="screenshot1" src="contrib/screenshots/screenshot1_cut.png">
</a>
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):
<a href="https://gist.github.com/fwSmit/9127d988b07bcec9d869f2c927d0f616">
<img alt="screenshot2" src="contrib/screenshots/screenshot2_cut.png">
</a>
## 📜 Scripting
<a href="https://gitlab.manjaro.org/profiles-and-settings/manjaro-theme-settings/-/blob/master/skel/.config/dunst/dunstrc">
<img alt="screenshot_urgency" src="contrib/screenshots/screenshot_urgency.png">
</a>
Run custom scripts on notifications matching a specified pattern. Have espeak
read out your notifications, or play a song when your significant other signs on
in pidgin!
## 📋 Rules
Change the look or behavior of notifications matching a specified pattern. You
could use this to change the color of message notifications from your favorite
jabber buddies, or to prevent important work email notifications from
disappearing until you manually dismiss them.
## ⏸️ Pause
If you want to take a break and not receive any notifications for a while, just
pause dunst. All notifications will be saved for you to catch up
later.
## 🕘 History
Catch an unread notification disappearing from the corner of your eye? Just tap
a keyboard shortcut to replay the last notification, or continue tapping to see
your notification history.
# Documentation
Most documentation can be found in dunst's man pages. In
[**dunst(1)**](docs/dunst.1.pod) contains some general instructions on how
to run dunst and in
[**dunst(5)**](docs/dunst.5.pod) all of dunst's configuration options are
explained.
On the dunst [wiki][wiki] you can find guides and installation instructions and
on the dunst [website][website] there is a [FAQ][FAQ] with common issues.
## Installation
Dunst is available in many package repositories. If it's not available in your
distro's repositories, don't worry, it's not hard to build it yourself.
### Dependencies
- dbus (runtime)
- dbus
- libxinerama
- libxrandr
- libxss
- libxdg-basedir
- glib
- pango/cairo
- libnotify (optional, for dunstify)
- wayland-client (can build without, see [make parameters](#make-parameters))
- wayland-protocols (optional, for recompiling protocols)
- libgtk-3-dev
The names will be different depending on your [distribution](https://github.com/dunst-project/dunst/wiki/Dependencies).
### Building
```
git clone https://github.com/dunst-project/dunst.git
cd dunst
make
sudo make install
```
### Make parameters
- `DESTDIR=<PATH>`: Set the destination directory of the installation. (Default: `/`)
- `PREFIX=<PATH>`: Set the prefix of the installation. (Default: `/usr/local`)
- `BINDIR=<PATH>`: Set the `dunst` executable's path (Default: `${PREFIX}/bin`)
- `DATADIR=<PATH>`: Set the path for shared files. (Default: `${PREFIX}/share`)
- `MANDIR=<PATH>`: Set the prefix of the manpage. (Default: `${DATADIR}/man`)
- `SYSTEMD=(0|1)`: Disable/Enable the systemd unit. (Default: detected via `pkg-config`)
- `WAYLAND=(0|1)`: Disable/Enable wayland support. (Default: 1 (enabled))
- `SERVICEDIR_SYSTEMD=<PATH>`: The path to put the systemd user service file. Unused, if `SYSTEMD=0`. (Default: detected via `pkg-config`)
- `SERVICEDIR_DBUS=<PATH>`: The path to put the dbus service file. (Default: detected via `pkg-config`)
- `EXTRA_CFLAGS=<FLAGS>`: Additional flags for the compiler.
**Make sure to run all make calls with the same parameter set. So when building with `make PREFIX=/usr`, you have to install it with `make PREFIX=/usr install`, too.**
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.
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.
## Screenshots
## Mantainers
<a href="https://gist.github.com/MCotocel/2b34486ae59ccda4319fcb93454d212c">
<img alt="screenshot3" src="contrib/screenshots/screenshot3_cut.png">
</a>
Nikos Tsipinakis <nikos@tsipinakis.com>
<a href="https://gitlab.manjaro.org/profiles-and-settings/manjaro-theme-settings/-/blob/master/skel/.config/dunst/dunstrc">
<img alt="progress" src="https://user-images.githubusercontent.com/23078054/102542111-98b01e00-40b1-11eb-967e-bc952430bd06.png">
</a>
## Maintainers
- [Nikos Tsipinakis](https://github.com/tsipinakis) <nikos@tsipinakis.com>
- [Benedikt Heine](https://github.com/bebehei) <bebe@bebehei.de>
Jonathan Lusso <jonilusso@gmail.com>
## Author
Written by Sascha Kruse <dunst@knopwob.de>
written by Sascha Kruse <dunst@knopwob.de>
## Copyright
Copyright 2013 Sascha Kruse and contributors (see [`LICENSE`](./LICENSE) for licensing information)
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
[website]: https://dunst-project.org
[FAQ]: https://dunst-project.org/faq

View File

@ -1,139 +1,3 @@
===================================================================================
Release Notes For v1.6.0
===================================================================================
For users:
At long last, dunst has native wayland support. On startup dunst will now
autodetect the display environment it's run on and use the appropriate backend
(X11 or wayland).
Additionally, support for progress bars has been added when the 'value' hint is
used. Try it out with `notify-send -h int:value:70 'Progress bars!'`
Last but most importantly, support for the
`DUNST_COMMAND_{PAUSE,RESUME,TOGGLE}` has been removed as they could
potentially be used to DoS dunst. `dunstctl` has been available as a direct
replacement for the use-case they served since last release. See
https://github.com/dunst-project/dunst/pull/830 for details
For maintainers:
Dunst now depends on the wayland libraries and (optionally) on the
wayland-protocols package. A global configuration file is now installed by
default in `/etc/dunst/dunstrc`
===================================================================================
Release Notes For v1.5.0
===================================================================================
For users:
The most important update from the previous version is the addition of the
dunstctl command and dunstify utility, a drop-in notify-send replacement (which
existed for a while, but wasn't installed by default).
The internal keyboard shortcut support in dunst is now considered deprecated
and should be replaced by dunstctl calls. You can use the configuration of your
WM or DE to bind these to shortcuts of your choice.
Additionally, another long requested feature implemented is RGBA/transparency
support. Given an active compositor you can now add an optional transparency
component to all colors in #RRGGBBAA format.
For maintainers:
As mentioned above, two new binaries are now installed by default, dunstctl and dunstify.
libnotify has been added as a dependency as it's used internally by dunstify.
===================================================================================
Release Notes For v1.4.0
===================================================================================
There has been significant internal refactoring since the last release which
might have introduced some new bugs. Be sure to report anything you find.
However, as usual, there has been a lot of bug-fixing and a lot of new features
have been added as well. Look at the full changelog for a breakdown.
Some important points to note:
For users:
* Behavioural changes
In the previous release we introduced support for clients to mark
notifications as 'transient'. Transient notifications used to 1) bypass
idle_threshold and 2) not be saved in history.
The latter behaviour has been disabled by default and can be re-created using
rules if necessary. Transient notifications will now only bypass
idle_threshold.
Additionally, to be compliant with the notification spec, the notification
summary no longer accepts markup.
For maintainers:
* Dependency on libxdg-basedir has been removed
===================================================================================
Release Notes For v1.3.0
===================================================================================
Version 1.3 is supposed to be fully backwards compatible with 1.2.
For users:
* Behavioural changes
Dunst respects the timeout with millisecond accuracy now. Notifications with
a one second timeout are not shown up to three seconds.
Additionally you can specify timeout values in milliseconds, seconds, minutes,
hours or days using the ms, s, h, or d suffix in the config value
respectively.
Transient notifications time out ignoring the `idle_threshold` setting and are not
saved in history. This can be overridden with a rule containing `set_transient = no`.
In the same vein there is the `match_transient` condition to match transient
notifications via rules.
A prefixed tilde (`~/`) in path settings (browser, dmenu, script) is interpreted as the
home folder of the user.
* Configuration Options
`icon_folders` got deprecated and renamed to `icon_path`. `icon_folders` is still
supported, but will get removed in future.
The option `ellipsize` got introduced. It controls where to ellipsize the text of
an overlong notification if `word_wrap = no`.
For maintainers:
* Dependencies
The GTK3+ dependency got removed. Instead of this gdk-pixbuf is required
explicitly. This had been a transient dependency before.
In the Makefile, libxrandr is now specified to require version 1.5 or newer.
The dependency on libxrandr >= 1.5 is not new, Dunst 1.2.0 required it too
but there was no active check for it.
* Installation process
The internals of dunst's make installation process have slightly changed. The
install routine won't install the service files for DBus and systemd in a hardcoded
subdirectory of $PREFIX. It'll now query the `dbus-1` and `systemd` pkg-config
packages for those paths and will put it there.
To overwrite the pkg-config values, you can manually specify another path.
Use `SERVICEDIR_(DBUS|SYSTEMD)` vars as parameters to your make calls.
For all introduced variables, see [the README.md].
* Portability
GNU-specific functions have been disabled to make dunst portable to nongnu libc's.
For a full list of changes see [CHANGELOG.md].
===================================================================================
Release Notes For v1.2.0
===================================================================================

View File

@ -1,33 +1,22 @@
/* see example dunstrc for additional explanations about these options */
struct settings defaults = {
settings_t defaults = {
.font = "-*-terminus-medium-r-*-*-16-*-*-*-*-*-*-*",
.markup = MARKUP_NO,
.colors_norm.bg = "#1793D1",
.colors_norm.fg = "#DDDDDD",
.colors_norm.highlight = "#1745d1",
.colors_crit.bg = "#ffaaaa",
.colors_crit.fg = "#000000",
.colors_crit.highlight = "#ff6666",
.colors_low.bg = "#aaaaff",
.colors_low.fg = "#000000",
.colors_low.highlight = "#7f7fff",
.normbgcolor = "#1793D1",
.normfgcolor = "#DDDDDD",
.critbgcolor = "#ffaaaa",
.critfgcolor = "#000000",
.lowbgcolor = "#aaaaff",
.lowfgcolor = "#000000",
.format = "%s %b", /* default format */
.timeouts = { S2US(10), S2US(10), S2US(0) }, /* low, normal, critical */
.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 */
.geometry = { .x = 0, /* geometry */
.y = 0,
.w = 0,
.h = 0,
.negative_x = 0,
.negative_y = 0,
.negative_width = 0,
.width_set = 0
},
.geom = "0x0", /* geometry */
.title = "Dunst", /* the title of dunst notification windows */
.class = "Dunst", /* the class of dunst notification windows */
.shrink = false, /* shrinking */
@ -35,27 +24,21 @@ struct settings defaults = {
.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 = ALIGN_LEFT, /* text alignment ALIGN_[LEFT|CENTER|RIGHT] */
.vertical_alignment = VERTICAL_CENTER, /* vertical content alignment VERTICAL_[TOP|CENTER|BOTTOM] */
.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,
.ignore_dbusclose = false,
.ellipsize = ELLIPSE_MIDDLE,
.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 */
.corner_radius = 0,
.force_xinerama = false,
.force_xwayland = false,
.separator_height = 2, /* height of the separator line between two notifications */
.padding = 0,
.h_padding = 0, /* horizontal padding */
.text_icon_padding = 0, /* padding between icon and text*/
.sep_color = {SEP_AUTO}, /* SEP_AUTO, SEP_FOREGROUND, SEP_FRAME, SEP_CUSTOM */
.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",
@ -74,7 +57,6 @@ struct settings defaults = {
.browser = "/usr/bin/firefox",
.min_icon_size = 0,
.max_icon_size = 0,
/* paths to default icons */
@ -111,26 +93,9 @@ struct settings defaults = {
.code = 0,.sym = NoSymbol,.is_valid = false
}, /* ignore this */
.mouse_left_click = (enum mouse_action []){MOUSE_CLOSE_CURRENT, -1},
.mouse_middle_click = (enum mouse_action []){MOUSE_DO_ACTION, -1},
.mouse_right_click = (enum mouse_action []){MOUSE_CLOSE_ALL, -1},
.progress_bar_height = 10,
.progress_bar_min_width = 150,
.progress_bar_max_width = 300,
.progress_bar_frame_width = 1,
.progress_bar = true,
.layer = ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY,
};
struct rule default_rules[] = {
rule_t default_rules[] = {
/* name can be any unique string. It is used to identify
* the rule in dunstrc to override it there
*/
@ -147,16 +112,37 @@ struct rule default_rules[] = {
.timeout = -1,
.urgency = -1,
.markup = MARKUP_NULL,
.history_ignore = -1,
.match_transient = -1,
.history_ignore = 1,
.match_transient = 1,
.set_transient = -1,
.skip_display = -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 ft=c tabstop=8 shiftwidth=8 expandtab textwidth=0: */
/* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */

View File

@ -1,67 +1,38 @@
# paths
PREFIX ?= /usr/local
BINDIR ?= ${PREFIX}/bin
SYSCONFDIR ?= /etc
DATADIR ?= ${PREFIX}/share
# around for backwards compatibility
MANPREFIX ?= ${DATADIR}/man
MANDIR ?= ${MANPREFIX}
EXTRA_CFLAGS ?=
DOXYGEN ?= doxygen
FIND ?= find
GCOVR ?= gcovr
GIT ?= git
PKG_CONFIG ?= pkg-config
POD2MAN ?= pod2man
SED ?= sed
SYSTEMCTL ?= systemctl
VALGRIND ?= valgrind
# Disable systemd service file installation,
# if you don't want to use systemd albeit installed
#SYSTEMD ?= 0
# Disable dependency on wayland. This will force dunst to use
# xwayland on wayland compositors
# You can also use "make WAYLAND=0" to build without wayland
# WAYLAND ?= 0
ifneq (0, ${WAYLAND})
ENABLE_WAYLAND= -DENABLE_WAYLAND
endif
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
DEFAULT_CPPFLAGS = -D_DEFAULT_SOURCE -DVERSION=\"${VERSION}\"
DEFAULT_CFLAGS = -g --std=gnu99 -pedantic -Wall -Wno-overlength-strings -Os ${STATIC} ${ENABLE_WAYLAND} ${EXTRA_CFLAGS}
DEFAULT_LDFLAGS = -lm -lrt
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 := gio-2.0 \
gdk-pixbuf-2.0 \
"glib-2.0 >= 2.44" \
pkg_config_packs := dbus-1 \
gio-2.0 \
gdk-3.0 \
"glib-2.0 >= 2.36" \
pangocairo \
x11 \
xinerama \
xext \
"xrandr >= 1.5" \
xscrnsaver \
xscrnsaver
# dunstify also needs libnotify
pkg_config_packs += libnotify
ifneq (0,${WAYLAND})
pkg_config_packs += wayland-client
endif
ifneq (,$(findstring STATIC_CONFIG,$(CFLAGS)))
# 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

View File

@ -1,255 +0,0 @@
#!/usr/bin/env bash
###############################################################################
##
## Usage
##
## ./<script_name> [<OPTIONS>]
##
## If it does not run, give execute permissions to the script with
## chmod +x <script_name>. Then run ./<script_name>.
##
## Options
##
## -h|--help Optional. Show help message.
##
## Description
##
## This script creates a dunst themed user config in $HOME/.config/dunst/
## folder, changing dunst basic theming options (like fonts, colors, etc.)
## according to your current X resources color palette.
##
## To make this possible, it reads your current user config
## ($HOME/.conf/dunst/dunstrc, which is copied from the default config if
## it does not exist) and changes the attributes values with new ones (see
## Theming section for more info). Then it dumps the new configuration to
## $user_xr_color_conf file.
##
## Theming
##
## To change colors and fonts:
##
## * Firstly you have to ensure that those dunst attributes are defined in
## the corresponding sections. For example, to change the frame_color
## value from the global section, in $HOME/.config/dunst/dunstrc should
## be:
##
## ...
## [global]
## ...
## frame_color = <value>
## ...
##
## * Then, you can change attribute values as you wish with the
## following format in theme_attr_dict:
##
## ["<sec>-<attr>"]="<val>|$(xrdb_get '<X_res>' '<dev_val>')"
##
## Each <variable> means the following:
##
## * sec - current section name e.g.: global.
## * attr - current attribute name e.g.: frame-color.
## * val - a simple value e.g.: "#fffeee", Monospace, 11...
## * X_res - X resource name e.g.: color8.
## * dev_val - default value to set if X_res is not found e.g.: #65737e.
##
## Theming example (you can check theme_attr_dict for other values):
##
## ["global-frame_color"]="\"$(xrdb_get 'color8' '#65737e')
##
## The function xrdb_get, searches the first parameter in the X resources
## database with appres (command installed from xorg-appres in archlinux
## and x11-utils in ubuntu). If the first parameter does not exist, the
## function returns the second parameter. For hex colors, is important to
## scape " characters for proper functioning of dunst config reader (for
## example "#ffeegg").
##
## You can define X_res variables in $HOME/.Xresources file. For in depth
## syntax go to https://wiki.archlinux.org/index.php/X_resources.
##
###############################################################################
set -e
# Check if appres is installed
if [ ! "$(command -v appres)" ]; then
printf 'Install xorg-appres in archlinux and x11-utils in debian/ubuntu.\n'
exit 1
fi
readonly script_name="$(basename "$0")"
readonly base_dir="$(realpath "$(dirname "$0")")"
# Show ussage
usage() {
grep -e '^##[^#]*$' "$base_dir/$script_name" | \
sed -e "s/^##[[:space:]]\{0,1\}//g" \
-e "s/<script_name>/${script_name}/g"
exit 2
} 2>/dev/null
# Show help
if [ "$#" -gt 0 ]; then
if ! [[ "$1" == "--help" || "$1" == "-h" ]]; then
printf '\nUnknown option.\n'
fi
usage
fi
# Function to get resource values
xrdb_get () {
output="$(appres Dunst | grep "$1:" | head -n1 | cut -f2)"
default="$2"
printf '%s' "${output:-$default}"
}
#
# Attributes dictionary. Add or remove attributes (see header for more info)
#
declare -A theme_attr_dict=(
["global-font"]="$(xrdb_get 'font' 'Monospace') $(xrdb_get '*.font_size:' '11')"
["global-frame_width"]="$(xrdb_get 'border_width' '1')"
["global-frame_color"]="\"$(xrdb_get 'color8' '#65737e')\""
["urgency_low-background"]="\"$(xrdb_get 'color0' '#2b303b')\""
["urgency_low-foreground"]="\"$(xrdb_get 'color4' '#65737e')\""
["urgency_low-frame_color"]="\"$(xrdb_get 'color4' '#65737e')\""
["urgency_normal-background"]="\"$(xrdb_get 'color0' '#2b303b')\""
["urgency_normal-foreground"]="\"$(xrdb_get 'color2' '#a3be8c')\""
["urgency_normal-frame_color"]="\"$(xrdb_get 'color2' '#a3be8c')\""
["urgency_critical-background"]="\"$(xrdb_get 'color0' '#2b303b')\""
["urgency_critical-foreground"]="\"$(xrdb_get 'color1' '#bf616a')\""
["urgency_critical-frame_color"]="\"$(xrdb_get 'color1' '#bf616a')\""
)
# Attributes dictionary keys.
readonly valid_keys="${!theme_attr_dict[@]}"
#
# File paths
#
# User config dir and file
readonly user_conf_dir="${XDG_CONFIG_HOME:-$HOME/.config}/dunst"
readonly user_conf="$user_conf_dir/dunstrc"
# Default config dir and example file
example_conf_dir="/usr/share/dunst"
example_conf="$example_conf_dir/dunstrc"
# User xresources color config file
readonly user_xr_color_conf="$user_conf_dir/dunstrc_xr_colors"
# Check if the user config directory exists
if ! [ -d "$user_conf_dir" ]; then
printf 'Creating folder "%s".\n' "$user_conf_dir"
mkdir -p "$user_conf_dir"
fi
# Check if the user config file exists
if ! [ -f "$user_conf" ]; then
printf '"%s" file does not exist.\nChecking for config file example.' \
"$user_conf"
if [ -d "/usr/share/dunst" ]; then
# Archlinux default dir and example file
example_conf_dir="/usr/share/dunst"
if [ -f "$example_conf_dir/dunstrc" ]; then
example_conf="$example_conf_dir/dunstrc"
else
printf 'Could not find the example config file in "%s".
\nPlease, change $example_conf variable in the script.' \
"$example_conf_dir"
exit 1
fi
elif [ -d "/usr/share/doc/dunst" ]; then
# Debian/Ubuntu default dir
example_conf_dir="/usr/share/doc/dunst"
if [ -f "$example_conf_dir/dunstrc.example.gz" ]; then
# Ubuntu <= 17.10 and Debian <= 1.2.0-2 example file:
example_conf="$example_conf_dir/dunstrc.example.gz"
elif [ -f "$example_conf_dir/dunstrc.gz" ]; then
# Ubuntu >= 18.04 and Debian >= 1.3.0-2 example file:
example_conf="$example_conf_dir/dunstrc.gz"
else
printf 'Could not find the example config file in "%s".
\nPlease, change $example_conf variable in the script.' \
"$example_conf_dir"
exit 1
fi
else
printf 'Could not find the example config directory.
\nPlease, change $example_conf_dir variable in the script.'
exit 1
fi
printf 'Copying example config to "%s".\n' "$user_conf_dir"
# Get the extension to check if the file is compressed
if [[ "${example_conf##*\.}" == "gz" ]]; then
# Extract example file to user config file
gunzip -c "$example_conf" > "$user_conf"
else
cp "$example_conf" "$user_conf_dir"
fi
fi
# Regular expressions
readonly re_section_line='^\[(.*)\]$'
readonly re_empty_comment_line='(^$)|(^[[:space:]]*(\#)|(;))'
readonly re_attribute_line='^([[:space:]]*)([_[:alnum:]]+)'
# Create an array with the file lines
mapfile -t conf < "$user_conf"
# Iterate over the file lines
for idx in "${!conf[@]}"; do
# Current line
curr_line="${conf[$idx]}"
# If we are in a new section:
if [[ "$curr_line" =~ $re_section_line ]]; then
curr_section="${BASH_REMATCH[1]}"
continue
fi
# Skip the line if it is empty or has a comment
if [[ "$curr_line" =~ $re_empty_comment_line ]]; then
continue
fi
# Get the attribute in the current line
[[ "$curr_line" =~ $re_attribute_line ]]
curr_attr_name="${BASH_REMATCH[2]}"
curr_sett_name="${curr_section}-${curr_attr_name}"
# If the current attribute is not in our dictionary, continue
case "$valid_keys" in
*"$curr_sett_name"*)
printf -v conf[$idx] ' %s = %s' \
"${curr_attr_name}" \
"${theme_attr_dict[$curr_sett_name]}"
;;
esac
done
# Create a header for the xr_color config file
user_xr_color_conf_content="\
###################################
#
# Config file created with
# $script_name wrapper
#
###################################
"
# After everything is completed, write the new config to a file
user_xr_color_conf_content+="$(printf '%s\n' "${conf[@]}")"
printf '%s\n' "$user_xr_color_conf_content" > "$user_xr_color_conf"
printf '"%s" updated successfully.\n' "$user_xr_color_conf"

View File

@ -1,75 +0,0 @@
#!/usr/bin/env sh
# progress-notify - Send audio and brightness notifications for dunst
# dependencies: dunstify, ponymix, Papirus (icons)
### How to use: ###
# Pass the values via stdin and provide the notification type
# as an argument. Options are audio, brightness and muted
### Audio notifications ###
# ponymix increase 5 | notify audio
# ponymix decrease 5 | notify audio
# pulsemixer --toggle-mute --get-mute | notify muted
### Brightness notifications ###
# xbacklight -inc 5 && xbacklight -get | notify brightness
# xbacklight -dec 5 && xbacklight -get | notify brightness
notifyMuted() {
volume="$1"
dunstify -h string:x-canonical-private-synchronous:audio "Muted" -h int:value:"$volume" -t 1500 --icon audio-volume-muted
}
notifyAudio() {
volume="$1"
ponymix is-muted && notifyMuted "$volume" && return
if [ $volume -eq 0 ]; then
notifyMuted "$volume"
elif [ $volume -le 30 ]; then
dunstify -h string:x-canonical-private-synchronous:audio "Volume: " -h int:value:"$volume" -t 1500 --icon audio-volume-low
elif [ $volume -le 70 ]; then
dunstify -h string:x-canonical-private-synchronous:audio "Volume: " -h int:value:"$volume" -t 1500 --icon audio-volume-medium
else
dunstify -h string:x-canonical-private-synchronous:audio "Volume: " -h int:value:"$volume" -t 1500 --icon audio-volume-high
fi
}
notifyBrightness() {
brightness="$1"
if [ $brightness -eq 0 ]; then
dunstify -h string:x-canonical-private-synchronous:brightness "Brightness: " -h int:value:"$brightness" -t 1500 --icon display-brightness-off-symbolic
elif [ $brightness -le 30 ]; then
dunstify -h string:x-canonical-private-synchronous:brightness "Brightness: " -h int:value:"$brightness" -t 1500 --icon display-brightness-low-symbolic
elif [ $brightness -le 70 ]; then
dunstify -h string:x-canonical-private-synchronous:brightness "Brightness: " -h int:value:"$brightness" -t 1500 --icon display-brightness-medium-symbolic
else
dunstify -h string:x-canonical-private-synchronous:brightness "Brightness: " -h int:value:"$brightness" -t 1500 --icon display-brightness-high-symbolic
fi
}
input=`cat /dev/stdin`
case "$1" in
muted)
volume=`ponymix get-volume`
if [ "$input" -eq 0 ]
then
notifyAudio "$volume"
else
notifyMuted "$volume"
fi
;;
audio)
notifyAudio "$input"
;;
brightness)
notifyBrightness "$input"
;;
*)
echo "Not the right arguments"
echo "$1"
exit 2
esac

Binary file not shown.

Before

Width:  |  Height:  |  Size: 388 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 406 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 672 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 214 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 393 KiB

View File

@ -1,172 +0,0 @@
=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.
=head2 Autostarting dunst
On most installations dunst should be able to automatically be started by D-Bus
when a notification is sent. This is not recommended when multiple notification
deamons are installed, because D-Bus will not know which one to start.
Other ways of autostarting dunst include starting dunst with your desktop
environment or window manager's autostart functionality or via the provided
systemd service.
=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.
=item B<->F<SETTING> B<[value]>
Where F<SETTING> can be any setting that's available in the global section of
the configuration file. See dunst(5) for possible settings.
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.
=back
=head1 CONFIGURATION
An example configuration file is included (usually /etc/dunst/dunstrc). Note:
this was previously /usr/share/dunst/dunstrc.
Before using dunst, copy this file to ~/.config/dunst/dunstrc and edit
it accordingly. See dunst(5) for all possible settings.
=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 -h string:frcolor:#44ff44
=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 prepended 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. It is preferred to set this keybinding with your window
manager or desktop envirorment and let it execute C<dunsctl context>. Another
option is to set this keybinding in your dunstrc, but this is soon to be deprecated
(and doesn't work on Wayland).
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. You can change the mouse button to right click
by setting C<mouse_right_click = close_all> in your dunstrc.
=head1 MISCELLANEOUS
Dunst can be paused via the `dunstctl set-paused true` command. To unpause dunst use
`dunstctl set-paused false`.
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
These are the places where dunst will look for a configuration file. They are
listed here in order and if dunst finds one of them, it will stop looking for
more.
$XDG_CONFIG_HOME/dunst/dunstrc
$HOME/.config/dunst/dunstrc
-or-
$XDG_CONFIG_HOME/dunst/dunstrc
/etc/xdg/dunst/dunstrc
=over 4
=item /etc/dunst/dunstrc
This is where the default config file is located
=back
=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
dunst(5), dunstctl(1), dmenu(1), notify-send(1)

View File

@ -1,20 +1,48 @@
=head1 NAME
dunst - configuration file
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
An example configuration file is included (usually /etc/dunst/dunstrc). Note:
this was previously /usr/share/dunst/dunstrc.
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 setions 'urgency_low',
'urgency_normal' and 'urgency_critical' are for low, normal and critical urgency
notifications respectively. The 'shortcuts' section (deprecated) contains all
keyboard configuration and the 'experimental' section all the features that have
not yet been tested thoroughly.
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.
@ -22,6 +50,24 @@ 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
@ -36,10 +82,6 @@ starts at 0. See the B<follow> setting.
Defines where the notifications should be placed in a multi-monitor setup. All
values except I<none> override the B<monitor> setting.
On Wayland there is no difference between mouse and keyboard focus. When either
of the is used, the compositor will choose an output. This will generally be
the output last interacted with.
=over 4
=item B<none>
@ -100,32 +142,6 @@ the notification on the left border of the screen while a horizontal offset of
=back
=item B<progress_bar> (values: [true/false], default: true)
When an integer value is passed to dunst as a hint (see B<NOTIFY-SEND>), a
progress bar will be drawn at the bottom of the notification. This
behavior can be turned off by setting this setting to false.
=item B<progress_bar_height> (default: 10)
The height of the progress bar in pixel. This includes the frame. Make sure
this value is bigger than twice the frame width.
=item B<progress_bar_min_width> (default: 150)
The minimum width of the progress bar in pixels. The notification is rescaled
to fit the bar.
=item B<progress_bar_max_width> (default: 300)
The maximum width of the progress bar in pixels. The notification is resized
to fit the progress bar.
=item B<progress_bar_frame_width> (default: 1)
The frame width of the progress bar in pixels. This value should be smaller
than half of the progress bar height.
=item B<indicate_hidden> (values: [true/false], default: true)
If this is set to true, a notification indicating how many notifications are
@ -172,23 +188,6 @@ in the vertical axis
The distance in pixels from the content to the border of the window
in the horizontal axis
=item B<text_icon_padding> (default: 0)
The distance in pixels from the text to the icon (or vice versa)
in the horizontal axis.
Setting this to a non-zero value overwrites any padding that horizontal_padding was adding between the notification text and icon.
So for example setting
text_icon_padding=10
horizontal_padding=10
is equivalent to
text_icon_padding=0
horizontal_padding=10
=item B<frame_width> (default: 0)
Defines width in pixels of frame around the notification window. Set to 0 to
@ -236,27 +235,8 @@ See TIME FORMAT for valid times.
Set to 0 to disable.
A client can mark a notification as transient to bypass this setting and timeout
anyway. Use a rule with 'set_transient = no' to disable this behavior.
Note: this doesn't work on xwayland.
=item B<layer> (Wayland only)
One of bottom, top or overlay.
Place dunst notifications on the selected layer. Using overlay
will cause notifications to be displayed above fullscreen windows, though
this may also occur at top depending on your compositor.
The bottom layer is below all windows and above the background.
Default: overlay
=item B<force_xwayland> (values: [true/false], default: false) (Wayland only)
Force the use of X11 output, even on a wayland compositor. This setting
has no effect when not using a Wayland compositor.
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")
@ -292,7 +272,7 @@ Allow a small subset of html markup in notifications
<u>underline</u>
For a complete reference see
<https://developer.gnome.org/pango/stable/pango-Markup.html>
<http://developer.gnome.org/pango/stable/PangoMarkupFormat.html>
=item B<strip>
@ -357,11 +337,6 @@ removed from the format.
Defines how the text should be aligned within the notification.
=item B<vertical_alignment> (values: [top/center/bottom], default: center)
Defines how the text and icon should be aligned vertically within the
notification. If icons are disabled, this option has no effect.
=item B<show_age_threshold> (default: -1)
Show age of message if message is older than this time.
@ -373,7 +348,7 @@ Set to -1 to disable.
Specifies how very long lines should be handled
If it's set to false, long lines will be truncated and ellipsized.
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.
@ -395,7 +370,7 @@ 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_duplicate_count> (values: [true/false], default: false)
=item B<hide_duplicates_count> (values: [true/false], default: false)
Hide the count of stacked duplicate notifications.
@ -409,28 +384,14 @@ ACTIONS below for further details.
Defines the position of the icon in the notification window. Setting it to off
disables icons.
=item B<min_icon_size> (default: 0)
Defines the minimum size in pixels for the icons.
If the icon is larger than or equal to the specified value it won't be affected.
If it's smaller then it will be scaled up so that the smaller axis is equivalent
to the specified size.
Set to 0 to disable icon upscaling. (default)
If B<icon_position> is set to off, this setting is ignored.
=item B<max_icon_size> (default: 0)
Defines the maximum size in pixels for the icons.
If the icon is smaller than or equal to the specified value it won't be affected.
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 downscaling. (default)
If both B<min_icon_size> and B<max_icon_size> are enabled, the latter
gets the last say.
Set to 0 to disable icon scaling. (default)
If B<icon_position> is set to off, this setting is ignored.
@ -483,13 +444,7 @@ WM_CLASS). There should be no need to modify this setting for regular use.
Display a notification on startup. This is usually used for debugging and there
shouldn't be any need to use this option.
=item B<verbosity> (values: 'crit', 'warn', 'mesg', 'info', 'debug' default 'mesg')
Do not display log messages, which have lower precedence than specified
verbosity. This won't affect printing notifications on the terminal. Use
the '-print' option for this.
=item B<force_xinerama> (values: [true/false], default: false) (X11 only)
=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
@ -499,67 +454,9 @@ 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.
=item B<corner_radius> (default: 0)
Define the corner radius in pixels. A corner radius of 0 will result in
rectangular shaped notifications.
By enabling this setting the outer border and the frame will be shaped.
If you have multiple notifications, the whole window is shaped, not every
single notification.
To avoid the corners clipping the icon or text the corner radius will be
automatically lowered to half of the notification height if it exceeds it.
=item B<mouse_left/middle/right_click> (values: [none/do_action/close_current/close_all/context/context_all])
Defines action of mouse click. A touch input in Wayland acts as a mouse left
click.
=over 4
=item B<none>
Don't do anything.
=item B<do_action> (default for mouse_middle_click)
Invoke the action determined by the action_name rule. If there is no such
action, open the context menu.
=item B<open_url>
If the notification has exactly one url, open it. If there are multiple
ones, open the context menu.
=item B<close_current> (default for mouse_left_click)
Close current notification.
=item B<close_all> (default for mouse_right_click)
Close all notifications.
=item B<context>
Open context menu for the notification.
=item B<context_all>
Open context menu for all notifications.
=back
=item B<ignore_dbusclose> (default: false)
Ignore the dbus closeNotification message. This is useful to enforce the timeout
set by dunst configuration. Without this parameter, an application may close
the notification sent before the user defined timeout.
=back
=head2 Shortcut section B<DEPRECATED SEE DUNSTCTL> (X11 only)
=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.
@ -596,8 +493,8 @@ Specifies the keyboard shortcut that opens the context menu.
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, hightlight, timeout,
frame_color and icon attributes can be modified.
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.
@ -630,12 +527,6 @@ Defines the background color for low, normal and critical notifications respecti
See COLORS for the value format.
=item B<-lh/nh/ch color>
Defines the highlight 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.
@ -650,53 +541,18 @@ See TIME FORMAT for valid times.
=back
=head1 DUNSTCTL
Dunst now contains a command line control command that can be used to interact
with it. It supports all functions previously done only via keyboard shortcuts
but also has a lot of extra functionality. So see more see dunstctl(1).
=head1 HISTORY
Dunst saves a number of notifications (specified by B<history_length>) in memory.
These notifications can be recalled (i.e. redisplayed) by calling
B<dunstctl history> (see dunstctl(1)). Whether these notifications will time out
like if they have been just send depends on the value of the B<sticky_history>
setting.
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 WAYLAND
Dunst has Wayland support since version 1.6.0. Because the Wayland protocol
is more focused on security, some things that are possible in X11 are not
possible in Wayland. Those differences are reflected in the configuration.
The main things that change are that dunst on Wayland cannot use global
hotkeys (they are deprecated anyways, use dunstctl).
Some dunst features on wayland might need your compositor to support a certain
protocol. Dunst will warn you if an optional feature isn't supported and will
disable the corresponding functionality.
Fullscreen detection works on wayland with some limitations (see B<fullscreen>).
If you want notifications to appear over fullscreen windows, set
B<layer = overlay> in the global options.
Note that the same limitations exist when using xwayland.
If something doesn't quite work in Wayland, please file a bug report. In the
mean time, you can try if the X11 output does work on wayland. Use
B<force_xwayland = true> for that.
If you have your dunst notifications on the same side of your display as your
status bar, you might notice that your notifications appear a bit higher or
lower than on X11. This is because the notification cannot be placed on top of
your status bar. The notifications are placed relative to your status bar,
making them appear higher or lower by the height of your status bar. We cannot
do anything about that behavior, so you will need to change your B<geometry>
variable accordingly.
=head1 RULES
Rules allow the conditional modification of notifications. They are defined by
@ -712,64 +568,11 @@ matched.
=item B<filtering>
Notifications can be matched for any of the following attributes:
=over 4
=item C<appname> (discouraged, see desktop_entry)
The name of the application as reported by the client. Be aware that the name
can often differ depending on the locale used.
=item C<body>
The body of the notification
=item C<category>
The category of the notification as defined by the notification spec. See
https://developer.gnome.org/notification-spec/#categories
=item C<desktop_entry>
GLib based applications export their desktop-entry name. In comparison to the appname,
the desktop-entry won't get localized.
=item C<icon>
The icon of the notification in the form of a file path. Can be empty if no icon
is available or a raw icon is used instead.
=item C<match_transient>
Match if the notification has been declared as transient by the client or by
some other rule.
See C<set_transient> for more details about this attribute.
=item C<msg_urgency>
Matches the urgency of the notification as set by the client or by some other
rule.
=item C<stack_tag>
Matches the stack tag of the notification as set by the client or by some other
rule.
See set_stack_tag for more information about stack tags.
=item C<summary>
Matches the summary, 'title', of the notification.
=back
C<msg_urgency> is the urgency of the notification, it is named so to not conflict
with trying to modify the urgency.
Instead of the appname filter, it's recommended to use the desktop_entry filter.
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:
@ -784,107 +587,9 @@ Shell-like globing is supported.
=item B<modifying>
The following attributes can be overridden:
=over 4
=item C<background>
The background color of the notification. See COLORS for possible values.
=item C<foreground>
The foreground color of the notification. See COLORS for possible values.
=item C<highlight>
The highlight color of the notification. This color is used for coloring the
progress bar. See COLORS for possible values.
=item C<format>
Equivalent to the C<format> setting.
=item C<frame_color>
The frame color color of the notification. See COLORS for possible values.
=item C<fullscreen>
One of show, delay, or pushback.
This attribute specifies how notifications are handled if a fullscreen window
is focused. By default it's set to show so notifications are being shown.
Other possible values are delay: Already shown notifications are continued to be
displayed until they are dismissed or time out but new notifications will be
held back and displayed when the focus to the fullscreen window is lost.
Or pushback which is equivalent to delay with the difference that already
existing notifications are paused and hidden until the focus to the fullscreen
window is lost.
On wayland, if B<follow> is set to mouse or keyboard, the output where the
notification is located cannot be determined. So dunst will delay or pushback if
any of the outputs is fullscreen. Since the fullscreen protocol is fairly new,
you will need a recent version of a compositor that supports it. At the time of
writing, you will need the git version of sway.
See also B<layer> to change if notifications appear above fullscreen windows in
Wayland.
Default: show
=item C<new_icon>
Updates the icon of the notification, it should be a path to a valid image.
=item C<set_stack_tag>
Sets the stack tag for the notification, notifications with the same (non-empty)
stack tag will replace each-other so only the newest one is visible. This can be
useful for example in volume or brightness notifications where you only want one of
the same type visible.
The stack tag can be set by the client with the 'synchronous',
'private-synchronous' 'x-canonical-private-synchronous' or the
'x-dunst-stack-tag' hints.
=item C<set_transient>
Sets whether the notification is considered transient.
Transient notifications will bypass the idle_threshold setting.
By default notifications are _not_ considered transient but clients can set the
value of this by specifying the 'transient' hint when sending notifications.
=item C<timeout>
Equivalent to the C<timeout> setting in the urgency sections.
=item C<urgency>
This sets the notification urgency.
B<IMPORTANT NOTE>: This currently DOES NOT re-apply the attributes from the
urgency_* sections. The changed urgency will only be visible in rules defined
later. Use C<msg_urgency> to match it.
=item C<skip_display>
Setting this to true will prevent the notification from being displayed
initially but will be saved in history for later viewing.
=item C<action_name>
Sets the name of the action to be invoked on do_action. If not specified, the
action set as default action or the only available action will be invoked.
Default: "default"
=back
As with the filtering attributes, each one corresponds to
the respective notification attribute to be modified.
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.
@ -900,22 +605,11 @@ 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 environment variables. The following variables are available:
B<DUNST_APP_NAME>, B<DUNST_SUMMARY>, B<DUNST_BODY>, B<DUNST_ICON_PATH>,
B<DUNST_URGENCY>, B<DUNST_ID>, B<DUNST_PROGRESS>, B<DUNST_CATEGORY>,
B<DUNST_STACK_TAG>, B<DUNST_URLS>, B<DUNST_TIMEOUT>, B<DUNST_TIMESTAMP>
and B<DUNST_STACK_TAG>.
passed via command line parameters in the following order: appname, summary,
body, icon, urgency.
Another, less recommended way to get notifcations details from a script is via
command line parameters. These are passed to the script in the following order:
B<appname>, B<summary>, B<body>, B<icon_path>, B<urgency>.
Where B<DUNST_ICON_PATH> or B<icon_path> is the absolute path to the icon file
if there is one. B<DUNST_URGENCY> or B<urgency> is one of "LOW", "NORMAL" or
"CRITICAL". B<DUNST_URLS> is a newline-separated list of urls associated with
the notification.
Note that some variables may be empty.
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.
@ -930,8 +624,6 @@ 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.
You may also specify a transparency component in #RGBA or #RRGGBBAA format.
B<NOTE>: '#' is interpreted as a comment, to use it the entire value needs to
be in quotes like so: separator_color="#123456"
@ -941,51 +633,11 @@ 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.
B<All hints>
See RULES for more detailed explanations for some options.
=over 4
=item B<fgcolor>:
Foreground cololor
=item B<bgcolor>:
Background color
=item B<frcolor>:
Frame color
=item B<hlcolor>:
Highlight color
=item B<value>:
Progress value.
=item B<image-path>:
Icon name. This may be a path or just the icon name.
=item B<image-data>:
A stream of raw image data.
=item B<category>:
The category.
=item B<desktop_entry>:
The desktop entry.
=item B<transient>:
The transient value.
=back
B<Examples>
=over 4
=item notify-send -h string:fgcolor:#ff4444
=item notify-send -h string:bgcolor:#4444ff -h string:fgcolor:#ff4444 -h string:frcolor:#44ff44
=item notify-send -h string:bgcolor:#4444ff -h string:fgcolor:#ff4444
=item notify-send -h int:value:42 "Working ..."
@ -998,7 +650,7 @@ 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 prepended to
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.
@ -1023,8 +675,8 @@ Example time: "1000ms" "10m"
=head1 MISCELLANEOUS
Dunst can be paused via the `dunstctl set-paused true` command. To unpause dunst use
`dunstctl set-paused false`.
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:
@ -1043,27 +695,11 @@ missed notifications after returning to the computer.
=head1 FILES
These are the places where dunst will look for a configuration file. They are
listed here in order and if dunst finds one of them, it will stop looking for
more.
$XDG_CONFIG_HOME/dunst/dunstrc
$HOME/.config/dunst/dunstrc
-or-
$XDG_CONFIG_HOME/dunst/dunstrc
/etc/xdg/dunst/dunstrc
=over 4
=item /etc/dunst/dunstrc
This is where the default config file is located
=back
$HOME/.config/dunst/dunstrc
=head1 AUTHORS
@ -1081,4 +717,4 @@ If you feel that copyrights are violated, please send me an email.
=head1 SEE ALSO
dunst(1), dunstctl(1), dmenu(1), notify-send(1)
dwm(1), dmenu(1), twmn(1), notify-send(1)

View File

@ -1,64 +0,0 @@
=head1 NAME
dunstctl - Command line control utility for dunst, a customizable and lightweight notification-daemon
=head1 SYNOPSIS
dunstctl COMMAND [PARAMETER]
=head1 COMMANDS
=over 4
=item B<action> notification_position
Performs the default action or, if not available, opens the context menu of the
notification at the given position (starting count at the top, first
notification being 0).
=item B<close>
Close the topmost notification currently being displayed.
=item B<close-all>
Close all notifications currently being displayed
=item B<context>
Open the context menu, presenting all available actions and urls for the
currently open notifications.
=item B<count> [displayed/history/waiting]
Returns the number of displayed, shown and waiting notifications. If no argument
is provided, everything will be printed.
=item B<history-pop>
Redisplay the notification that was most recently closed. This can be called
multiple times to show older notifications, up to the history limit configured
in dunst.
=item B<is-paused>
Check if dunst is currently running or paused. If dunst is paused notifications
will be kept but not shown until it is unpaused.
=item B<set-paused> true/false/toggle
Set the paused status of dunst. If false, dunst is running normally, if true,
dunst is paused. See the is-paused command and the dunst man page for more
information.
=item B<debug>
Tries to contact dunst and checks for common faults between dunstctl and dunst.
Useful if something isn't working
=item B<help>
Show all available commands with a brief description
=back

View File

@ -1,245 +0,0 @@
# Doxyfile 1.8.14
#---------------------------------------------------------------------------
# Project related configuration options
#---------------------------------------------------------------------------
DOXYFILE_ENCODING = UTF-8
PROJECT_NAME = Dunst
PROJECT_NUMBER =
PROJECT_BRIEF = "Lightweight notification daemon"
PROJECT_LOGO =
OUTPUT_DIRECTORY = docs/internal
CREATE_SUBDIRS = NO
ALLOW_UNICODE_NAMES = NO
OUTPUT_LANGUAGE = English
BRIEF_MEMBER_DESC = YES
REPEAT_BRIEF = YES
ALWAYS_DETAILED_SEC = NO
INLINE_INHERITED_MEMB = NO
FULL_PATH_NAMES = YES
STRIP_FROM_PATH =
STRIP_FROM_INC_PATH =
SHORT_NAMES = NO
JAVADOC_AUTOBRIEF = NO
QT_AUTOBRIEF = NO
MULTILINE_CPP_IS_BRIEF = NO
INHERIT_DOCS = YES
SEPARATE_MEMBER_PAGES = NO
TAB_SIZE = 8
ALIASES =
TCL_SUBST =
OPTIMIZE_OUTPUT_FOR_C = YES
OPTIMIZE_OUTPUT_JAVA = NO
OPTIMIZE_FOR_FORTRAN = NO
OPTIMIZE_OUTPUT_VHDL = NO
EXTENSION_MAPPING =
MARKDOWN_SUPPORT = YES
TOC_INCLUDE_HEADINGS = 0
AUTOLINK_SUPPORT = YES
BUILTIN_STL_SUPPORT = NO
CPP_CLI_SUPPORT = NO
SIP_SUPPORT = NO
IDL_PROPERTY_SUPPORT = YES
DISTRIBUTE_GROUP_DOC = NO
GROUP_NESTED_COMPOUNDS = NO
SUBGROUPING = YES
INLINE_GROUPED_CLASSES = NO
INLINE_SIMPLE_STRUCTS = NO
TYPEDEF_HIDES_STRUCT = YES
LOOKUP_CACHE_SIZE = 0
#---------------------------------------------------------------------------
# Build related configuration options
#---------------------------------------------------------------------------
EXTRACT_ALL = YES
EXTRACT_PRIVATE = YES
EXTRACT_PACKAGE = YES
EXTRACT_STATIC = YES
EXTRACT_LOCAL_CLASSES = YES
EXTRACT_LOCAL_METHODS = YES
EXTRACT_ANON_NSPACES = NO
HIDE_UNDOC_MEMBERS = NO
HIDE_UNDOC_CLASSES = NO
HIDE_FRIEND_COMPOUNDS = NO
HIDE_IN_BODY_DOCS = NO
INTERNAL_DOCS = NO
CASE_SENSE_NAMES = NO
HIDE_SCOPE_NAMES = YES
HIDE_COMPOUND_REFERENCE= NO
SHOW_INCLUDE_FILES = YES
SHOW_GROUPED_MEMB_INC = NO
FORCE_LOCAL_INCLUDES = NO
INLINE_INFO = YES
SORT_MEMBER_DOCS = YES
SORT_BRIEF_DOCS = NO
SORT_MEMBERS_CTORS_1ST = NO
SORT_GROUP_NAMES = NO
SORT_BY_SCOPE_NAME = NO
STRICT_PROTO_MATCHING = NO
GENERATE_TODOLIST = YES
GENERATE_TESTLIST = YES
GENERATE_BUGLIST = YES
GENERATE_DEPRECATEDLIST= YES
ENABLED_SECTIONS =
MAX_INITIALIZER_LINES = 30
SHOW_USED_FILES = YES
SHOW_FILES = YES
SHOW_NAMESPACES = YES
FILE_VERSION_FILTER =
LAYOUT_FILE =
CITE_BIB_FILES =
#---------------------------------------------------------------------------
# Configuration options related to warning and progress messages
#---------------------------------------------------------------------------
QUIET = NO
WARNINGS = YES
WARN_IF_UNDOCUMENTED = YES
WARN_IF_DOC_ERROR = YES
WARN_NO_PARAMDOC = YES
WARN_AS_ERROR = YES
WARN_FORMAT = "$file:$line: $text"
WARN_LOGFILE =
#---------------------------------------------------------------------------
# Configuration options related to the input files
#---------------------------------------------------------------------------
INPUT = . \
HACKING.md
INPUT_ENCODING = UTF-8
FILE_PATTERNS = *.c \
*.h
RECURSIVE = YES
EXCLUDE =
EXCLUDE_SYMLINKS = NO
EXCLUDE_PATTERNS = */test/greatest.h
EXCLUDE_SYMBOLS =
EXAMPLE_PATH =
EXAMPLE_PATTERNS = *
EXAMPLE_RECURSIVE = NO
IMAGE_PATH =
INPUT_FILTER =
FILTER_PATTERNS =
FILTER_SOURCE_FILES = NO
FILTER_SOURCE_PATTERNS =
USE_MDFILE_AS_MAINPAGE = HACKING.md
#---------------------------------------------------------------------------
# Configuration options related to source browsing
#---------------------------------------------------------------------------
SOURCE_BROWSER = YES
INLINE_SOURCES = NO
STRIP_CODE_COMMENTS = YES
REFERENCED_BY_RELATION = NO
REFERENCES_RELATION = NO
REFERENCES_LINK_SOURCE = YES
SOURCE_TOOLTIPS = YES
USE_HTAGS = NO
VERBATIM_HEADERS = YES
#---------------------------------------------------------------------------
# Configuration options related to the alphabetical class index
#---------------------------------------------------------------------------
ALPHABETICAL_INDEX = YES
COLS_IN_ALPHA_INDEX = 5
IGNORE_PREFIX =
#---------------------------------------------------------------------------
# Configuration options related to the HTML output
#---------------------------------------------------------------------------
GENERATE_HTML = YES
HTML_OUTPUT = html
HTML_FILE_EXTENSION = .html
HTML_HEADER =
HTML_FOOTER =
HTML_STYLESHEET =
HTML_EXTRA_STYLESHEET =
HTML_EXTRA_FILES =
HTML_COLORSTYLE_HUE = 220
HTML_COLORSTYLE_SAT = 100
HTML_COLORSTYLE_GAMMA = 80
HTML_TIMESTAMP = YES
HTML_DYNAMIC_MENUS = YES
HTML_DYNAMIC_SECTIONS = YES
HTML_INDEX_NUM_ENTRIES = 0
GENERATE_DOCSET = NO
GENERATE_HTMLHELP = NO
GENERATE_CHI = NO
GENERATE_QHP = NO
GENERATE_ECLIPSEHELP = NO
ECLIPSE_DOC_ID = org.dunst-project.dunst
DISABLE_INDEX = NO
GENERATE_TREEVIEW = YES
ENUM_VALUES_PER_LINE = 4
TREEVIEW_WIDTH = 250
EXT_LINKS_IN_WINDOW = NO
FORMULA_FONTSIZE = 10
FORMULA_TRANSPARENT = YES
USE_MATHJAX = NO
SEARCHENGINE = YES
SERVER_BASED_SEARCH = NO
EXTERNAL_SEARCH = NO
SEARCHENGINE_URL =
SEARCHDATA_FILE = searchdata.xml
EXTERNAL_SEARCH_ID =
EXTRA_SEARCH_MAPPINGS =
#---------------------------------------------------------------------------
# Multiple disabled output formats
#---------------------------------------------------------------------------
GENERATE_LATEX = NO
GENERATE_RTF = NO
GENERATE_MAN = NO
GENERATE_XML = NO
GENERATE_DOCBOOK = NO
GENERATE_AUTOGEN_DEF = NO
GENERATE_PERLMOD = NO
ENABLE_PREPROCESSING = YES
MACRO_EXPANSION = NO
EXPAND_ONLY_PREDEF = NO
SEARCH_INCLUDES = YES
INCLUDE_PATH =
INCLUDE_FILE_PATTERNS =
PREDEFINED =
EXPAND_AS_DEFINED =
SKIP_FUNCTION_MACROS = YES
#---------------------------------------------------------------------------
# Configuration options related to external references
#---------------------------------------------------------------------------
TAGFILES =
GENERATE_TAGFILE =
ALLEXTERNALS = NO
EXTERNAL_GROUPS = YES
EXTERNAL_PAGES = YES
#---------------------------------------------------------------------------
# Configuration options related to the dot tool
#---------------------------------------------------------------------------
CLASS_DIAGRAMS = YES
MSCGEN_PATH =
DIA_PATH =
HIDE_UNDOC_RELATIONS = YES
HAVE_DOT = YES
DOT_NUM_THREADS = 0
DOT_FONTNAME = Helvetica
DOT_FONTSIZE = 10
DOT_FONTPATH =
CLASS_GRAPH = YES
COLLABORATION_GRAPH = YES
GROUP_GRAPHS = YES
UML_LOOK = NO
UML_LIMIT_NUM_FIELDS = 10
TEMPLATE_RELATIONS = NO
INCLUDE_GRAPH = YES
INCLUDED_BY_GRAPH = YES
CALL_GRAPH = NO
CALLER_GRAPH = NO
GRAPHICAL_HIERARCHY = YES
DIRECTORY_GRAPH = YES
DOT_IMAGE_FORMAT = svg
INTERACTIVE_SVG = YES
DOT_PATH =
DOTFILE_DIRS =
MSCFILE_DIRS =
DIAFILE_DIRS =
PLANTUML_JAR_PATH =
PLANTUML_CFG_FILE =
PLANTUML_INCLUDE_PATH =
DOT_GRAPH_MAX_NODES = 50
MAX_DOT_GRAPH_DEPTH = 0
DOT_TRANSPARENT = NO
DOT_MULTI_TARGETS = NO
GENERATE_LEGEND = YES
DOT_CLEANUP = YES

View File

@ -1,33 +0,0 @@
# Main repo
- [ ] Verify that the changelog is up to date
- [ ] Write release notes (Only if non-patch release)
- [ ] Verify that the working directory is clean and on the master branch
- [ ] Change the version in the Makefile to "x.x.x (iso-date)"
- [ ] Update changelog `Unreleased` entry to the new version
- [ ] Commit changes (Commit title: `Dunst vX.X.X`)
- [ ] Tag commit, make sure it's an annotated tag (git tag -a) (Tag title: Dunst vX.X.X)
- [ ] Push commits
- [ ] Push tags
# Dunst-project.org
- [ ] Update release number in the download page
- [ ] Update release date in the download page
- [ ] Update version number in the download link
- [ ] Copy release notes to the download page (Only if non-patch release)
- [ ] Copy changelog to the changelog page
- [ ] Copy documentation to the documentation page
- [ ] Verify that they look fine when rendered
- [ ] Commit changes
- [ ] Run deploy script
- [ ] Push to main website repo
- [ ] Push to gh-pages
# Main repo
- [ ] Copy release notes to githubs release feature
- [ ] Publish release on github
- [ ] Update maint branch to point to master
- [ ] Create new Unreleased section for the changelog
- [ ] Update Makefile version to -non-git
- [ ] Commit & push

View File

@ -7,3 +7,7 @@ PartOf=graphical-session.target
Type=dbus
BusName=org.freedesktop.Notifications
ExecStart=##PREFIX##/bin/dunst
[Install]
WantedBy=default.target

122
dunstctl
View File

@ -1,122 +0,0 @@
#!/bin/sh
set -eu
DBUS_NAME="org.freedesktop.Notifications"
DBUS_PATH="/org/freedesktop/Notifications"
DBUS_IFAC_DUNST="org.dunstproject.cmd0"
DBUS_IFAC_PROP="org.freedesktop.DBus.Properties"
DBUS_IFAC_FDN="org.freedesktop.Notifications"
die(){ printf "%s\n" "${1}" >&2; exit 1; }
show_help() {
cat <<-EOH
Usage: dunstctl <command> [parameters]"
Commands:
action Perform the default action, or open the
context menu of the notification at the
given position
close Close the last notification
close-all Close the all notifications
context Open context menu
count [displayed|history|waiting] Show the number of notifications
history-pop Pop one notification from history
is-paused Check if dunst is running or paused
set-paused [true|false|toggle] Set the pause status
debug Print debugging information
help Show this help
EOH
}
dbus_send_checked() {
dbus-send "$@" \
|| die "Failed to communicate with dunst, is it running? Or maybe the version is outdated. You can try 'dunstctl debug' as a next debugging step."
}
method_call() {
dbus_send_checked --print-reply=literal --dest="${DBUS_NAME}" "${DBUS_PATH}" "$@"
}
property_get() {
dbus_send_checked --print-reply=literal --dest="${DBUS_NAME}" "${DBUS_PATH}" "${DBUS_IFAC_PROP}.Get" "string:${DBUS_IFAC_DUNST}" "string:${1}"
}
property_set() {
dbus_send_checked --print-reply=literal --dest="${DBUS_NAME}" "${DBUS_PATH}" "${DBUS_IFAC_PROP}.Set" "string:${DBUS_IFAC_DUNST}" "string:${1}" "${2}"
}
command -v dbus-send >/dev/null 2>/dev/null || \
die "Command dbus-send not found"
case "${1:-}" in
"action")
method_call "${DBUS_IFAC_DUNST}.NotificationAction" "int32:${2:-0}" >/dev/null
;;
"close")
method_call "${DBUS_IFAC_DUNST}.NotificationCloseLast" >/dev/null
;;
"close-all")
method_call "${DBUS_IFAC_DUNST}.NotificationCloseAll" >/dev/null
;;
"context")
method_call "${DBUS_IFAC_DUNST}.ContextMenuCall" >/dev/null
;;
"count")
[ $# -eq 1 ] || [ "${2}" = "displayed" ] || [ "${2}" = "history" ] || [ "${2}" = "waiting" ] \
|| die "Please give either 'displayed', 'history', 'waiting' or none as count parameter."
if [ $# -eq 1 ]; then
property_get waitingLength | ( read -r _ _ waiting; printf " Waiting: %s\n" "${waiting}" )
property_get displayedLength | ( read -r _ _ displayed; printf " Currently displayed: %s\n" "${displayed}" )
property_get historyLength | ( read -r _ _ history; printf " History: %s\n" "${history}")
else
property_get ${2}Length | ( read -r _ _ notifications; printf "%s\n" "${notifications}"; )
fi
;;
"history-pop")
method_call "${DBUS_IFAC_DUNST}.NotificationShow" >/dev/null
;;
"is-paused")
property_get paused | ( read -r _ _ paused; printf "%s\n" "${paused}"; )
;;
"set-paused")
[ "${2:-}" ] \
|| die "No status parameter specified. Please give either 'true', 'false' or 'toggle' as paused parameter."
[ "${2}" = "true" ] || [ "${2}" = "false" ] || [ "${2}" = "toggle" ] \
|| die "Please give either 'true', 'false' or 'toggle' as paused parameter."
if [ "${2}" = "toggle" ]; then
paused=$(property_get paused | ( read -r _ _ paused; printf "%s\n" "${paused}"; ))
if [ "${paused}" = "true" ]; then
property_set paused variant:boolean:false
else
property_set paused variant:boolean:true
fi
else
property_set paused variant:boolean:"$2"
fi
;;
"help"|"--help"|"-h")
show_help
;;
"debug")
dbus-send --print-reply=literal --dest="${DBUS_NAME}" "${DBUS_PATH}" "${DBUS_IFAC_FDN}.GetServerInformation" >/dev/null 2>/dev/null \
|| die "Dunst is not running."
dbus-send --print-reply=literal --dest="${DBUS_NAME}" "${DBUS_PATH}" "${DBUS_IFAC_FDN}.GetServerInformation" \
| (
read -r name _ version _
[ "${name}" = "dunst" ]
printf "dunst version: %s\n" "${version}"
) \
|| die "Another notification manager is running. It's not dunst"
dbus-send --print-reply=literal --dest="${DBUS_NAME}" "${DBUS_PATH}" "${DBUS_IFAC_DUNST}.Ping" >/dev/null 2>/dev/null \
|| die "Dunst controlling interface not available. Is the version too old?"
;;
"")
die "dunstctl: No command specified. Please consult the usage."
;;
*)
die "dunstctl: unrecognized command '${1:-}'. Please consult the usage."
;;
esac

View File

@ -36,7 +36,7 @@ static GOptionEntry entries[] =
{ "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, "Close the notification with the specified ID", "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 }
};
@ -76,33 +76,6 @@ void print_serverinfo(void)
spec_version);
}
/*
* Glib leaves the option terminator "--" in the argv after parsing in some
* cases. This function gets the specified argv element ignoring the first
* terminator.
*
* See https://developer.gnome.org/glib/stable/glib-Commandline-option-parser.html#g-option-context-parse for details
*/
char *get_argv(char *argv[], int index)
{
for (int i = 0; i <= index; i++) {
if (strcmp(argv[i], "--") == 0) {
return argv[index + 1];
}
}
return argv[index];
}
/* Count the number of arguments in argv excluding the terminator "--" */
int count_args(char *argv[], int argc) {
for (int i = 0; i < argc; i++) {
if (strcmp(argv[i], "--") == 0)
return argc - 1;
}
return argc;
}
void parse_commandline(int argc, char *argv[])
{
GError *error = NULL;
@ -127,23 +100,17 @@ void parse_commandline(int argc, char *argv[])
die(0);
}
if (*appname == '\0') {
g_printerr("Provided appname was empty\n");
die(1);
}
int n_args = count_args(argv, argc);
if (n_args < 2 && close_id < 1) {
if (argc < 2 && close_id < 1) {
g_printerr("I need at least a summary\n");
die(1);
} else if (n_args < 2) {
} else if (argc < 2) {
summary = g_strdup("These are not the summaries you are looking for");
} else {
summary = g_strdup(get_argv(argv, 1));
summary = g_strdup(argv[1]);
}
if (n_args > 2) {
body = g_strcompress(get_argv(argv, 2));
if (argc > 2) {
body = g_strdup(argv[2]);
}
if (urgency_str) {
@ -239,7 +206,7 @@ void add_action(NotifyNotification *n, char *str)
char *label = strchr(str, ',');
if (!label || *(label+1) == '\0') {
g_printerr("Malformed action. Expected \"action,label\", got \"%s\"", str);
g_printerr("Malformed action. Excpected \"action,label\", got \"%s\"", str);
return;
}
@ -364,4 +331,4 @@ int main(int argc, char *argv[])
die(0);
}
/* vim: set ft=c tabstop=8 shiftwidth=8 expandtab textwidth=0: */
/* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */

185
dunstrc
View File

@ -31,23 +31,6 @@
# screen width minus the width defined in within the geometry option.
geometry = "300x5-30+20"
# Turn on the progess bar
progress_bar = true
# Set the progress bar height. This includes the frame, so make sure
# it's at least twice as big as the frame width.
progress_bar_height = 10
# Set the frame width of the progress bar
progress_bar_frame_width = 1
# Set the minimum width for the progress bar
progress_bar_min_width = 150
# Set the maximum width for the progress bar
progress_bar_max_width = 300
# Show how many messages are currently hidden (because of geometry).
indicate_hidden = yes
@ -76,9 +59,6 @@
# Horizontal padding.
horizontal_padding = 8
# Padding between text and icon.
text_icon_padding = 0
# Defines width in pixels of frame around the notification window.
# Set to 0 to disable.
frame_width = 3
@ -100,8 +80,7 @@
# 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.
# A client can set the 'transient' hint to bypass this. See the rules
# section for how to disable this if necessary
# Transient notifications ignore this setting.
idle_threshold = 120
### Text ###
@ -120,7 +99,7 @@
# <u>underline</u>
#
# For a complete reference see
# <https://developer.gnome.org/pango/stable/pango-Markup.html>.
# <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
@ -152,10 +131,6 @@
# Possible values are "left", "center" and "right".
alignment = left
# Vertical alignment of message text and icon.
# Possible values are "top", "center" and "bottom".
vertical_alignment = center
# Show age of message if message is older than show_age_threshold
# seconds.
# Set to -1 to disable.
@ -165,17 +140,17 @@
# geometry.
word_wrap = yes
# When word_wrap is set to no, specify where to make an ellipsis in long lines.
# 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
# Stack together notifications with the same content
# Merge multiple notifications with the same content
stack_duplicates = true
# Hide the count of stacked notifications with the same content
# Hide the count of merged notifications with the same content
hide_duplicate_count = false
# Display indicators for URLs (U) and actions (A).
@ -184,12 +159,7 @@
### Icons ###
# Align icons left/right/off
icon_position = left
# Scale small icons up to this size, set to 0 to disable. Helpful
# for e.g. small files or high-dpi screens. In case of conflict,
# max_icon_size takes precedence over this.
min_icon_size = 0
icon_position = off
# Scale larger icons down to this size, set to 0 to disable
max_icon_size = 32
@ -228,38 +198,6 @@
# automatically after a crash.
startup_notification = false
# Manage dunst's desire for talking
# Can be one of the following values:
# crit: Critical features. Dunst aborts
# warn: Only non-fatal warnings
# mesg: Important Messages
# info: all unimportant stuff
# debug: all less than unimportant stuff
verbosity = mesg
# Define the corner radius of the notification window
# in pixel size. If the radius is 0, you have no rounded
# corners.
# The radius will be automatically lowered if it exceeds half of the
# notification height to avoid clipping text and/or icons.
corner_radius = 0
# Ignore the dbus closeNotification message.
# Useful to enforce the timeout set by dunst configuration. Without this
# parameter, an application may close the notification sent before the
# user defined timeout.
ignore_dbusclose = false
### Wayland ###
# These settings are Wayland-specific. They have no effect when using X11
# Uncomment this if you want to let notications appear under fullscreen
# applications (default: overlay)
# layer = top
# Set this to true to use X11 output on Wayland.
force_xwayland = false
### Legacy
# Use the Xinerama extension instead of RandR for multi-monitor support.
@ -272,25 +210,6 @@
# layout changes.
force_xinerama = false
### mouse
# Defines list of actions for each mouse event
# Possible values are:
# * none: Don't do anything.
# * do_action: Invoke the action determined by the action_name rule. If there is no
# such action, open the context menu.
# * open_url: If the notification has exactly one url, open it. If there are multiple
# ones, open the context menu.
# * close_current: Close current notification.
# * close_all: Close all notifications.
# * context: Open context menu for the notification.
# * context_all: Open context menu for all notifications.
# These values can be strung together for each mouse event, and
# will be executed in sequence.
mouse_left_click = close_current
mouse_middle_click = do_action, close_current
mouse_right_click = close_all
# Experimental features that may or may not work correctly. Do not expect them
# to have a consistent behaviour across releases.
[experimental]
@ -301,10 +220,6 @@
# where there are multiple screens with very different dpi values.
per_monitor_dpi = false
# The internal keyboard shortcut support in dunst is now considered deprecated
# and should be replaced by dunstctl calls. You can use the configuration of your
# WM or DE to bind these to shortcuts of your choice.
# Check the dunstctl manual page for more info.
[shortcuts]
# Shortcuts are specified as [modifier+][modifier+]...key
@ -312,21 +227,20 @@
# "mod3" and "mod4" (windows-key).
# Xev might be helpful to find names for keys.
# Close notification. Equivalent dunstctl command:
# dunstctl close
# close = ctrl+space
# Close notification.
close = ctrl+space
# Close all notifications. Equivalent dunstctl command:
# dunstctl close-all
# close_all = ctrl+shift+space
# Close all notifications.
close_all = ctrl+shift+space
# Redisplay last message(s). Equivalent dunstctl command:
# dunstctl history-pop
# history = ctrl+grave
# 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. Equivalent dunstctl command:
# dunstctl context
# context = ctrl+shift+period
# Context menu.
context = ctrl+shift+period
[urgency_low]
# IMPORTANT: colors have to be defined in quotation marks.
@ -354,37 +268,11 @@
# 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 (discouraged, see desktop_entry)
# body
# category
# desktop_entry
# icon
# match_transient
# msg_urgency
# stack_tag
# summary
#
# and you can override the
# background
# foreground
# format
# frame_color
# fullscreen
# new_icon
# set_stack_tag
# set_transient
# timeout
# urgency
# action_name
#
# 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.
#
# Instead of the appname filter, it's recommended to use the desktop_entry filter.
# GLib based applications export their desktop-entry name. In comparison to the appname,
# the desktop-entry won't get localized.
#
# SCRIPTING
# You can specify a script that gets run when the rule matches by
# setting the "script" option.
@ -397,30 +285,6 @@
# NOTE: It might be helpful to run dunst -print in a terminal in order
# to find fitting options for rules.
# Disable the transient hint so that idle_threshold cannot be bypassed from the
# client
#[transient_disable]
# match_transient = yes
# set_transient = no
#
# Make the handling of transient notifications more strict by making them not
# be placed in history.
#[transient_history_ignore]
# match_transient = yes
# history_ignore = yes
# fullscreen values
# show: show the notifications, regardless if there is a fullscreen window opened
# delay: displays the new notification, if there is no fullscreen window active
# If the notification is already drawn, it won't get undrawn.
# pushback: same as delay, but when switching into fullscreen, the notification will get
# withdrawn from screen again and will get delayed like a new notification
#[fullscreen_delay_everything]
# fullscreen = delay
#[fullscreen_show_critical]
# msg_urgency = critical
# fullscreen = show
#[espeak]
# summary = "*"
# script = dunst_espeak.sh
@ -439,11 +303,6 @@
# summary = "foobar"
# history_ignore = yes
#[skip-display]
# # This notification will not be displayed, but will be included in the history
# summary = "foobar"
# skip_display = yes
#[signed_on]
# appname = Pidgin
# summary = "*signed on*"
@ -464,8 +323,4 @@
# summary = *twitter.com*
# urgency = normal
#
#[stack-volumes]
# appname = "some_volume_notifiers"
# set_stack_tag = "volume"
#
# vim: ft=cfg

2
main.c
View File

@ -4,4 +4,4 @@ int main(int argc, char *argv[])
{
return dunst_main(argc, argv);
}
/* vim: set ft=c tabstop=8 shiftwidth=8 expandtab textwidth=0: */
/* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */

File diff suppressed because it is too large Load Diff

View File

@ -3,23 +3,22 @@
#ifndef DUNST_DBUS_H
#define DUNST_DBUS_H
#include "dunst.h"
#include "notification.h"
/// The reasons according to the notification spec
enum reason {
REASON_MIN = 1, /**< Minimum value, useful for boundary checking */
REASON_TIME = 1, /**< The notification timed out */
REASON_USER = 2, /**< The user closed the notification */
REASON_SIG = 3, /**< The daemon received a `NotificationClose` signal */
REASON_UNDEF = 4, /**< Undefined reason not matching the previous ones */
REASON_MAX = 4, /**< Maximum value, useful for boundary checking */
REASON_MIN = 1,
REASON_TIME = 1,
REASON_USER = 2,
REASON_SIG = 3,
REASON_UNDEF = 4,
REASON_MAX = 4,
};
int dbus_init(void);
void dbus_teardown(int id);
void signal_notification_closed(struct notification *n, enum reason reason);
void signal_action_invoked(const struct notification *n, const char *identifier);
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 ft=c tabstop=8 shiftwidth=8 expandtab textwidth=0: */
/* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */

View File

@ -1,843 +0,0 @@
#include "draw.h"
#include <assert.h>
#include <math.h>
#include <pango/pango-attributes.h>
#include <pango/pangocairo.h>
#include <pango/pango-font.h>
#include <pango/pango-layout.h>
#include <pango/pango-types.h>
#include <stdlib.h>
#include <inttypes.h>
#include <glib.h>
#include "dunst.h"
#include "icon.h"
#include "log.h"
#include "markup.h"
#include "notification.h"
#include "queues.h"
#include "output.h"
#include "settings.h"
struct color {
double r;
double g;
double b;
double a;
};
struct colored_layout {
PangoLayout *l;
struct color fg;
struct color bg;
struct color highlight;
struct color frame;
char *text;
PangoAttrList *attr;
cairo_surface_t *icon;
const struct notification *n;
};
const struct output *output;
window win;
PangoFontDescription *pango_fdesc;
#define UINT_MAX_N(bits) ((1 << bits) - 1)
void draw_setup(void)
{
const struct output *out = output_create(settings.force_xwayland);
output = out;
win = out->win_create();
pango_fdesc = pango_font_description_from_string(settings.font);
}
static struct color hex_to_color(uint32_t hexValue, int dpc)
{
const int bpc = 4 * dpc;
const unsigned single_max = UINT_MAX_N(bpc);
struct color ret;
ret.r = ((hexValue >> 3 * bpc) & single_max) / (double)single_max;
ret.g = ((hexValue >> 2 * bpc) & single_max) / (double)single_max;
ret.b = ((hexValue >> 1 * bpc) & single_max) / (double)single_max;
ret.a = ((hexValue) & single_max) / (double)single_max;
return ret;
}
static struct color string_to_color(const char *str)
{
char *end;
uint_fast32_t val = strtoul(str+1, &end, 16);
if (end[0] != '\0' && end[1] != '\0') {
LOG_W("Invalid color string: '%s'", str);
}
switch (end - (str+1)) {
case 3: return hex_to_color((val << 4) | 0xF, 1);
case 6: return hex_to_color((val << 8) | 0xFF, 2);
case 4: return hex_to_color(val, 1);
case 8: return hex_to_color(val, 2);
}
/* return black on error */
LOG_W("Invalid color string: '%s'", str);
return hex_to_color(0xF, 1);
}
static inline double color_apply_delta(double base, double delta)
{
base += delta;
if (base > 1)
base = 1;
if (base < 0)
base = 0;
return base;
}
static struct color calculate_foreground_color(struct color bg)
{
double c_delta = 0.1;
struct color fg = bg;
/* do we need to darken or brighten the colors? */
bool darken = (bg.r + bg.g + bg.b) / 3 > 0.5;
int signedness = darken ? -1 : 1;
fg.r = color_apply_delta(fg.r, c_delta * signedness);
fg.g = color_apply_delta(fg.g, c_delta * signedness);
fg.b = color_apply_delta(fg.b, c_delta * signedness);
return fg;
}
static struct color layout_get_sepcolor(struct colored_layout *cl,
struct colored_layout *cl_next)
{
switch (settings.sep_color.type) {
case SEP_FRAME:
if (cl_next->n->urgency > cl->n->urgency)
return cl_next->frame;
else
return cl->frame;
case SEP_CUSTOM:
return string_to_color(settings.sep_color.sep_color);
case SEP_FOREGROUND:
return cl->fg;
case SEP_AUTO:
return calculate_foreground_color(cl->bg);
default:
LOG_E("Invalid %s enum value in %s:%d", "sep_color", __FILE__, __LINE__);
break;
}
}
static void layout_setup_pango(PangoLayout *layout, int width)
{
int scale = output->get_scale();
pango_layout_set_wrap(layout, PANGO_WRAP_WORD_CHAR);
pango_layout_set_width(layout, width * scale * PANGO_SCALE);
pango_layout_set_font_description(layout, pango_fdesc);
pango_layout_set_spacing(layout, settings.line_height * scale * PANGO_SCALE);
PangoAlignment align;
switch (settings.align) {
case ALIGN_LEFT:
default:
align = PANGO_ALIGN_LEFT;
break;
case ALIGN_CENTER:
align = PANGO_ALIGN_CENTER;
break;
case ALIGN_RIGHT:
align = PANGO_ALIGN_RIGHT;
break;
}
pango_layout_set_alignment(layout, align);
}
static void free_colored_layout(void *data)
{
struct colored_layout *cl = data;
g_object_unref(cl->l);
pango_attr_list_unref(cl->attr);
g_free(cl->text);
if (cl->icon) cairo_surface_destroy(cl->icon);
g_free(cl);
}
static bool have_dynamic_width(void)
{
return (settings.geometry.width_set && settings.geometry.w == 0);
}
static int get_text_icon_padding()
{
if (settings.text_icon_padding) {
return settings.text_icon_padding;
} else {
return settings.h_padding;
}
}
static bool have_progress_bar(const struct notification *n)
{
return (n->progress >= 0 && settings.progress_bar == true);
}
static void get_text_size(PangoLayout *l, int *w, int *h, int scale) {
pango_layout_get_pixel_size(l, w, h);
// scale the size down, because it may be rendered at higher DPI
if (w)
*w /= scale;
if (h)
*h /= scale;
}
static struct dimensions calculate_dimensions(GSList *layouts)
{
struct dimensions dim = { 0 };
int scale = output->get_scale();
const struct screen_info *scr = output->get_active_screen();
if (have_dynamic_width()) {
/* dynamic width */
dim.w = 0;
} else if (settings.geometry.width_set) {
/* fixed width */
if (settings.geometry.negative_width) {
dim.w = scr->w - settings.geometry.w;
} else {
dim.w = settings.geometry.w;
}
} else {
/* across the screen */
dim.w = scr->w;
}
dim.h += 2 * settings.frame_width;
dim.h += (g_slist_length(layouts) - 1) * settings.separator_height;
dim.corner_radius = settings.corner_radius;
int text_width = 0, total_width = 0;
for (GSList *iter = layouts; iter; iter = iter->next) {
struct colored_layout *cl = iter->data;
int w=0,h=0;
get_text_size(cl->l, &w, &h, scale);
if (cl->icon) {
h = MAX(get_icon_height(cl->icon, scale), h);
w += get_icon_width(cl->icon, scale) + settings.h_padding;
}
h = MAX(settings.notification_height, h + settings.padding * 2);
dim.h += h;
text_width = MAX(w, text_width);
if (have_dynamic_width() || settings.shrink) {
/* dynamic width */
total_width = MAX(text_width + 2 * settings.h_padding, total_width);
/* subtract height from the unwrapped text */
dim.h -= h;
if (total_width > scr->w) {
/* set width to screen width */
dim.w = scr->w - settings.geometry.x * 2;
} else if (have_dynamic_width() || (total_width < settings.geometry.w && settings.shrink)) {
/* set width to text width */
dim.w = total_width + 2 * settings.frame_width;
}
/* re-setup the layout */
w = dim.w;
w -= 2 * settings.h_padding;
w -= 2 * settings.frame_width;
if (cl->icon) {
w -= get_icon_width(cl->icon, scale) + get_text_icon_padding();
}
layout_setup_pango(cl->l, w);
/* re-read information */
get_text_size(cl->l, &w, &h, scale);
if (cl->icon) {
h = MAX(get_icon_height(cl->icon, scale), h);
w += get_icon_width(cl->icon, scale) + settings.h_padding;
}
h = MAX(settings.notification_height, h + settings.padding * 2);
dim.h += h;
text_width = MAX(w, text_width);
}
if (have_progress_bar(cl->n)){
dim.h += settings.progress_bar_height + settings.padding;
dim.w = MAX(dim.w, settings.progress_bar_min_width);
}
dim.corner_radius = MIN(dim.corner_radius, h/2);
}
if (dim.w <= 0) {
dim.w = text_width + 2 * settings.h_padding;
dim.w += 2 * settings.frame_width;
}
return dim;
}
static PangoLayout *layout_create(cairo_t *c)
{
const struct screen_info *screen = output->get_active_screen();
PangoContext *context = pango_cairo_create_context(c);
pango_cairo_context_set_resolution(context, screen->dpi);
PangoLayout *layout = pango_layout_new(context);
g_object_unref(context);
return layout;
}
static struct colored_layout *layout_init_shared(cairo_t *c, const struct notification *n)
{
struct colored_layout *cl = g_malloc(sizeof(struct colored_layout));
cl->l = layout_create(c);
int scale = output->get_scale();
if (!settings.word_wrap) {
PangoEllipsizeMode ellipsize;
switch (settings.ellipsize) {
case ELLIPSE_START:
ellipsize = PANGO_ELLIPSIZE_START;
break;
case ELLIPSE_MIDDLE:
ellipsize = PANGO_ELLIPSIZE_MIDDLE;
break;
case ELLIPSE_END:
ellipsize = PANGO_ELLIPSIZE_END;
break;
default:
LOG_E("Invalid %s enum value in %s:%d", "ellipsize", __FILE__, __LINE__);
break;
}
pango_layout_set_ellipsize(cl->l, ellipsize);
}
if (settings.icon_position != ICON_OFF && n->icon) {
cl->icon = gdk_pixbuf_to_cairo_surface(n->icon);
} else {
cl->icon = NULL;
}
if (cl->icon && cairo_surface_status(cl->icon) != CAIRO_STATUS_SUCCESS) {
cairo_surface_destroy(cl->icon);
cl->icon = NULL;
}
cl->fg = string_to_color(n->colors.fg);
cl->bg = string_to_color(n->colors.bg);
cl->highlight = string_to_color(n->colors.highlight);
cl->frame = string_to_color(n->colors.frame);
cl->n = n;
struct dimensions dim = calculate_dimensions(NULL);
int width = dim.w;
if (have_dynamic_width()) {
layout_setup_pango(cl->l, -1);
} else {
width -= 2 * settings.h_padding;
width -= 2 * settings.frame_width;
if (cl->icon) {
width -= get_icon_width(cl->icon, scale) + get_text_icon_padding();
}
layout_setup_pango(cl->l, width);
}
return cl;
}
static struct colored_layout *layout_derive_xmore(cairo_t *c, const struct notification *n, int qlen)
{
struct colored_layout *cl = layout_init_shared(c, n);
cl->text = g_strdup_printf("(%d more)", qlen);
cl->attr = NULL;
pango_layout_set_text(cl->l, cl->text, -1);
return cl;
}
static struct colored_layout *layout_from_notification(cairo_t *c, struct notification *n)
{
struct colored_layout *cl = layout_init_shared(c, n);
int scale = output->get_scale();
/* markup */
GError *err = NULL;
pango_parse_markup(n->text_to_render, -1, 0, &(cl->attr), &(cl->text), NULL, &err);
if (!err) {
pango_layout_set_text(cl->l, cl->text, -1);
pango_layout_set_attributes(cl->l, cl->attr);
} else {
/* remove markup and display plain message instead */
n->text_to_render = markup_strip(n->text_to_render);
cl->text = NULL;
cl->attr = NULL;
pango_layout_set_text(cl->l, n->text_to_render, -1);
if (n->first_render) {
LOG_W("Unable to parse markup: %s", err->message);
}
g_error_free(err);
}
get_text_size(cl->l, NULL, &(n->displayed_height), scale);
if (cl->icon) n->displayed_height = MAX(get_icon_height(cl->icon, scale), n->displayed_height);
n->displayed_height = n->displayed_height + settings.padding * 2;
// progress bar
if (have_progress_bar(n)) n->displayed_height += settings.progress_bar_height + settings.padding;
n->displayed_height = MAX(settings.notification_height, n->displayed_height);
n->first_render = false;
return cl;
}
static GSList *create_layouts(cairo_t *c)
{
GSList *layouts = NULL;
int qlen = queues_length_waiting();
bool xmore_is_needed = qlen > 0 && settings.indicate_hidden;
for (const GList *iter = queues_get_displayed();
iter; iter = iter->next)
{
struct notification *n = iter->data;
notification_update_text_to_render(n);
if (!iter->next && xmore_is_needed && settings.geometry.h == 1) {
char *new_ttr = g_strdup_printf("%s (%d more)", n->text_to_render, qlen);
g_free(n->text_to_render);
n->text_to_render = new_ttr;
}
layouts = g_slist_append(layouts,
layout_from_notification(c, n));
}
if (xmore_is_needed && settings.geometry.h != 1) {
/* append xmore message as new message */
layouts = g_slist_append(layouts,
layout_derive_xmore(c, queues_get_head_waiting(), qlen));
}
return layouts;
}
static int layout_get_height(struct colored_layout *cl, int scale)
{
int h;
int h_icon = 0;
int h_progress_bar = 0;
get_text_size(cl->l, NULL, &h, scale);
if (cl->icon)
h_icon = get_icon_height(cl->icon, scale);
if (have_progress_bar(cl->n)){
h_progress_bar = settings.progress_bar_height + settings.padding;
}
int res = MAX(h, h_icon) + h_progress_bar;
return res;
}
/* Attempt to make internal radius more organic.
* Simple r-w is not enough for too small r/w ratio.
* simplifications: r/2 == r - w + w*w / (r * 2) with (w == r)
* r, w - corner radius & frame width,
* h - box height
*/
static int frame_internal_radius (int r, int w, int h)
{
if (r == 0 || h + (w - r) * 2 == 0)
return 0;
// Integer precision scaler, using 1/4 of int size
const int s = 2 << (8 * sizeof(int) / 4);
int r1, r2, ret;
h *= s;
r *= s;
w *= s;
r1 = r - w + w * w / (r * 2); // w < r
r2 = r * h / (h + (w - r) * 2); // w >= r
ret = (r > w) ? r1 : (r / 2 < r2) ? r / 2 : r2;
return ret / s;
}
/**
* Create a path on the given cairo context to draw the background of a notification.
* The top corners will get rounded by `corner_radius`, if `first` is set.
* Respectably the same for `last` with the bottom corners.
*/
void draw_rounded_rect(cairo_t *c, int x, int y, int width, int height, int corner_radius, int scale, bool first, bool last)
{
width *= scale;
height *= scale;
x *= scale;
y *= scale;
corner_radius *= scale;
const float degrees = M_PI / 180.0;
cairo_new_sub_path(c);
if (last) {
// bottom right
cairo_arc(c,
x + width - corner_radius,
y + height - corner_radius,
corner_radius,
degrees * 0,
degrees * 90);
// bottom left
cairo_arc(c,
x + corner_radius,
y + height - corner_radius,
corner_radius,
degrees * 90,
degrees * 180);
} else {
cairo_line_to(c, x + width, y + height);
cairo_line_to(c, x, y + height);
}
if (first) {
// top left
cairo_arc(c,
x + corner_radius,
y + corner_radius,
corner_radius,
degrees * 180,
degrees * 270);
// top right
cairo_arc(c,
x + width - corner_radius,
y + corner_radius,
corner_radius,
degrees * 270,
degrees * 360);
} else {
cairo_line_to(c, x, y);
cairo_line_to(c, x + width, y);
}
cairo_close_path(c);
}
/**
* A small wrapper around cairo_rectange for drawing a scaled rectangle.
*/
void draw_rect(cairo_t *c, int x, int y, int width, int height, int scale) {
cairo_rectangle(c, x * scale, y * scale, width * scale, height * scale);
}
static cairo_surface_t *render_background(cairo_surface_t *srf,
struct colored_layout *cl,
struct colored_layout *cl_next,
int y,
int width,
int height,
int corner_radius,
bool first,
bool last,
int *ret_width,
int scale)
{
int x = 0;
int radius_int = corner_radius;
cairo_t *c = cairo_create(srf);
/* stroke area doesn't intersect with main area */
cairo_set_fill_rule(c, CAIRO_FILL_RULE_EVEN_ODD);
/* for correct combination of adjacent areas */
cairo_set_operator(c, CAIRO_OPERATOR_ADD);
if (first)
height += settings.frame_width;
if (last)
height += settings.frame_width;
else
height += settings.separator_height;
draw_rounded_rect(c, x, y, width, height, corner_radius, scale, first, last);
/* adding frame */
x += settings.frame_width;
if (first) {
y += settings.frame_width;
height -= settings.frame_width;
}
width -= 2 * settings.frame_width;
if (last)
height -= settings.frame_width;
else
height -= settings.separator_height;
radius_int = frame_internal_radius(corner_radius, settings.frame_width, height);
draw_rounded_rect(c, x, y, width, height, radius_int, scale, first, last);
cairo_set_source_rgba(c, cl->frame.r, cl->frame.g, cl->frame.b, cl->frame.a);
cairo_fill(c);
draw_rounded_rect(c, x, y, width, height, radius_int, scale, first, last);
cairo_set_source_rgba(c, cl->bg.r, cl->bg.g, cl->bg.b, cl->bg.a);
cairo_fill(c);
cairo_set_operator(c, CAIRO_OPERATOR_SOURCE);
if ( settings.sep_color.type != SEP_FRAME
&& settings.separator_height > 0
&& !last) {
struct color sep_color = layout_get_sepcolor(cl, cl_next);
cairo_set_source_rgba(c, sep_color.r, sep_color.g, sep_color.b, sep_color.a);
draw_rect(c, settings.frame_width, y + height, width, settings.separator_height, scale);
cairo_fill(c);
}
cairo_destroy(c);
if (ret_width)
*ret_width = width;
return cairo_surface_create_for_rectangle(srf, x * scale, y * scale, width * scale, height * scale);
}
static void render_content(cairo_t *c, struct colored_layout *cl, int width, int scale)
{
const int h = layout_get_height(cl, scale);
int h_without_progress_bar = h;
if (have_progress_bar(cl->n)){
h_without_progress_bar -= settings.progress_bar_height + settings.padding;
}
int h_text;
get_text_size(cl->l, NULL, &h_text, scale);
int text_x = settings.h_padding,
text_y = settings.padding + h_without_progress_bar / 2 - h_text / 2;
// text positioning
if (cl->icon) {
// vertical alignment
if (settings.vertical_alignment == VERTICAL_TOP) {
text_y = settings.padding;
} else if (settings.vertical_alignment == VERTICAL_BOTTOM) {
text_y = h_without_progress_bar + settings.padding - h_text;
if (text_y < 0)
text_y = settings.padding;
} // else VERTICAL_CENTER
// icon position
if (settings.icon_position == ICON_LEFT) {
text_x = get_icon_width(cl->icon, scale) + settings.h_padding + get_text_icon_padding();
} // else ICON_RIGHT
}
cairo_move_to(c, text_x * scale, text_y * scale);
cairo_set_source_rgba(c, cl->fg.r, cl->fg.g, cl->fg.b, cl->fg.a);
pango_cairo_update_layout(c, cl->l);
pango_cairo_show_layout(c, cl->l);
// icon positioning
if (cl->icon) {
unsigned int image_width = get_icon_width(cl->icon, scale),
image_height = get_icon_height(cl->icon, scale),
image_x = width - settings.h_padding - image_width,
image_y = settings.padding + h_without_progress_bar/2 - image_height/2;
// vertical alignment
if (settings.vertical_alignment == VERTICAL_TOP) {
image_y = settings.padding;
} else if (settings.vertical_alignment == VERTICAL_BOTTOM) {
image_y = h_without_progress_bar + settings.padding - image_height;
if (image_y < settings.padding || image_y > h_without_progress_bar)
image_y = settings.padding;
} // else VERTICAL_CENTER
// icon position
if (settings.icon_position == ICON_LEFT) {
image_x = settings.h_padding;
} // else ICON_RIGHT
cairo_set_source_surface(c, cl->icon, image_x * scale, image_y * scale);
draw_rect(c, image_x, image_y, image_width, image_height, scale);
cairo_fill(c);
}
// progress bar positioning
if (have_progress_bar(cl->n)){
int progress = MIN(cl->n->progress, 100);
unsigned int frame_width = settings.progress_bar_frame_width,
progress_width = MIN(width - 2 * settings.h_padding, settings.progress_bar_max_width),
progress_height = settings.progress_bar_height - frame_width,
frame_x = settings.h_padding,
frame_y = settings.padding + h - settings.progress_bar_height,
progress_width_without_frame = progress_width - 2 * frame_width,
progress_width_1 = progress_width_without_frame * progress / 100,
progress_width_2 = progress_width_without_frame - progress_width_1,
x_bar_1 = frame_x + frame_width,
x_bar_2 = x_bar_1 + progress_width_1;
double half_frame_width = frame_width / 2.0;
// draw progress bar
// Note: the bar could be drawn a bit smaller, because the frame is drawn on top
// left side
cairo_set_source_rgba(c, cl->highlight.r, cl->highlight.g, cl->highlight.b, cl->highlight.a);
draw_rect(c, x_bar_1, frame_y, progress_width_1, progress_height, scale);
cairo_fill(c);
// right side
cairo_set_source_rgba(c, cl->bg.r, cl->bg.g, cl->bg.b, cl->bg.a);
draw_rect(c, x_bar_2, frame_y, progress_width_2, progress_height, scale);
cairo_fill(c);
// border
cairo_set_source_rgba(c, cl->frame.r, cl->frame.g, cl->frame.b, cl->frame.a);
// TODO draw_rect instead of cairo_rectangle resulted in blurry lines. Why?
cairo_rectangle(c, (frame_x + half_frame_width) * scale, (frame_y + half_frame_width) * scale, (progress_width - frame_width) * scale, progress_height * scale);
cairo_set_line_width(c, frame_width * scale);
cairo_stroke(c);
}
}
static struct dimensions layout_render(cairo_surface_t *srf,
struct colored_layout *cl,
struct colored_layout *cl_next,
struct dimensions dim,
bool first,
bool last)
{
int scale = output->get_scale();
const int cl_h = layout_get_height(cl, scale);
int h_text = 0;
get_text_size(cl->l, NULL, &h_text, scale);
int bg_width = 0;
int bg_height = MAX(settings.notification_height, (2 * settings.padding) + cl_h);
cairo_surface_t *content = render_background(srf, cl, cl_next, dim.y, dim.w, bg_height, dim.corner_radius, first, last, &bg_width, scale);
cairo_t *c = cairo_create(content);
render_content(c, cl, bg_width, scale);
/* adding frame */
if (first)
dim.y += settings.frame_width;
if (!last)
dim.y += settings.separator_height;
if (settings.notification_height <= (2 * settings.padding) + cl_h)
dim.y += cl_h + 2 * settings.padding;
else
dim.y += settings.notification_height;
cairo_destroy(c);
cairo_surface_destroy(content);
return dim;
}
/**
* Calculates the position the window should be placed at given its width and
* height and stores them in \p ret_x and \p ret_y.
*/
static void calc_window_pos(int width, int height, int *ret_x, int *ret_y)
{
const struct screen_info *scr = output->get_active_screen();
if (ret_x) {
if (settings.geometry.negative_x) {
*ret_x = (scr->x + (scr->w - width)) + settings.geometry.x;
} else {
*ret_x = scr->x + settings.geometry.x;
}
}
if (ret_y) {
if (settings.geometry.negative_y) {
*ret_y = scr->y + (scr->h + settings.geometry.y) - height;
} else {
*ret_y = scr->y + settings.geometry.y;
}
}
}
void draw(void)
{
assert(queues_length_displayed() > 0);
GSList *layouts = create_layouts(output->win_get_context(win));
struct dimensions dim = calculate_dimensions(layouts);
int scale = output->get_scale();
cairo_surface_t *image_surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, dim.w * scale, dim.h * scale);
bool first = true;
for (GSList *iter = layouts; iter; iter = iter->next) {
struct colored_layout *cl_this = iter->data;
struct colored_layout *cl_next = iter->next ? iter->next->data : NULL;
dim = layout_render(image_surface, cl_this, cl_next, dim, first, !cl_next);
first = false;
}
calc_window_pos(dim.w, dim.h, &dim.x, &dim.y);
output->display_surface(image_surface, win, &dim);
cairo_surface_destroy(image_surface);
g_slist_free_full(layouts, free_colored_layout);
}
void draw_deinit(void)
{
output->win_destroy(win);
output->deinit();
}
int draw_get_scale(void)
{
if (output) {
return output->get_scale();
} else {
LOG_W("Called draw_get_scale before output init");
return 1;
}
}
/* vim: set ft=c tabstop=8 shiftwidth=8 expandtab textwidth=0: */

View File

@ -1,24 +0,0 @@
#ifndef DUNST_DRAW_H
#define DUNST_DRAW_H
#include <stdbool.h>
#include <cairo.h>
#include "output.h"
extern window win; // Temporary
extern const struct output *output;
void draw_setup(void);
void draw(void);
void draw_rounded_rect(cairo_t *c, int x, int y, int width, int height, int corner_radius, int scale, bool first, bool last);
// TODO get rid of this function by passing scale to everything that needs it.
int draw_get_scale(void);
void draw_deinit(void);
#endif
/* vim: set ft=c tabstop=8 shiftwidth=8 expandtab textwidth=0: */

View File

@ -1,121 +1,98 @@
/* copyright 2012 - 2013 Sascha Kruse and contributors (see LICENSE for licensing information) */
#define XLIB_ILLEGAL_ACCESS
#include "dunst.h"
#include <assert.h>
#include <glib.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 "draw.h"
#include "log.h"
#include "menu.h"
#include "notification.h"
#include "option_parser.h"
#include "queues.h"
#include "settings.h"
#include "utils.h"
#include "output.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;
static struct dunst_status status;
static bool setup_done = false;
GSList *rules = NULL;
/* see dunst.h */
void dunst_status(const enum dunst_status_field field,
bool value)
{
switch (field) {
case S_FULLSCREEN:
status.fullscreen = value;
break;
case S_IDLE:
status.idle = value;
break;
case S_RUNNING:
status.running = value;
break;
default:
LOG_E("Invalid %s enum value in %s:%d", "dunst_status", __FILE__, __LINE__);
break;
}
}
/* see dunst.h */
struct dunst_status dunst_status_get(void)
{
return status;
}
/* misc functions */
static gboolean run(void *data);
/* misc funtions */
void wake_up(void)
{
// If wake_up is being called before the output has been setup we should
// return.
if (!setup_done)
{
LOG_D("Ignoring wake up");
return;
}
LOG_D("Waking up");
run(NULL);
}
static gboolean run(void *data)
gboolean run(void *data)
{
queues_check_timeouts(x_is_idle());
queues_update();
static int timeout_cnt = 0;
static gint64 next_timeout = 0;
LOG_D("RUN");
dunst_status(S_FULLSCREEN, output->have_fullscreen_window());
dunst_status(S_IDLE, output->is_idle());
queues_update(status);
bool active = queues_length_displayed() > 0;
if (active) {
// Call draw before showing the window to avoid flickering
draw();
output->win_show(win);
} else {
output->win_hide(win);
if (data && timeout_cnt > 0) {
timeout_cnt--;
}
if (active) {
gint64 now = time_monotonic_now();
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;
LOG_D("Sleeping for %li ms", sleep/1000);
if (sleep >= 0) {
if (next_timeout < now || timeout_at < next_timeout) {
g_timeout_add(sleep/1000, run, NULL);
if (timeout_cnt == 0 || timeout_at < next_timeout) {
g_timeout_add(sleep/1000, run, mainloop);
next_timeout = timeout_at;
timeout_cnt++;
}
}
}
/* If the execution got triggered by g_timeout_add,
* we have to remove the timeout (which is actually a
* recurring interval), as we have set a new one
* by ourselves.
*/
return G_SOURCE_REMOVE;
/* always return false to delete timers */
return false;
}
gboolean pause_signal(gpointer data)
{
dunst_status(S_RUNNING, false);
queues_pause_on();
wake_up();
return G_SOURCE_CONTINUE;
@ -123,7 +100,7 @@ gboolean pause_signal(gpointer data)
gboolean unpause_signal(gpointer data)
{
dunst_status(S_RUNNING, true);
queues_pause_off();
wake_up();
return G_SOURCE_CONTINUE;
@ -140,32 +117,23 @@ static void teardown(void)
{
regex_teardown();
queues_teardown();
teardown_queues();
draw_deinit();
x_free();
}
int dunst_main(int argc, char *argv[])
{
dunst_status(S_RUNNING, true);
dunst_status(S_IDLE, false);
queues_init();
cmdline_load(argc, argv);
dunst_log_init(false);
if (cmdline_get_bool("-v/-version", false, "Print version")
|| cmdline_get_bool("--version", false, "Print version")) {
print_version();
}
char *verbosity = cmdline_get_string("-verbosity", NULL, "Minimum level for message");
log_set_level_from_string(verbosity);
g_free(verbosity);
char *cmdline_config_path;
cmdline_config_path =
cmdline_get_string("-conf/-config", NULL,
@ -177,11 +145,46 @@ int dunst_main(int argc, char *argv[])
usage(EXIT_SUCCESS);
}
int dbus_owner_id = dbus_init();
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);
draw_setup();
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);
@ -191,25 +194,9 @@ int dunst_main(int argc, char *argv[])
guint term_src = g_unix_signal_add(SIGTERM, quit_signal, NULL);
guint int_src = g_unix_signal_add(SIGINT, quit_signal, NULL);
if (settings.startup_notification) {
struct notification *n = notification_create();
n->id = 0;
n->appname = g_strdup("dunst");
n->summary = g_strdup("startup");
n->body = g_strdup("dunst is up and running");
n->progress = -1;
n->timeout = S2US(10);
n->markup = MARKUP_NO;
n->urgency = URG_LOW;
notification_init(n);
queues_notification_insert(n);
// we do not call wakeup now, wake_up does not work here yet
}
setup_done = true;
run(NULL);
g_main_loop_run(mainloop);
g_clear_pointer(&mainloop, g_main_loop_unref);
g_main_loop_unref(mainloop);
/* remove signal handler watches */
g_source_remove(pause_src);
@ -217,7 +204,9 @@ int dunst_main(int argc, char *argv[])
g_source_remove(term_src);
g_source_remove(int_src);
dbus_teardown(dbus_owner_id);
g_source_destroy(x11_source);
dbus_tear_down(owner_id);
teardown();
@ -240,4 +229,4 @@ void print_version(void)
exit(EXIT_SUCCESS);
}
/* vim: set ft=c tabstop=8 shiftwidth=8 expandtab textwidth=0: */
/* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */

View File

@ -6,39 +6,32 @@
#include <glib.h>
#include <stdbool.h>
#include <stdio.h>
#include <stddef.h>
#include "notification.h"
//!< A structure to describe dunst's global window status
struct dunst_status {
bool fullscreen; //!< a fullscreen window is currently focused
bool running; //!< set true if dunst is currently running
bool idle; //!< set true if the user is idle
};
#define PERR(msg, errnum) printf("(%d) %s : %s\n", __LINE__, (msg), (strerror(errnum)))
enum dunst_status_field {
S_FULLSCREEN,
S_IDLE,
S_RUNNING,
};
#define ColLast 3
#define ColFrame 2
#define ColFG 1
#define ColBG 0
/**
* Modify the current status of dunst
* @param field The field to change in the global status structure
* @param value Anything boolean or DO_TOGGLE to toggle the current value
*/
void dunst_status(const enum dunst_status_field field,
bool value);
struct dunst_status dunst_status_get(void);
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 ft=c tabstop=8 shiftwidth=8 expandtab textwidth=0: */
/* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */

View File

@ -1,410 +0,0 @@
#include "icon.h"
#include <assert.h>
#include <cairo.h>
#include <gdk-pixbuf/gdk-pixbuf.h>
#include <stdbool.h>
#include <string.h>
#include "log.h"
#include "notification.h"
#include "settings.h"
#include "utils.h"
static bool is_readable_file(const char *filename)
{
return (access(filename, R_OK) != -1);
}
/**
* Reassemble the data parts of a GdkPixbuf into a cairo_surface_t's data field.
*
* Requires to call on the surface flush before and mark_dirty after the execution.
*/
static void pixbuf_data_to_cairo_data(
const unsigned char *pixels_p,
unsigned char *pixels_c,
size_t rowstride_p,
size_t rowstride_c,
int width,
int height,
int n_channels)
{
#if G_BYTE_ORDER == G_LITTLE_ENDIAN
static const size_t CAIRO_B = 0;
static const size_t CAIRO_G = 1;
static const size_t CAIRO_R = 2;
static const size_t CAIRO_A = 3;
#elif G_BYTE_ORDER == G_BIG_ENDIAN
static const size_t CAIRO_A = 0;
static const size_t CAIRO_R = 1;
static const size_t CAIRO_G = 2;
static const size_t CAIRO_B = 3;
#elif G_BYTE_ORDER == G_PDP_ENDIAN
static const size_t CAIRO_R = 0;
static const size_t CAIRO_A = 1;
static const size_t CAIRO_B = 2;
static const size_t CAIRO_G = 3;
#else
// GLib doesn't support any other endiannesses
#error Unsupported Endianness
#endif
assert(pixels_p);
assert(pixels_c);
assert(width > 0);
assert(height > 0);
if (n_channels == 3) {
for (int h = 0; h < height; h++) {
unsigned char *iter_c = pixels_c + h * rowstride_c;
const unsigned char *iter_p = pixels_p + h * rowstride_p;
for (int w = 0; w < width; w++) {
iter_c[CAIRO_R] = iter_p[0];
iter_c[CAIRO_G] = iter_p[1];
iter_c[CAIRO_B] = iter_p[2];
iter_c[CAIRO_A] = 0xff;
iter_c += 4;
iter_p += n_channels;
}
}
} else {
for (int h = 0; h < height; h++) {
unsigned char *iter_c = pixels_c + h * rowstride_c;
const unsigned char *iter_p = pixels_p + h * rowstride_p;
for (int w = 0; w < width; w++) {
double alpha_factor = iter_p[3] / (double)0xff;
iter_c[CAIRO_R] = (unsigned char)(iter_p[0] * alpha_factor + .5);
iter_c[CAIRO_G] = (unsigned char)(iter_p[1] * alpha_factor + .5);
iter_c[CAIRO_B] = (unsigned char)(iter_p[2] * alpha_factor + .5);
iter_c[CAIRO_A] = iter_p[3];
iter_c += 4;
iter_p += n_channels;
}
}
}
}
int get_icon_width(cairo_surface_t *icon, int scale) {
return cairo_image_surface_get_width(icon) / scale;
}
int get_icon_height(cairo_surface_t *icon, int scale) {
return cairo_image_surface_get_height(icon) / scale;
}
cairo_surface_t *gdk_pixbuf_to_cairo_surface(GdkPixbuf *pixbuf)
{
assert(pixbuf);
int width = gdk_pixbuf_get_width(pixbuf);
int height = gdk_pixbuf_get_height(pixbuf);
cairo_format_t fmt = gdk_pixbuf_get_has_alpha(pixbuf) ? CAIRO_FORMAT_ARGB32 : CAIRO_FORMAT_RGB24;
cairo_surface_t *icon_surface = cairo_image_surface_create(fmt, width, height);
/* Copy pixel data from pixbuf to surface */
cairo_surface_flush(icon_surface);
pixbuf_data_to_cairo_data(gdk_pixbuf_read_pixels(pixbuf),
cairo_image_surface_get_data(icon_surface),
gdk_pixbuf_get_rowstride(pixbuf),
cairo_format_stride_for_width(fmt, width),
gdk_pixbuf_get_width(pixbuf),
gdk_pixbuf_get_height(pixbuf),
gdk_pixbuf_get_n_channels(pixbuf));
cairo_surface_mark_dirty(icon_surface);
return icon_surface;
}
/**
* Scales the given image dimensions if necessary according to the settings.
*
* @param w a pointer to the image width, to be modified in-place
* @param h a pointer to the image height, to be modified in-place
* @return TRUE if the dimensions were updated, FALSE if they were left unchanged
*/
static bool icon_size_clamp(int *w, int *h) {
int _w = *w, _h = *h;
int landscape = _w > _h;
int orig_larger = landscape ? _w : _h;
double larger = orig_larger;
double smaller = landscape ? _h : _w;
if (settings.min_icon_size && smaller < settings.min_icon_size) {
larger = larger / smaller * settings.min_icon_size;
smaller = settings.min_icon_size;
}
if (settings.max_icon_size && larger > settings.max_icon_size) {
smaller = smaller / larger * settings.max_icon_size;
larger = settings.max_icon_size;
}
if ((int) larger != orig_larger) {
*w = (int) (landscape ? larger : smaller);
*h = (int) (landscape ? smaller : larger);
return TRUE;
}
return FALSE;
}
/**
* Scales the given GdkPixbuf if necessary according to the settings.
*
* @param pixbuf (nullable) The pixbuf, which may be too big.
* Takes ownership of the reference.
* @param dpi_scale An integer for the dpi scaling. That doesn't mean the icon
* is always scaled by dpi_scale.
* @return the scaled version of the pixbuf. If scaling wasn't
* necessary, it returns the same pixbuf. Transfers full
* ownership of the reference.
*/
static GdkPixbuf *icon_pixbuf_scale(GdkPixbuf *pixbuf, int dpi_scale)
{
ASSERT_OR_RET(pixbuf, NULL);
int w = gdk_pixbuf_get_width(pixbuf);
int h = gdk_pixbuf_get_height(pixbuf);
// TODO immediately rescale icon upon scale changes
if (icon_size_clamp(&w, &h)) {
GdkPixbuf *scaled = gdk_pixbuf_scale_simple(
pixbuf,
w * dpi_scale,
h * dpi_scale,
GDK_INTERP_BILINEAR);
g_object_unref(pixbuf);
pixbuf = scaled;
}
return pixbuf;
}
GdkPixbuf *get_pixbuf_from_file(const char *filename, int scale)
{
char *path = string_to_path(g_strdup(filename));
GError *error = NULL;
gint w, h;
if (!gdk_pixbuf_get_file_info (path, &w, &h)) {
LOG_W("Failed to load image info for %s", filename);
g_free(path);
return NULL;
}
// TODO immediately rescale icon upon scale changes
icon_size_clamp(&w, &h);
GdkPixbuf *pixbuf = gdk_pixbuf_new_from_file_at_scale(path,
w * scale,
h * scale,
TRUE,
&error);
if (error) {
LOG_W("%s", error->message);
g_error_free(error);
}
g_free(path);
return pixbuf;
}
char *get_path_from_icon_name(const char *iconname)
{
if (STR_EMPTY(iconname))
return NULL;
const char *suffixes[] = { ".svg", ".svgz", ".png", ".xpm", NULL };
gchar *uri_path = NULL;
char *new_name = NULL;
if (g_str_has_prefix(iconname, "file://")) {
uri_path = g_filename_from_uri(iconname, NULL, NULL);
if (uri_path)
iconname = uri_path;
}
/* absolute path? */
if (iconname[0] == '/' || iconname[0] == '~') {
new_name = g_strdup(iconname);
} else {
/* search in icon_path */
char *start = settings.icon_path,
*end, *current_folder, *maybe_icon_path;
do {
end = strchr(start, ':');
if (!end) end = strchr(settings.icon_path, '\0'); /* end = end of string */
current_folder = g_strndup(start, end - start);
for (const char **suf = suffixes; *suf; suf++) {
gchar *name_with_extension = g_strconcat(iconname, *suf, NULL);
maybe_icon_path = g_build_filename(current_folder, name_with_extension, NULL);
if (is_readable_file(maybe_icon_path)) {
new_name = g_strdup(maybe_icon_path);
}
g_free(name_with_extension);
g_free(maybe_icon_path);
if (new_name)
break;
}
g_free(current_folder);
if (new_name)
break;
start = end + 1;
} while (STR_FULL(end));
if (!new_name)
LOG_W("No icon found in path: '%s'", iconname);
}
g_free(uri_path);
return new_name;
}
GdkPixbuf *get_pixbuf_from_icon(const char *iconname, int scale)
{
char *path = get_path_from_icon_name(iconname);
if (!path) {
return NULL;
}
GdkPixbuf *pixbuf = NULL;
pixbuf = get_pixbuf_from_file(path, scale);
g_free(path);
if (!pixbuf)
LOG_W("No icon found in path: '%s'", iconname);
return pixbuf;
}
GdkPixbuf *icon_get_for_name(const char *name, char **id, int scale)
{
ASSERT_OR_RET(name, NULL);
ASSERT_OR_RET(id, NULL);
GdkPixbuf *pb = get_pixbuf_from_icon(name, scale);
if (pb)
*id = g_strdup(name);
return pb;
}
GdkPixbuf *icon_get_for_data(GVariant *data, char **id, int dpi_scale)
{
ASSERT_OR_RET(data, NULL);
ASSERT_OR_RET(id, NULL);
if (!STR_EQ("(iiibiiay)", g_variant_get_type_string(data))) {
LOG_W("Invalid data for pixbuf given.");
return NULL;
}
/* The raw image is a big array of char data.
*
* The image is serialised rowwise pixel by pixel. The rows are aligned
* by a spacer full of garbage. The overall data length of data + garbage
* is called the rowstride.
*
* Mind the missing spacer at the last row.
*
* len: |<--------------rowstride---------------->|
* len: |<-width*pixelstride->|
* row 1: | data for row 1 | spacer of garbage |
* row 2: | data for row 2 | spacer of garbage |
* | . | spacer of garbage |
* | . | spacer of garbage |
* | . | spacer of garbage |
* row n-1: | data for row n-1 | spacer of garbage |
* row n: | data for row n |
*/
GdkPixbuf *pixbuf = NULL;
GVariant *data_variant = NULL;
unsigned char *data_pb;
gsize len_expected;
gsize len_actual;
gsize pixelstride;
int width;
int height;
int rowstride;
int has_alpha;
int bits_per_sample;
int n_channels;
g_variant_get(data,
"(iiibii@ay)",
&width,
&height,
&rowstride,
&has_alpha,
&bits_per_sample,
&n_channels,
&data_variant);
// note: (A+7)/8 rounds up A to the next byte boundary
pixelstride = (n_channels * bits_per_sample + 7)/8;
len_expected = (height - 1) * rowstride + width * pixelstride;
len_actual = g_variant_get_size(data_variant);
if (len_actual != len_expected) {
LOG_W("Expected image data to be of length %" G_GSIZE_FORMAT
" but got a length of %" G_GSIZE_FORMAT,
len_expected,
len_actual);
g_variant_unref(data_variant);
return NULL;
}
// g_memdup is deprecated in glib 2.67.4 and higher.
// g_memdup2 is a safer alternative
#if GLIB_CHECK_VERSION(2,67,3)
data_pb = (guchar *) g_memdup2(g_variant_get_data(data_variant), len_actual);
#else
data_pb = (guchar *) g_memdup(g_variant_get_data(data_variant), len_actual);
#endif
pixbuf = gdk_pixbuf_new_from_data(data_pb,
GDK_COLORSPACE_RGB,
has_alpha,
bits_per_sample,
width,
height,
rowstride,
(GdkPixbufDestroyNotify) g_free,
data_pb);
if (!pixbuf) {
/* Dear user, I'm sorry, I'd like to give you a more specific
* error message. But sadly, I can't */
LOG_W("Cannot serialise raw icon data into pixbuf.");
return NULL;
}
/* To calculate a checksum of the current image, we have to remove
* all excess spacers, so that our checksummed memory only contains
* real data. */
size_t data_chk_len = pixelstride * width * height;
unsigned char *data_chk = g_malloc(data_chk_len);
size_t rowstride_short = pixelstride * width;
for (int i = 0; i < height; i++) {
memcpy(data_chk + (i*rowstride_short),
data_pb + (i*rowstride),
rowstride_short);
}
*id = g_compute_checksum_for_data(G_CHECKSUM_MD5, data_chk, data_chk_len);
g_free(data_chk);
g_variant_unref(data_variant);
pixbuf = icon_pixbuf_scale(pixbuf, dpi_scale);
return pixbuf;
}
/* vim: set ft=c tabstop=8 shiftwidth=8 expandtab textwidth=0: */

View File

@ -1,90 +0,0 @@
#ifndef DUNST_ICON_H
#define DUNST_ICON_H
#include <cairo.h>
#include <gdk-pixbuf/gdk-pixbuf.h>
#include "notification.h"
cairo_surface_t *gdk_pixbuf_to_cairo_surface(GdkPixbuf *pixbuf);
/** Retrieve an icon by its full filepath, scaled according to settings.
*
* @param filename A string representing a readable file path
* @param scale An integer representing the output dpi scaling.
*
* @return an instance of `GdkPixbuf`
* @retval NULL: file does not exist, not readable, etc..
*/
GdkPixbuf *get_pixbuf_from_file(const char *filename, int scale);
/**
* Get the unscaled icon width.
*
* If scale is 2 for example, the icon will render in twice the size, but
* get_icon_width still returns the same size as when scale is 1.
*/
int get_icon_width(cairo_surface_t *icon, int scale);
/**
* Get the unscaled icon height, see get_icon_width.
*/
int get_icon_height(cairo_surface_t *icon, int scale);
/** Retrieve a path from an icon name.
*
* @param iconname A string describing a `file://` URL, an arbitary filename
* or an icon name, which then gets searched for in the
* settings.icon_path
*
* @return a newly allocated string with the icon path
* @retval NULL: file does not exist, not readable, etc..
*/
char *get_path_from_icon_name(const char *iconname);
/** Retrieve an icon by its name sent via the notification bus, scaled according to settings
*
* @param iconname A string describing a `file://` URL, an arbitary filename
* or an icon name, which then gets searched for in the
* settings.icon_path
* @param scale An integer representing the output dpi scaling.
*
* @return an instance of `GdkPixbuf`
* @retval NULL: file does not exist, not readable, etc..
*/
GdkPixbuf *get_pixbuf_from_icon(const char *iconname, int scale);
/** Read an icon from disk and convert it to a GdkPixbuf, scaled according to settings
*
* The returned id will be a unique identifier. To check if two given
* GdkPixbufs are equal, it's sufficient to just compare the id strings.
*
* @param name A string describing and icon. May be a full path, a file path or
* just a simple name. If it's a name without a slash, the icon will
* get searched in the folders of the icon_path setting.
* @param id (necessary) A unique identifier of the returned pixbuf. Only filled,
* if the return value is non-NULL.
* @param dpi_scale An integer representing the output dpi scaling.
* @return an instance of `GdkPixbuf`, representing the name's image
* @retval NULL: Invalid path given
*/
GdkPixbuf *icon_get_for_name(const char *name, char **id, int dpi_scale);
/** Convert a GVariant like described in GdkPixbuf, scaled according to settings
*
* The returned id will be a unique identifier. To check if two given
* GdkPixbufs are equal, it's sufficient to just compare the id strings.
*
* @param data A GVariant in the format "(iiibii@ay)" filled with values
* like described in the notification spec.
* @param id (necessary) A unique identifier of the returned pixbuf.
* Only filled, if the return value is non-NULL.
* @param scale An integer representing the output dpi scaling.
* @return an instance of `GdkPixbuf` derived from the GVariant
* @retval NULL: GVariant parameter nulled, invalid or in wrong format
*/
GdkPixbuf *icon_get_for_data(GVariant *data, char **id, int scale);
#endif
/* vim: set ft=c tabstop=8 shiftwidth=8 expandtab textwidth=0: */

View File

@ -1,81 +0,0 @@
#include "input.h"
#include "log.h"
#include "menu.h"
#include "settings.h"
#include "queues.h"
#include <stddef.h>
#include <linux/input-event-codes.h>
void input_handle_click(unsigned int button, bool button_down, int mouse_x, int mouse_y){
LOG_I("Pointer handle button %i: %i", button, button_down);
if (button_down) {
// make sure it only reacts on button release
return;
}
enum mouse_action *acts;
switch (button) {
case BTN_LEFT:
acts = settings.mouse_left_click;
break;
case BTN_MIDDLE:
acts = settings.mouse_middle_click;
break;
case BTN_RIGHT:
acts = settings.mouse_right_click;
break;
case BTN_TOUCH:
// TODO Add separate action for touch
acts = settings.mouse_left_click;
break;
default:
LOG_W("Unsupported mouse button: '%d'", button);
return;
}
for (int i = 0; acts[i]; i++) {
enum mouse_action act = acts[i];
if (act == MOUSE_CLOSE_ALL) {
queues_history_push_all();
break;
}
if (act == MOUSE_CONTEXT_ALL) {
context_menu();
continue;
}
if (act == MOUSE_DO_ACTION || act == MOUSE_CLOSE_CURRENT || act == MOUSE_CONTEXT || act == MOUSE_OPEN_URL) {
int y = settings.separator_height;
struct notification *n = NULL;
int first = true;
for (const GList *iter = queues_get_displayed(); iter;
iter = iter->next) {
n = iter->data;
if (mouse_y > y && mouse_y < y + n->displayed_height)
break;
y += n->displayed_height + settings.separator_height;
if (first)
y += settings.frame_width;
}
if (n) {
if (act == MOUSE_CLOSE_CURRENT) {
n->marked_for_closure = REASON_USER;
} else if (act == MOUSE_DO_ACTION) {
notification_do_action(n);
} else if (act == MOUSE_OPEN_URL) {
notification_open_url(n);
} else {
notification_open_context_menu(n);
}
}
}
}
wake_up();
}
/* vim: set ft=c tabstop=8 shiftwidth=8 expandtab textwidth=0: */

View File

@ -1,18 +0,0 @@
#ifndef DUNST_INPUT_H
#define DUNST_INPUT_H
#include <stdbool.h>
/**
* Handle incoming mouse click events
*
* @param button code, A linux input event code
* @param button_down State of the button
* @param mouse_x X-position of the mouse, relative to the window
* @param mouse_y Y-position of the mouse, relative to the window
*
*/
void input_handle_click(unsigned int button, bool button_down, int mouse_x, int mouse_y);
#endif
/* vim: set ft=c tabstop=8 shiftwidth=8 expandtab textwidth=0: */

103
src/log.c
View File

@ -1,103 +0,0 @@
/* copyright 2012 - 2013 Sascha Kruse and contributors (see LICENSE for licensing information) */
/**
* @file src/log.c
* @brief logging wrapper to use GLib's logging capabilities
*/
#include "log.h"
#include <glib.h>
#include "utils.h"
static GLogLevelFlags log_level = G_LOG_LEVEL_WARNING;
/* see log.h */
static const char *log_level_to_string(GLogLevelFlags level)
{
switch (level) {
case G_LOG_LEVEL_ERROR: return "ERROR";
case G_LOG_LEVEL_CRITICAL: return "CRITICAL";
case G_LOG_LEVEL_WARNING: return "WARNING";
case G_LOG_LEVEL_MESSAGE: return "MESSAGE";
case G_LOG_LEVEL_INFO: return "INFO";
case G_LOG_LEVEL_DEBUG: return "DEBUG";
default: return "UNKNOWN";
}
}
/* see log.h */
void log_set_level_from_string(const char *level)
{
ASSERT_OR_RET(level,);
if (STR_CASEQ(level, "critical"))
log_level = G_LOG_LEVEL_CRITICAL;
else if (STR_CASEQ(level, "crit"))
log_level = G_LOG_LEVEL_CRITICAL;
else if (STR_CASEQ(level, "warning"))
log_level = G_LOG_LEVEL_WARNING;
else if (STR_CASEQ(level, "warn"))
log_level = G_LOG_LEVEL_WARNING;
else if (STR_CASEQ(level, "message"))
log_level = G_LOG_LEVEL_MESSAGE;
else if (STR_CASEQ(level, "mesg"))
log_level = G_LOG_LEVEL_MESSAGE;
else if (STR_CASEQ(level, "info"))
log_level = G_LOG_LEVEL_INFO;
else if (STR_CASEQ(level, "debug"))
log_level = G_LOG_LEVEL_DEBUG;
else if (STR_CASEQ(level, "deb"))
log_level = G_LOG_LEVEL_DEBUG;
else
LOG_W("Unknown log level: '%s'", level);
}
void log_set_level(GLogLevelFlags level)
{
log_level = level;
}
/**
* Log handling function for GLib's logging wrapper
*
* @param log_domain Used only by GLib
* @param message_level Used only by GLib
* @param message Used only by GLib
* @param testing If not `NULL` (here: `true`), do nothing
*/
static void dunst_log_handler(
const gchar *log_domain,
GLogLevelFlags message_level,
const gchar *message,
gpointer testing)
{
if (testing)
log_level = G_LOG_LEVEL_ERROR;
GLogLevelFlags message_level_masked = message_level & G_LOG_LEVEL_MASK;
/* if you want to have a debug build, you want to log anything,
* unconditionally, without specifying debug log level again */
#ifndef DEBUG_BUILD
if (log_level < message_level_masked)
return;
#endif
const char *log_level_str =
log_level_to_string(message_level_masked);
/* Use stderr for warnings and higher */
if (message_level_masked <= G_LOG_LEVEL_WARNING)
g_printerr("%s: %s\n", log_level_str, message);
else
g_print("%s: %s\n", log_level_str, message);
}
/* see log.h */
void dunst_log_init(bool testing)
{
g_log_set_default_handler(dunst_log_handler, (void*)testing);
}
/* vim: set ft=c tabstop=8 shiftwidth=8 expandtab textwidth=0: */

View File

@ -1,48 +0,0 @@
/* copyright 2013 Sascha Kruse and contributors (see LICENSE for licensing information) */
#include <glib.h>
#include <stdbool.h>
#include <stdlib.h>
#ifndef DUNST_LOG_H
#define DUNST_LOG_H
#define LOG_E g_error
#define LOG_C g_critical
#define LOG_W g_warning
#define LOG_M g_message
#define LOG_I g_info
#define LOG_D g_debug
#define DIE(...) do { LOG_C(__VA_ARGS__); exit(EXIT_FAILURE); } while (0)
/**
* Set the current loglevel to `level`
*
* @param level The desired log level
*
* If `level` is `NULL`, nothing will be done.
* If `level` is an invalid value, nothing will be done.
*/
void log_set_level(GLogLevelFlags level);
/**
* Set the current loglevel to `level`
*
* @param level The desired log level as a string
*
* If `level` is `NULL`, nothing will be done.
* If `level` is an invalid value, nothing will be done.
*/
void log_set_level_from_string(const char* level);
/**
* Initialise log handling. Can be called any time.
*
* @param testing If we're in testing mode and should
* suppress all output
*/
void dunst_log_init(bool testing);
#endif
/* vim: set ft=c tabstop=8 shiftwidth=8 expandtab textwidth=0: */

View File

@ -3,22 +3,14 @@
#include "markup.h"
#include <assert.h>
#include <ctype.h>
#include <stdbool.h>
#include <stdio.h>
#include <string.h>
#include "log.h"
#include "settings.h"
#include "utils.h"
/**
* Convert all HTML special symbols to HTML entities.
* @param str (nullable)
*/
static char *markup_quote(char *str)
{
ASSERT_OR_RET(str, NULL);
assert(str != NULL);
str = string_replace_all("&", "&amp;", str);
str = string_replace_all("\"", "&quot;", str);
@ -29,13 +21,9 @@ static char *markup_quote(char *str)
return str;
}
/**
* Convert all HTML special entities to their actual char.
* @param str (nullable)
*/
static char *markup_unquote(char *str)
{
ASSERT_OR_RET(str, NULL);
assert(str != NULL);
str = string_replace_all("&quot;", "\"", str);
str = string_replace_all("&apos;", "'", str);
@ -46,13 +34,9 @@ static char *markup_unquote(char *str)
return str;
}
/**
* Convert all HTML linebreak tags to a newline character
* @param str (nullable)
*/
static char *markup_br2nl(char *str)
{
ASSERT_OR_RET(str, NULL);
assert(str != NULL);
str = string_replace_all("<br>", "\n", str);
str = string_replace_all("<br/>", "\n", str);
@ -60,168 +44,23 @@ static char *markup_br2nl(char *str)
return str;
}
/* see markup.h */
void markup_strip_a(char **str, char **urls)
{
assert(*str);
char *tag1 = NULL;
if (urls)
*urls = NULL;
while ((tag1 = strstr(*str, "<a"))) {
// use href=" as stated in the notification spec
char *href = strstr(tag1, "href=\"");
char *tag1_end = strstr(tag1, ">");
char *tag2 = strstr(tag1, "</a>");
// the tag is broken, ignore it
if (!tag1_end) {
LOG_W("Given link is broken: '%s'",
tag1);
string_replace_at(*str, tag1-*str, strlen(tag1), "");
break;
}
if (tag2 && tag2 < tag1_end) {
int repl_len = (tag2 - tag1) + strlen("</a>");
LOG_W("Given link is broken: '%.*s.'",
repl_len, tag1);
string_replace_at(*str, tag1-*str, repl_len, "");
break;
}
// search contents of href attribute
char *plain_url = NULL;
if (href && href < tag1_end) {
// shift href to the actual begin of the value
href = href+6;
const char *quote = strstr(href, "\"");
if (quote && quote < tag1_end) {
plain_url = g_strndup(href, quote-href);
}
}
// text between a tags
int text_len;
if (tag2)
text_len = tag2 - (tag1_end+1);
else
text_len = strlen(tag1_end+1);
char *text = g_strndup(tag1_end+1, text_len);
int repl_len = text_len + (tag1_end-tag1) + 1;
repl_len += tag2 ? strlen("</a>") : 0;
*str = string_replace_at(*str, tag1-*str, repl_len, text);
// if there had been a href attribute,
// add it to the URLs
if (plain_url && urls) {
text = string_replace_all("]", "", text);
text = string_replace_all("[", "", text);
char *url = g_strdup_printf("[%s] %s", text, plain_url);
*urls = string_append(*urls, url, "\n");
g_free(url);
}
g_free(plain_url);
g_free(text);
}
}
/* see markup.h */
void markup_strip_img(char **str, char **urls)
{
const char *start;
if (urls)
*urls = NULL;
while ((start = strstr(*str, "<img"))) {
const char *end = strstr(start, ">");
// the tag is broken, ignore it
if (!end) {
LOG_W("Given image is broken: '%s'", start);
string_replace_at(*str, start-*str, strlen(start), "");
break;
}
// use attribute=" as stated in the notification spec
const char *alt_s = strstr(start, "alt=\"");
const char *src_s = strstr(start, "src=\"");
char *text_alt = NULL;
char *text_src = NULL;
const char *src_e = NULL, *alt_e = NULL;
if (alt_s)
alt_e = strstr(alt_s + strlen("alt=\""), "\"");
if (src_s)
src_e = strstr(src_s + strlen("src=\""), "\"");
// Move pointer to the actual start
alt_s = alt_s ? alt_s + strlen("alt=\"") : NULL;
src_s = src_s ? src_s + strlen("src=\"") : NULL;
/* check if alt and src attribute are given
* If both given, check the alignment of all pointers */
if ( alt_s && alt_e
&& src_s && src_e
&& ( (alt_s < src_s && alt_e < src_s-strlen("src=\"") && src_e < end)
||(src_s < alt_s && src_e < alt_s-strlen("alt=\"") && alt_e < end)) ) {
text_alt = g_strndup(alt_s, alt_e-alt_s);
text_src = g_strndup(src_s, src_e-src_s);
/* check if single valid alt attribute is available */
} else if (alt_s && alt_e && alt_e < end && (!src_s || src_s < alt_s || alt_e < src_s - strlen("src=\""))) {
text_alt = g_strndup(alt_s, alt_e-alt_s);
/* check if single valid src attribute is available */
} else if (src_s && src_e && src_e < end && (!alt_s || alt_s < src_s || src_e < alt_s - strlen("alt=\""))) {
text_src = g_strndup(src_s, src_e-src_s);
} else {
LOG_W("Given image argument is broken: '%.*s'",
(int)(end-start), start);
}
// replacement text for alt
int repl_len = end - start + 1;
if (!text_alt)
text_alt = g_strdup("[image]");
*str = string_replace_at(*str, start-*str, repl_len, text_alt);
// if there had been a href attribute,
// add it to the URLs
if (text_src && urls) {
text_alt = string_replace_all("]", "", text_alt);
text_alt = string_replace_all("[", "", text_alt);
char *url = g_strdup_printf("[%s] %s", text_alt, text_src);
*urls = string_append(*urls, url, "\n");
g_free(url);
}
g_free(text_src);
g_free(text_alt);
}
}
/* see markup.h */
/*
* 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)
{
ASSERT_OR_RET(str, NULL);
if (str == NULL) {
return NULL;
}
/* strip all tags */
string_strip_delimited(str, '<', '>');
@ -232,83 +71,15 @@ char *markup_strip(char *str)
return str;
}
/**
* Determine if an & character pointed to by \p str is a markup & entity or
* part of the text
*
* @retval true: \p str is an entity
* @retval false: It's no valid entity
/*
* Transform the string in accordance with `markup_mode` and
* `settings.ignore_newline`
*/
static bool markup_is_entity(const char *str)
{
assert(str);
assert(*str == '&');
char *end = strchr(str, ';');
ASSERT_OR_RET(end, false);
// Parse (hexa)decimal entities with the format &#1234; or &#xABC;
if (str[1] == '#') {
const char *cur = str + 2;
if (*cur == 'x') {
cur++;
// Reject &#x;
if (*cur == ';')
return false;
while (isxdigit(*cur) && cur < end)
cur++;
} else {
// Reject &#;
if (*cur == ';')
return false;
while (isdigit(*cur) && cur < end)
cur++;
}
return (cur == end);
} else {
const char *supported_tags[] = {"&amp;", "&lt;", "&gt;", "&quot;", "&apos;"};
for (int i = 0; i < sizeof(supported_tags)/sizeof(*supported_tags); i++) {
if (g_str_has_prefix(str, supported_tags[i]))
return true;
}
return false;
}
}
/**
* Escape all unsupported and invalid &-entities in a string. If the resulting
* string does not fit it will be reallocated.
*
* @param str The string to be transformed
*/
static char *markup_escape_unsupported(char *str)
{
ASSERT_OR_RET(str, NULL);
char *match = str;
while ((match = strchr(match, '&'))) {
if (!markup_is_entity(match)) {
int pos = match - str;
str = string_replace_at(str, pos, 1, "&amp;");
match = str + pos + strlen("&amp;");
} else {
match++;
}
}
return str;
}
/* see markup.h */
char *markup_transform(char *str, enum markup_mode markup_mode)
{
ASSERT_OR_RET(str, NULL);
if (str == NULL) {
return NULL;
}
switch (markup_mode) {
case MARKUP_NULL:
@ -324,10 +95,7 @@ char *markup_transform(char *str, enum markup_mode markup_mode)
str = markup_quote(str);
break;
case MARKUP_FULL:
str = markup_escape_unsupported(str);
str = markup_br2nl(str);
markup_strip_a(&str, NULL);
markup_strip_img(&str, NULL);
break;
}
@ -338,4 +106,4 @@ char *markup_transform(char *str, enum markup_mode markup_mode)
return str;
}
/* vim: set ft=c tabstop=8 shiftwidth=8 expandtab textwidth=0: */
/* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */

View File

@ -2,50 +2,10 @@
#ifndef DUNST_MARKUP_H
#define DUNST_MARKUP_H
enum markup_mode {
MARKUP_NULL,
MARKUP_NO,
MARKUP_STRIP,
MARKUP_FULL
};
#include "settings.h"
/**
* 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);
/**
* Remove HTML hyperlinks of a string.
*
* @param str The string to replace a tags
* @param urls (nullable) If any href-attributes found, an `\n` concatenated
* string of the URLs in format `[<text between tags>] <href>`
*/
void markup_strip_a(char **str, char **urls);
/**
* Remove img-tags of a string. If alt attribute given, use this as replacement.
*
* @param str The string to replace img tags
* @param urls (nullable) If any src-attributes found, an `\n` concatenated string of
* the URLs in format `[<alt>] <src>`
*/
void markup_strip_img(char **str, char **urls);
/**
* Transform the string in accordance with `markup_mode` and
* `settings.ignore_newline`
*/
char *markup_transform(char *str, enum markup_mode markup_mode);
#endif
/* vim: set ft=c tabstop=8 shiftwidth=8 expandtab textwidth=0: */
/* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */

View File

@ -14,81 +14,76 @@
#include "dbus.h"
#include "dunst.h"
#include "log.h"
#include "notification.h"
#include "queues.h"
#include "settings.h"
#include "utils.h"
static bool is_initialized = false;
static regex_t url_regex;
static regex_t cregex;
static gpointer context_menu_thread(gpointer data);
struct {
GList *locked_notifications;
} menu_ctx;
/**
* Initializes regexes needed for matching.
*
* @return true if initialization succeeded
*/
static bool regex_init(void)
static int regex_init(void)
{
if (is_initialized)
return true;
return 1;
char *regex =
"\\<(https?://|ftps?://|news://|mailto:|file://|www\\.)"
"\\b(https?://|ftps?://|news://|mailto:|file://|www\\.)"
"[-[:alnum:]_\\@;/?:&=%$.+!*\x27,~#]*"
"(\\([-[:alnum:]_\\@;/?:&=%$.+!*\x27,~#]*\\)|[-[:alnum:]_\\@;/?:&=%$+*~])+";
int code = regcomp(&url_regex, regex, REG_EXTENDED | REG_ICASE);
if (code != 0) {
char error_buf[120];
regerror(code, &url_regex, error_buf, sizeof(error_buf));
LOG_W("Failed to compile URL-matching regex: %s", error_buf);
return false;
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 true;
return 1;
}
}
void regex_teardown(void)
{
if (is_initialized) {
regfree(&url_regex);
regfree(&cregex);
is_initialized = false;
}
}
/* see menu.h */
/*
* Exctract all urls from a given string.
*
* Return: a string of urls separated by \n
*
*/
char *extract_urls(const char *to_match)
{
if (!to_match)
return NULL;
char *urls = NULL;
if (!regex_init())
return NULL;
char *urls = NULL;
const char *p = to_match;
regmatch_t m;
while (1) {
int nomatch = regexec(&url_regex, p, 1, &m, 0);
if (nomatch || m.rm_so == -1)
int nomatch = regexec(&cregex, p, 1, &m, 0);
if (nomatch) {
return urls;
}
int start;
int finish;
if (m.rm_so == -1) {
break;
int start = m.rm_so + (p - to_match);
int finish = m.rm_eo + (p - to_match);
}
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;
@ -100,66 +95,28 @@ char *extract_urls(const char *to_match)
*/
void open_browser(const char *in)
{
if (!settings.browser_cmd) {
LOG_C("Unable to open browser: No browser command set.");
// 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);
}
}
char *url, *end;
// If any, remove leading [ linktext ] from URL
if (*in == '[' && (end = strstr(in, "] ")))
url = g_strdup(end + 2);
else
url = g_strdup(in);
int argc = 2+g_strv_length(settings.browser_cmd);
char **argv = g_malloc_n(argc, sizeof(char*));
memcpy(argv, settings.browser_cmd, argc * sizeof(char*));
argv[argc-2] = url;
argv[argc-1] = NULL;
GError *err = NULL;
g_spawn_async(NULL,
argv,
NULL,
G_SPAWN_DEFAULT
| G_SPAWN_SEARCH_PATH
| G_SPAWN_STDOUT_TO_DEV_NULL
| G_SPAWN_STDERR_TO_DEV_NULL,
NULL,
NULL,
NULL,
&err);
if (err) {
LOG_C("Cannot spawn browser: %s", err->message);
g_error_free(err);
}
g_free(argv);
g_free(url);
}
char *notification_dmenu_string(struct notification *n)
{
char *dmenu_str = NULL;
gpointer p_key;
gpointer p_value;
GHashTableIter iter;
g_hash_table_iter_init(&iter, n->actions);
while (g_hash_table_iter_next(&iter, &p_key, &p_value)) {
char *key = (char*) p_key;
char *value = (char*) p_value;
char *act_str = g_strdup_printf("#%s (%s) [%d,%s]", value, n->summary, n->id, key);
dmenu_str = string_append(dmenu_str, act_str, "\n");
g_free(act_str);
}
return dmenu_str;
}
/*
@ -168,225 +125,138 @@ char *notification_dmenu_string(struct notification *n)
*/
void invoke_action(const char *action)
{
struct notification *invoked = NULL;
uint id;
notification *invoked = NULL;
char *action_identifier = NULL;
char *data_start, *data_comma, *data_end;
/* format: #<human readable> (<summary>)[<id>,<action>] */
data_start = strrchr(action, '[');
if (!data_start) {
LOG_W("Invalid action: '%s'", action);
char *appname_begin = strchr(action, '[');
if (!appname_begin) {
printf("invalid action: %s\n", action);
return;
}
id = strtol(++data_start, &data_comma, 10);
if (*data_comma != ',') {
LOG_W("Invalid action: '%s'", action);
return;
}
data_end = strchr(data_comma+1, ']');
if (!data_end) {
LOG_W("Invalid action: '%s'", action);
return;
}
char *action_key = g_strndup(data_comma+1, data_end-data_comma-1);
for (const GList *iter = queues_get_displayed();
iter;
iter = iter->next) {
struct notification *n = iter->data;
if (n->id != id)
continue;
if (g_hash_table_contains(n->actions, action_key)) {
invoked = n;
break;
}
}
if (invoked && action_key) {
signal_action_invoked(invoked, action_key);
}
g_free(action_key);
}
/**
* Dispatch whatever has been returned by dmenu.
* If the given result of dmenu is empty or NULL, nothing will be done.
*
* @param input The result from dmenu.
*/
void dispatch_menu_result(const char *input)
{
ASSERT_OR_RET(input,);
char *in = g_strdup(input);
g_strstrip(in);
if (in[0] == '#')
invoke_action(in + 1);
else if (in[0] != '\0')
open_browser(in);
g_free(in);
}
/** Call dmenu with the specified input. Blocks until dmenu is finished.
*
* @param dmenu_input The input string to feed into dmenu
* @returns the selected string from dmenu
*/
char *invoke_dmenu(const char *dmenu_input)
{
if (!settings.dmenu_cmd) {
LOG_C("Unable to open dmenu: No dmenu command set.");
return NULL;
}
ASSERT_OR_RET(STR_FULL(dmenu_input), NULL);
gint dunst_to_dmenu;
gint dmenu_to_dunst;
GError *err = NULL;
char buf[1024];
char *ret = NULL;
g_spawn_async_with_pipes(NULL,
settings.dmenu_cmd,
NULL,
G_SPAWN_DEFAULT
| G_SPAWN_SEARCH_PATH,
NULL,
NULL,
NULL,
&dunst_to_dmenu,
&dmenu_to_dunst,
NULL,
&err);
if (err) {
LOG_C("Cannot spawn dmenu: %s", err->message);
g_error_free(err);
} else {
size_t wlen = strlen(dmenu_input);
if (write(dunst_to_dmenu, dmenu_input, wlen) != wlen) {
LOG_W("Cannot feed dmenu with input: %s", strerror(errno));
}
close(dunst_to_dmenu);
ssize_t rlen = read(dmenu_to_dunst, buf, sizeof(buf));
close(dmenu_to_dunst);
if (rlen > 0)
ret = g_strndup(buf, rlen);
else
LOG_W("Didn't receive input from dmenu.");
}
return ret;
}
/**
* Lock and get all notifications with an action or URL.
**/
static GList *get_actionable_notifications(void)
{
GList *locked_notifications = NULL;
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) {
struct notification *n = iter->data;
notification *n = iter->data;
if (g_str_has_prefix(appname_begin, n->appname) && strlen(n->appname) == appname_len) {
if (!n->actions)
continue;
if (n->urls || g_hash_table_size(n->actions)) {
notification_lock(n);
locked_notifications = g_list_prepend(locked_notifications, n);
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;
}
}
}
}
return locked_notifications;
if (invoked && action_identifier) {
action_invoked(invoked, action_identifier);
}
}
/* see menu.h */
/*
* 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)
{
GList *notifications = get_actionable_notifications();
context_menu_for(notifications);
}
/* see menu.h */
void context_menu_for(GList *notifications)
{
if (menu_ctx.locked_notifications) {
LOG_W("Context menu already running, refusing to rerun");
if (settings.dmenu_cmd == NULL) {
fprintf(stderr, "dmenu command not set properly. Cowardly refusing to open the context menu.\n");
return;
}
menu_ctx.locked_notifications = notifications;
GError *err = NULL;
g_thread_unref(g_thread_try_new("dmenu",
context_menu_thread,
NULL,
&err));
if (err) {
LOG_C("Cannot start thread to call dmenu: %s", err->message);
g_error_free(err);
}
}
static gboolean context_menu_result_dispatch(gpointer user_data)
{
char *dmenu_output = (char*)user_data;
dispatch_menu_result(dmenu_output);
for (GList *iter = menu_ctx.locked_notifications; iter; iter = iter->next) {
struct notification *n = iter->data;
notification_unlock(n);
if (n->marked_for_closure) {
// Don't close notification if context was aborted
if (dmenu_output != NULL)
queues_notification_close(n, n->marked_for_closure);
n->marked_for_closure = 0;
}
}
menu_ctx.locked_notifications = NULL;
g_list_free(menu_ctx.locked_notifications);
g_free(dmenu_output);
wake_up();
return G_SOURCE_REMOVE;
}
static gpointer context_menu_thread(gpointer data)
{
char *dmenu_input = NULL;
char *dmenu_output;
for (GList *iter = menu_ctx.locked_notifications; iter; iter = iter->next) {
struct notification *n = iter->data;
char *dmenu_str = notification_dmenu_string(n);
dmenu_input = string_append(dmenu_input, dmenu_str, "\n");
g_free(dmenu_str);
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");
}
dmenu_output = invoke_dmenu(dmenu_input);
g_timeout_add(50, context_menu_result_dispatch, dmenu_output);
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);
return NULL;
}
/* vim: set ft=c tabstop=8 shiftwidth=8 expandtab textwidth=0: */
/* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */

View File

@ -2,31 +2,10 @@
#ifndef DUNST_MENU_H
#define DUNST_MENU_H
#include <glib.h>
/**
* Extract all urls from the given string.
*
* @param to_match (nullable) String to extract URLs
* @return a string of urls separated by '\n'
* @retval NULL: No URLs found
*/
char *extract_urls(const char *to_match);
void open_browser(const char *in);
void invoke_action(const char *action);
void regex_teardown(void);
/**
* Open the context menu that lets the user select urls/actions/etc for all displayed notifications.
*/
void context_menu(void);
/**
* Open the context menu that lets the user select urls/actions/etc for the specified notifications.
* @param notifications (nullable) List of notifications for which the context menu should be opened
*/
void context_menu_for(GList *notifications);
#endif
/* vim: set ft=c tabstop=8 shiftwidth=8 expandtab textwidth=0: */
/* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */

View File

@ -15,156 +15,92 @@
#include "dbus.h"
#include "dunst.h"
#include "icon.h"
#include "log.h"
#include "markup.h"
#include "menu.h"
#include "queues.h"
#include "rules.h"
#include "settings.h"
#include "utils.h"
#include "draw.h"
#include "x11/x.h"
static void notification_extract_urls(struct notification *n);
static void notification_format_message(struct notification *n);
/* see notification.h */
const char *enum_to_string_fullscreen(enum behavior_fullscreen in)
/*
* print a human readable representation
* of the given notification to stdout.
*/
void notification_print(notification *n)
{
switch (in) {
case FS_SHOW: return "show";
case FS_DELAY: return "delay";
case FS_PUSHBACK: return "pushback";
case FS_NULL: return "(null)";
default:
LOG_E("Invalid %s enum value in %s:%d", "fullscreen", __FILE__, __LINE__);
break;
}
}
struct _notification_private {
gint refcount;
};
/* see notification.h */
void notification_print(const struct notification *n)
{
//TODO: use logging info for this
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->iconname);
printf("\traw_icon set: %s\n", (n->icon_id && !STR_EQ(n->iconname, n->icon_id)) ? "true" : "false");
printf("\ticon_id: '%s'\n", n->icon_id);
printf("\tdesktop_entry: '%s'\n", n->desktop_entry ? n->desktop_entry : "");
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->colors.fg);
printf("\tbg: %s\n", n->colors.bg);
printf("\thighlight: %s\n", n->colors.highlight);
printf("\tframe: %s\n", n->colors.frame);
printf("\tfullscreen: %s\n", enum_to_string_fullscreen(n->fullscreen));
printf("\tprogress: %d\n", n->progress);
printf("\tstack_tag: %s\n", (n->stack_tag ? n->stack_tag : ""));
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) {
char *urls = string_replace_all("\n", "\t\t\n", g_strdup(n->urls));
printf("\turls:\n");
printf("\t{\n");
printf("\t\t%s\n", urls);
printf("\t}\n");
g_free(urls);
}
if (g_hash_table_size(n->actions) == 0) {
printf("\tactions: {}\n");
} else {
gpointer p_key, p_value;
GHashTableIter iter;
g_hash_table_iter_init(&iter, n->actions);
printf("\tactions: {\n");
while (g_hash_table_iter_next(&iter, &p_key, &p_value))
printf("\t\t\"%s\": \"%s\"\n", (char*)p_key, (char*)p_value);
printf("\t\t%s\n", n->urls);
printf("\t}\n");
}
printf("\tscript_count: %d\n", n->script_count);
if (n->script_count > 0) {
printf("\tscripts: ");
for (int i = 0; i < n->script_count; i++) {
printf("'%s' ",n->scripts[i]);
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("\n");
printf("\t}\n");
printf("\tactions_dmenu: %s\n", n->actions->dmenu_str);
}
printf("\tscript: %s\n", n->script);
printf("}\n");
fflush(stdout);
}
/* see notification.h */
void notification_run_script(struct notification *n)
/*
* Run the script associated with the
* given notification.
*/
void notification_run_script(notification *n)
{
if (n->script_run && !settings.always_run_script)
if (!n->script || strlen(n->script) < 1)
return;
n->script_run = true;
const char *appname = n->appname ? n->appname : "";
const char *summary = n->summary ? n->summary : "";
const char *body = n->body ? n->body : "";
const char *icon = n->iconname ? n->iconname : "";
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);
for(int i = 0; i < n->script_count; i++) {
int pid1 = fork();
const char *script = n->scripts[i];
if (STR_EMPTY(script))
continue;
int pid1 = fork();
if (pid1) {
int status;
waitpid(pid1, &status, 0);
if (pid1) {
int status;
waitpid(pid1, &status, 0);
} else {
int pid2 = fork();
if (pid2) {
exit(0);
} else {
// second fork to prevent zombie processes
int pid2 = fork();
if (pid2) {
exit(0);
} else {
// Set environment variables
gchar *n_id_str = g_strdup_printf("%i", n->id);
gchar *n_progress_str = g_strdup_printf("%i", n->progress);
gchar *n_timeout_str = g_strdup_printf("%li", n->timeout/1000);
gchar *n_timestamp_str = g_strdup_printf("%li", n->timestamp / 1000);
char* icon_path = get_path_from_icon_name(icon);
safe_setenv("DUNST_APP_NAME", appname);
safe_setenv("DUNST_SUMMARY", summary);
safe_setenv("DUNST_BODY", body);
safe_setenv("DUNST_ICON_PATH", icon_path);
safe_setenv("DUNST_URGENCY", urgency);
safe_setenv("DUNST_ID", n_id_str);
safe_setenv("DUNST_PROGRESS", n_progress_str);
safe_setenv("DUNST_CATEGORY", n->category);
safe_setenv("DUNST_STACK_TAG", n->stack_tag);
safe_setenv("DUNST_URLS", n->urls);
safe_setenv("DUNST_TIMEOUT", n_timeout_str);
safe_setenv("DUNST_TIMESTAMP", n_timestamp_str);
safe_setenv("DUNST_STACK_TAG", n->stack_tag);
execlp(script,
script,
appname,
summary,
body,
icon,
urgency,
(char *)NULL);
LOG_W("Unable to run script %s: %s", n->scripts[i], strerror(errno));
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);
}
}
@ -174,7 +110,7 @@ void notification_run_script(struct notification *n)
/*
* Helper function to convert an urgency to a string
*/
const char *notification_urgency_to_string(const enum urgency urgency)
const char *notification_urgency_to_string(enum urgency urgency)
{
switch (urgency) {
case URG_NONE:
@ -190,145 +126,105 @@ const char *notification_urgency_to_string(const enum urgency urgency)
}
}
/* see notification.h */
int notification_cmp(const struct notification *a, const struct notification *b)
/*
* Helper function to compare to given
* notifications.
*/
int notification_cmp(const void *va, const void *vb)
{
if (settings.sort && a->urgency != b->urgency) {
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;
}
}
/* see notification.h */
/*
* Wrapper for notification_cmp to match glib's
* compare functions signature.
*/
int notification_cmp_data(const void *va, const void *vb, void *data)
{
struct notification *a = (struct notification *) va;
struct notification *b = (struct notification *) vb;
return notification_cmp(a, b);
return notification_cmp(va, vb);
}
bool notification_is_duplicate(const struct notification *a, const struct notification *b)
int notification_is_duplicate(const notification *a, const notification *b)
{
return STR_EQ(a->appname, b->appname)
&& STR_EQ(a->summary, b->summary)
&& STR_EQ(a->body, b->body)
&& (settings.icon_position != ICON_OFF ? STR_EQ(a->icon_id, b->icon_id) : 1)
//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;
}
bool notification_is_locked(struct notification *n) {
assert(n);
return g_atomic_int_get(&n->locked) != 0;
}
struct notification* notification_lock(struct notification *n) {
assert(n);
g_atomic_int_set(&n->locked, 1);
notification_ref(n);
return n;
}
struct notification* notification_unlock(struct notification *n) {
assert(n);
g_atomic_int_set(&n->locked, 0);
notification_unref(n);
return n;
}
static void notification_private_free(NotificationPrivate *p)
/*
* Free the actions element
* @a: (nullable): Pointer to #Actions
*/
void actions_free(Actions *a)
{
g_free(p);
}
/* see notification.h */
gint notification_refcount_get(struct notification *n)
{
assert(n->priv->refcount > 0);
return g_atomic_int_get(&n->priv->refcount);
}
/* see notification.h */
void notification_ref(struct notification *n)
{
assert(n->priv->refcount > 0);
g_atomic_int_inc(&n->priv->refcount);
}
/* see notification.h */
void notification_unref(struct notification *n)
{
ASSERT_OR_RET(n,);
assert(n->priv->refcount > 0);
if (!g_atomic_int_dec_and_test(&n->priv->refcount))
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->iconname);
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);
g_free(n->colors.fg);
g_free(n->colors.bg);
g_free(n->colors.highlight);
g_free(n->colors.frame);
g_free(n->stack_tag);
g_free(n->desktop_entry);
g_hash_table_unref(n->actions);
g_free(n->default_action_name);
if (n->icon)
g_object_unref(n->icon);
g_free(n->icon_id);
notification_private_free(n->priv);
if (n->script_count > 0){
g_free(n->scripts);
}
actions_free(n->actions);
rawimage_free(n->raw_icon);
g_free(n);
}
void notification_icon_replace_path(struct notification *n, const char *new_icon)
{
ASSERT_OR_RET(n,);
ASSERT_OR_RET(new_icon,);
ASSERT_OR_RET(n->iconname != new_icon,);
g_free(n->iconname);
n->iconname = g_strdup(new_icon);
g_clear_object(&n->icon);
g_clear_pointer(&n->icon_id, g_free);
n->icon = icon_get_for_name(new_icon, &n->icon_id, draw_get_scale());
}
void notification_icon_replace_data(struct notification *n, GVariant *new_icon)
{
ASSERT_OR_RET(n,);
ASSERT_OR_RET(new_icon,);
g_clear_object(&n->icon);
g_clear_pointer(&n->icon_id, g_free);
n->icon = icon_get_for_data(new_icon, &n->icon_id, draw_get_scale());
}
/* see notification.h */
/*
* 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,
@ -352,127 +248,101 @@ void notification_replace_single_field(char **haystack,
g_free(input);
}
static NotificationPrivate *notification_private_create(void)
char *notification_extract_markup_urls(char **str_ptr)
{
NotificationPrivate *priv = g_malloc0(sizeof(NotificationPrivate));
g_atomic_int_set(&priv->refcount, 1);
char *start, *end, *replace_buf, *str, *urls = NULL, *url, *index_buf;
int linkno = 1;
return priv;
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;
}
/* see notification.h */
struct notification *notification_create(void)
/*
* Create notification struct and initialise everything to NULL,
* this function is guaranteed to return a valid pointer.
*/
notification *notification_create(void)
{
struct notification *n = g_malloc0(sizeof(struct notification));
return g_malloc0(sizeof(notification));
}
n->priv = notification_private_create();
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;
/* Unparameterized default values */
n->first_render = true;
n->markup = settings.markup;
n->format = settings.format;
n->timestamp = time_monotonic_now();
n->urgency = URG_NORM;
n->timeout = -1;
n->transient = false;
n->progress = -1;
n->script_run = false;
n->dbus_valid = false;
n->fullscreen = FS_SHOW;
n->actions = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
n->default_action_name = g_strdup("default");
n->script_count = 0;
return n;
}
/* see notification.h */
void notification_init(struct notification *n)
{
/* default to empty string to avoid further NULL faults */
n->appname = n->appname ? n->appname : g_strdup("unknown");
n->summary = n->summary ? n->summary : g_strdup("");
n->body = n->body ? n->body : g_strdup("");
n->category = n->category ? n->category : g_strdup("");
/* sanitize urgency */
if (n->urgency < URG_MIN)
n->urgency = URG_LOW;
if (n->urgency > URG_MAX)
n->urgency = URG_CRIT;
/* Timeout processing */
if (n->timeout < 0)
n->timeout = settings.timeouts[n->urgency];
/* Icon handling */
if (STR_EMPTY(n->iconname))
g_clear_pointer(&n->iconname, g_free);
if (!n->icon && n->iconname) {
char *icon = g_strdup(n->iconname);
notification_icon_replace_path(n, icon);
g_free(icon);
}
if (!n->icon && !n->iconname)
notification_icon_replace_path(n, settings.icons[n->urgency]);
/* Color hints */
struct notification_colors defcolors;
switch (n->urgency) {
case URG_LOW:
defcolors = settings.colors_low;
break;
case URG_NORM:
defcolors = settings.colors_norm;
break;
case URG_CRIT:
defcolors = settings.colors_crit;
break;
default:
g_error("Unhandled urgency type: %d", n->urgency);
}
if (!n->colors.fg)
n->colors.fg = g_strdup(defcolors.fg);
if (!n->colors.bg)
n->colors.bg = g_strdup(defcolors.bg);
if (!n->colors.highlight)
n->colors.highlight = g_strdup(defcolors.highlight);
if (!n->colors.frame)
n->colors.frame = g_strdup(defcolors.frame);
/* Sanitize misc hints */
if (n->progress < 0)
n->progress = -1;
/* Process rules */
rule_apply_all(n);
if (g_str_has_prefix(n->summary, "DUNST_COMMAND_")) {
char *msg = "DUNST_COMMAND_* has been removed, please switch to dunstctl. See #830 for more details. https://github.com/dunst-project/dunst/pull/830";
LOG_W("%s", msg);
n->body = string_append(n->body, msg, "\n");
if (n->icon != NULL && strlen(n->icon) <= 0) {
g_free(n->icon);
n->icon = NULL;
}
/* UPDATE derived fields */
notification_extract_urls(n);
notification_format_message(n);
}
if (n->raw_icon == NULL && n->icon == NULL) {
n->icon = g_strdup(settings.icons[n->urgency]);
}
static void notification_format_message(struct notification *n)
{
g_clear_pointer(&n->msg, g_free);
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;
substr;
substr = strchr(substr, '%')) {
char pg[16];
@ -491,7 +361,7 @@ static void notification_format_message(struct notification *n)
&n->msg,
&substr,
n->summary,
MARKUP_NO);
n->markup);
break;
case 'b':
notification_replace_single_field(
@ -501,7 +371,7 @@ static void notification_format_message(struct notification *n)
n->markup);
break;
case 'I':
icon_tmp = g_strdup(n->iconname);
icon_tmp = g_strdup(n->icon);
notification_replace_single_field(
&n->msg,
&substr,
@ -513,7 +383,7 @@ static void notification_format_message(struct notification *n)
notification_replace_single_field(
&n->msg,
&substr,
n->iconname ? n->iconname : "",
n->icon ? n->icon : "",
MARKUP_NO);
break;
case 'p':
@ -544,12 +414,12 @@ static void notification_format_message(struct notification *n)
MARKUP_NO);
break;
case '\0':
LOG_W("format_string has trailing %% character. "
"To escape it use %%%%.");
substr++;
fprintf(stderr, "WARNING: format_string has trailing %% character."
"To escape it use %%%%.");
break;
default:
LOG_W("format_string %%%c is unknown.", substr[1]);
fprintf(stderr, "WARNING: format_string %%%c"
" is unknown\n", substr[1]);
// shift substr pointer forward,
// as we can't interpret the format string
substr++;
@ -560,41 +430,73 @@ static void notification_format_message(struct notification *n)
n->msg = g_strchomp(n->msg);
/* truncate overlong messages */
if (strnlen(n->msg, DUNST_NOTIF_MAX_CHARS + 1) > DUNST_NOTIF_MAX_CHARS) {
char * buffer = g_strndup(n->msg, DUNST_NOTIF_MAX_CHARS);
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);
}
static void notification_extract_urls(struct notification *n)
void notification_update_text_to_render(notification *n)
{
g_clear_pointer(&n->urls, g_free);
char *urls_in = string_append(g_strdup(n->summary), n->body, " ");
char *urls_a = NULL;
char *urls_img = NULL;
markup_strip_a(&urls_in, &urls_a);
markup_strip_img(&urls_in, &urls_img);
// remove links and images first to not confuse
// plain urls extraction
char *urls_text = extract_urls(urls_in);
n->urls = string_append(n->urls, urls_a, "\n");
n->urls = string_append(n->urls, urls_img, "\n");
n->urls = string_append(n->urls, urls_text, "\n");
g_free(urls_in);
g_free(urls_a);
g_free(urls_img);
g_free(urls_text);
}
void notification_update_text_to_render(struct notification *n)
{
g_clear_pointer(&n->text_to_render, g_free);
g_free(n->text_to_render);
n->text_to_render = NULL;
char *buf = NULL;
@ -602,14 +504,14 @@ void notification_update_text_to_render(struct notification *n)
/* print dup_count and msg */
if ((n->dup_count > 0 && !settings.hide_duplicate_count)
&& (g_hash_table_size(n->actions) || n->urls) && settings.show_indicators) {
&& (n->actions || n->urls) && settings.show_indicators) {
buf = g_strdup_printf("(%d%s%s) %s",
n->dup_count,
g_hash_table_size(n->actions) ? "A" : "",
n->actions ? "A" : "",
n->urls ? "U" : "", msg);
} else if ((g_hash_table_size(n->actions) || n->urls) && settings.show_indicators) {
} else if ((n->actions || n->urls) && settings.show_indicators) {
buf = g_strdup_printf("(%s%s) %s",
g_hash_table_size(n->actions) ? "A" : "",
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);
@ -619,7 +521,7 @@ void notification_update_text_to_render(struct notification *n)
/* print age */
gint64 hours, minutes, seconds;
gint64 t_delta = time_monotonic_now() - n->timestamp;
gint64 t_delta = g_get_monotonic_time() - n->timestamp;
if (settings.show_age_threshold >= 0
&& t_delta >= settings.show_age_threshold) {
@ -647,48 +549,32 @@ void notification_update_text_to_render(struct notification *n)
n->text_to_render = buf;
}
/* see notification.h */
void notification_do_action(struct notification *n)
/*
* 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)
{
assert(n->default_action_name);
if (g_hash_table_size(n->actions)) {
if (g_hash_table_contains(n->actions, n->default_action_name)) {
signal_action_invoked(n, n->default_action_name);
if (n->actions) {
if (n->actions->count == 2) {
action_invoked(n, n->actions->actions[0]);
return;
}
if (strcmp(n->default_action_name, "default") == 0 && g_hash_table_size(n->actions) == 1) {
GList *keys = g_hash_table_get_keys(n->actions);
signal_action_invoked(n, keys->data);
g_list_free(keys);
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;
}
}
notification_open_context_menu(n);
context_menu();
} else if (n->urls) {
if (strstr(n->urls, "\n") == NULL)
open_browser(n->urls);
else
context_menu();
}
}
/* see notification.h */
void notification_open_url(struct notification *n)
{
if (strstr(n->urls, "\n"))
notification_open_context_menu(n);
else
open_browser(n->urls);
}
/* see notification.h */
void notification_open_context_menu(struct notification *n)
{
GList *notifications = NULL;
notifications = g_list_append(notifications, n);
notification_lock(n);
context_menu_for(notifications);
}
void notification_invalidate_actions(struct notification *n) {
g_hash_table_remove_all(n->actions);
}
/* vim: set ft=c tabstop=8 shiftwidth=8 expandtab textwidth=0: */
/* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */

View File

@ -2,237 +2,86 @@
#ifndef DUNST_NOTIFICATION_H
#define DUNST_NOTIFICATION_H
#include <gdk-pixbuf/gdk-pixbuf.h>
#include <glib.h>
#include <stdbool.h>
#include "markup.h"
#include "settings.h"
#define DUNST_NOTIF_MAX_CHARS 50000
#define DUNST_NOTIF_MAX_CHARS 5000
enum behavior_fullscreen {
FS_NULL, //!< Invalid value
FS_DELAY, //!< Delay the notification until leaving fullscreen mode
FS_PUSHBACK, //!< When entering fullscreen mode, push the notification back to waiting
FS_SHOW, //!< Show the message when in fullscreen mode
};
/// Representing the urgencies according to the notification spec
enum urgency {
URG_NONE = -1, /**< Urgency not set (invalid) */
URG_MIN = 0, /**< Minimum value, useful for boundary checking */
URG_LOW = 0, /**< Low urgency */
URG_NORM = 1, /**< Normal urgency */
URG_CRIT = 2, /**< Critical urgency */
URG_MAX = 2, /**< Maximum value, useful for boundary checking */
URG_NONE = -1,
URG_MIN = 0,
URG_LOW = 0,
URG_NORM = 1,
URG_CRIT = 2,
URG_MAX = 2,
};
typedef struct _notification_private NotificationPrivate;
typedef struct _raw_image {
int width;
int height;
int rowstride;
int has_alpha;
int bits_per_sample;
int n_channels;
unsigned char *data;
} RawImage;
struct notification_colors {
char *frame;
char *bg;
char *fg;
char *highlight;
};
struct notification {
NotificationPrivate *priv;
int id;
char *dbus_client;
bool dbus_valid;
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 *desktop_entry; /**< The desktop entry hint sent via every GApplication */
enum urgency urgency;
GdkPixbuf *icon; /**< The raw cached icon data used to draw */
char *icon_id; /**< plain icon information, which acts as the pixbuf's id, which is saved in .icon
May be a hash for a raw icon or a name/path for a regular app icon. */
char *iconname; /**< plain icon information (may be a path or just a name)
Use this to compare the icon name with rules.*/
gint64 start; /**< begin of current display (in milliseconds) */
gint64 timestamp; /**< arrival time (in milliseconds) */
gint64 timeout; /**< time to display (in milliseconds) */
int locked; /**< If non-zero the notification is locked **/
GHashTable *actions;
char *default_action_name; /**< The name of the action to be invoked on do_action */
enum markup_mode markup;
char *text_to_render;
const char *format;
const char **scripts;
int script_count;
struct notification_colors colors;
char *stack_tag; /**< stack notifications by tag */
/* Hints */
bool transient; /**< timeout albeit user is idle */
int progress; /**< percentage (-1: undefined) */
int history_ignore; /**< push to history or free directly */
int skip_display; /**< insert notification into history, skipping initial waiting and display */
/* internal */
bool redisplayed; /**< has been displayed before? */
bool first_render; /**< markup has been rendered before? */
int dup_count; /**< amount of duplicate notifications stacked onto this */
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;
enum behavior_fullscreen fullscreen; //!< The instruction what to do with it, when desktop enters fullscreen
bool script_run; /**< Has the script been executed already? */
guint8 marked_for_closure;
const char *color_strings[3];
bool first_render;
bool transient;
/* derived fields */
char *msg; /**< formatted message */
char *text_to_render; /**< formatted message (with age and action indicators) */
char *urls; /**< urllist delimited by '\\n' */
};
int progress; /* percentage (-1: undefined) */
int history_ignore;
const char *script;
char *urls;
Actions *actions;
} notification;
/**
* Create notification struct and initialise all fields with either
* - the default (if it's not needed to be freed later)
* - its undefined representation (NULL, -1)
*
* The reference counter is set to 1.
*
* This function is guaranteed to return a valid pointer.
* @returns The generated notification
*/
struct notification *notification_create(void);
/**
* Retrieve the current reference count of the notification
*/
gint notification_refcount_get(struct notification *n);
/**
* Increase the reference counter of the notification.
*/
void notification_ref(struct notification *n);
/**
* Sanitize values of notification, apply all matching rules
* and generate derived fields.
*
* @param n: the notification to sanitize
*/
void notification_init(struct notification *n);
/**
* Decrease the reference counter of the notification.
*
* If the reference count drops to 0, the object gets freed.
*/
void notification_unref(struct notification *n);
/**
* Helper function to compare two given notifications.
*/
int notification_cmp(const struct notification *a, const struct notification *b);
/**
* Wrapper for notification_cmp to match glib's
* compare functions signature.
*/
int notification_cmp_data(const void *va, const void *vb, void *data);
bool notification_is_duplicate(const struct notification *a, const struct notification *b);
bool notification_is_locked(struct notification *n);
struct notification *notification_lock(struct notification *n);
struct notification *notification_unlock(struct notification *n);
/**Replace the current notification's icon with the icon specified by path.
*
* Removes the reference for the previous icon automatically and will also free the
* iconname field. So passing n->iconname as new_icon is invalid.
*
* @param n the notification to replace the icon
* @param new_icon The path of the new icon. May be an absolute path or an icon name.
*/
void notification_icon_replace_path(struct notification *n, const char *new_icon);
/**Replace the current notification's icon with the raw icon given in the GVariant.
*
* Removes the reference for the previous icon automatically.
*
* @param n the notification to replace the icon
* @param new_icon The icon's data. Has to be in the format of the notification spec.
*/
void notification_icon_replace_data(struct notification *n, GVariant *new_icon);
/**
* Run the script associated with the
* given notification.
*
* If the script of the notification has been executed already and
* settings.always_run_script is not set, do nothing.
*/
void notification_run_script(struct notification *n);
/**
* print a human readable representation
* of the given notification to stdout.
*/
void notification_print(const struct notification *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.
*/
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);
void notification_update_text_to_render(struct notification *n);
/**
* If the notification has an action named n->default_action_name or there is only one
* action and n->default_action_name is set to "default", invoke it. If there is no
* such action, open the context menu if threre are other actions. Otherwise, do nothing.
*/
void notification_do_action(struct notification *n);
/**
* If the notification has exactly one url, invoke it. If there are multiple,
* open the context menu. If there are no urls, do nothing.
*/
void notification_open_url(struct notification *n);
/**
* Open the context menu for the notification.
*
* Convenience function that creates the GList and passes it to context_menu_for().
*/
void notification_open_context_menu(struct notification *n);
/**
* Remove all client action data from the notification.
*
* This should be called after a notification is closed to avoid showing
* actions that will not work anymore since the client has stopped listening
* for them.
*/
void notification_invalidate_actions(struct notification *n);
const char *notification_urgency_to_string(const enum urgency urgency);
/**
* Return the string representation for fullscreen behavior
*
* @param in the #behavior_fullscreen enum value to represent
* @return the string representation for `in`
*/
const char *enum_to_string_fullscreen(enum behavior_fullscreen in);
const char *notification_urgency_to_string(enum urgency urgency);
#endif
/* vim: set ft=c tabstop=8 shiftwidth=8 expandtab textwidth=0: */
/* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */

View File

@ -8,29 +8,27 @@
#include <stdlib.h>
#include <string.h>
#include "dunst.h"
#include "log.h"
#include "utils.h"
#include "settings.h"
struct entry {
typedef struct _entry_t {
char *key;
char *value;
};
} entry_t;
struct section {
typedef struct _section_t {
char *name;
int entry_count;
struct entry *entries;
};
entry_t *entries;
} section_t;
static int section_count = 0;
static struct section *sections;
static section_t *sections;
static struct section *new_section(const char *name);
static struct section *get_section(const char *name);
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;
@ -40,180 +38,16 @@ static void cmdline_usage_append(const char *key, const char *type, const char *
static int cmdline_find_option(const char *key);
#define STRING_PARSE_RET(string, value) if (STR_EQ(s, string)) { *ret = value; return true; }
bool string_parse_alignment(const char *s, enum alignment *ret)
{
ASSERT_OR_RET(STR_FULL(s), false);
ASSERT_OR_RET(ret, false);
STRING_PARSE_RET("left", ALIGN_LEFT);
STRING_PARSE_RET("center", ALIGN_CENTER);
STRING_PARSE_RET("right", ALIGN_RIGHT);
return false;
}
bool string_parse_ellipsize(const char *s, enum ellipsize *ret)
{
ASSERT_OR_RET(STR_FULL(s), false);
ASSERT_OR_RET(ret, false);
STRING_PARSE_RET("start", ELLIPSE_START);
STRING_PARSE_RET("middle", ELLIPSE_MIDDLE);
STRING_PARSE_RET("end", ELLIPSE_END);
return false;
}
bool string_parse_follow_mode(const char *s, enum follow_mode *ret)
{
ASSERT_OR_RET(STR_FULL(s), false);
ASSERT_OR_RET(ret, false);
STRING_PARSE_RET("mouse", FOLLOW_MOUSE);
STRING_PARSE_RET("keyboard", FOLLOW_KEYBOARD);
STRING_PARSE_RET("none", FOLLOW_NONE);
return false;
}
bool string_parse_fullscreen(const char *s, enum behavior_fullscreen *ret)
{
ASSERT_OR_RET(STR_FULL(s), false);
ASSERT_OR_RET(ret, false);
STRING_PARSE_RET("show", FS_SHOW);
STRING_PARSE_RET("delay", FS_DELAY);
STRING_PARSE_RET("pushback", FS_PUSHBACK);
return false;
}
bool string_parse_icon_position(const char *s, enum icon_position *ret)
{
ASSERT_OR_RET(STR_FULL(s), false);
ASSERT_OR_RET(ret, false);
STRING_PARSE_RET("left", ICON_LEFT);
STRING_PARSE_RET("right", ICON_RIGHT);
STRING_PARSE_RET("off", ICON_OFF);
return false;
}
bool string_parse_vertical_alignment(const char *s, enum vertical_alignment *ret)
{
ASSERT_OR_RET(STR_FULL(s), false);
ASSERT_OR_RET(ret, false);
STRING_PARSE_RET("top", VERTICAL_TOP);
STRING_PARSE_RET("center", VERTICAL_CENTER);
STRING_PARSE_RET("bottom", VERTICAL_BOTTOM);
return false;
}
bool string_parse_markup_mode(const char *s, enum markup_mode *ret)
{
ASSERT_OR_RET(STR_FULL(s), false);
ASSERT_OR_RET(ret, false);
STRING_PARSE_RET("strip", MARKUP_STRIP);
STRING_PARSE_RET("no", MARKUP_NO);
STRING_PARSE_RET("full", MARKUP_FULL);
STRING_PARSE_RET("yes", MARKUP_FULL);
return false;
}
bool string_parse_mouse_action(const char *s, enum mouse_action *ret)
{
ASSERT_OR_RET(STR_FULL(s), false);
ASSERT_OR_RET(ret, false);
STRING_PARSE_RET("none", MOUSE_NONE);
STRING_PARSE_RET("do_action", MOUSE_DO_ACTION);
STRING_PARSE_RET("close_current", MOUSE_CLOSE_CURRENT);
STRING_PARSE_RET("close_all", MOUSE_CLOSE_ALL);
STRING_PARSE_RET("context", MOUSE_CONTEXT);
STRING_PARSE_RET("context_all", MOUSE_CONTEXT_ALL);
STRING_PARSE_RET("open_url", MOUSE_OPEN_URL);
return false;
}
bool string_parse_mouse_action_list(char **s, enum mouse_action **ret)
{
ASSERT_OR_RET(s, false);
ASSERT_OR_RET(ret, false);
int len = 0;
while (s[len])
len++;
*ret = g_malloc_n((len + 1), sizeof(enum mouse_action));
for (int i = 0; i < len; i++) {
if (!string_parse_mouse_action(s[i], *ret + i)) {
LOG_W("Unknown mouse action value: '%s'", s[i]);
g_free(*ret);
return false;
}
}
(*ret)[len] = -1; // sentinel end value
return true;
}
bool string_parse_sepcolor(const char *s, struct separator_color_data *ret)
{
ASSERT_OR_RET(STR_FULL(s), false);
ASSERT_OR_RET(ret, false);
STRING_PARSE_RET("auto", (struct separator_color_data){.type = SEP_AUTO});
STRING_PARSE_RET("foreground", (struct separator_color_data){.type = SEP_FOREGROUND});
STRING_PARSE_RET("frame", (struct separator_color_data){.type = SEP_FRAME});
ret->type = SEP_CUSTOM;
ret->sep_color = g_strdup(s);
return true;
}
bool string_parse_urgency(const char *s, enum urgency *ret)
{
ASSERT_OR_RET(STR_FULL(s), false);
ASSERT_OR_RET(ret, false);
STRING_PARSE_RET("low", URG_LOW);
STRING_PARSE_RET("normal", URG_NORM);
STRING_PARSE_RET("critical", URG_CRIT);
return false;
}
bool string_parse_layer(const char *s, enum zwlr_layer_shell_v1_layer *ret)
{
ASSERT_OR_RET(STR_FULL(s), false);
ASSERT_OR_RET(ret, false);
STRING_PARSE_RET("bottom", ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM);
STRING_PARSE_RET("top", ZWLR_LAYER_SHELL_V1_LAYER_TOP);
STRING_PARSE_RET("overlay", ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY);
return false;
}
struct section *new_section(const char *name)
section_t *new_section(const char *name)
{
for (int i = 0; i < section_count; i++) {
if (STR_EQ(name, sections[i].name)) {
DIE("Duplicated section in dunstrc detected.");
if (!strcmp(name, sections[i].name)) {
die("Duplicated section in dunstrc detected.\n", -1);
}
}
section_count++;
sections = g_realloc(sections, sizeof(struct section) * 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;
@ -230,14 +64,15 @@ void free_ini(void)
g_free(sections[i].entries);
g_free(sections[i].name);
}
g_clear_pointer(&sections, g_free);
g_free(sections);
section_count = 0;
sections = NULL;
}
struct section *get_section(const char *name)
section_t *get_section(const char *name)
{
for (int i = 0; i < section_count; i++) {
if (STR_EQ(sections[i].name, name))
if (strcmp(sections[i].name, name) == 0)
return &sections[i];
}
@ -246,24 +81,27 @@ struct section *get_section(const char *name)
void add_entry(const char *section_name, const char *key, const char *value)
{
struct section *s = get_section(section_name);
if (!s)
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(struct entry) * len);
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 = string_strip_quotes(value);
s->entries[s->entry_count - 1].value = clean_value(value);
}
const char *get_value(const char *section, const char *key)
{
struct section *s = get_section(section);
ASSERT_OR_RET(s, NULL);
section_t *s = get_section(section);
if (!s) {
return NULL;
}
for (int i = 0; i < s->entry_count; i++) {
if (STR_EQ(s->entries[i].key, key)) {
if (strcmp(s->entries[i].key, key) == 0) {
return s->entries[i].value;
}
}
@ -296,31 +134,22 @@ gint64 ini_get_time(const char *section, const char *key, gint64 def)
return val;
}
char **ini_get_list(const char *section, const char *key, const char *def)
{
const char *value = get_value(section, key);
if (value)
return string_to_array(value);
else
return string_to_array(def);
}
int ini_get_int(const char *section, const char *key, int def)
{
const char *value = get_value(section, key);
if (value)
return atoi(value);
else
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)
return atof(value);
else
if (value == NULL)
return def;
else
return atof(value);
}
bool ini_is_set(const char *ini_section, const char *ini_key)
@ -330,11 +159,15 @@ bool ini_is_set(const char *ini_section, const char *ini_key)
const char *next_section(const char *section)
{
ASSERT_OR_RET(section_count > 0, NULL);
ASSERT_OR_RET(section, sections[0].name);
if (section_count == 0)
return NULL;
if (section == NULL) {
return sections[0].name;
}
for (int i = 0; i < section_count; i++) {
if (STR_EQ(section, sections[i].name)) {
if (strcmp(section, sections[i].name) == 0) {
if (i + 1 >= section_count)
return NULL;
else
@ -347,7 +180,9 @@ const char *next_section(const char *section)
int ini_get_bool(const char *section, const char *key, int def)
{
const char *value = get_value(section, key);
if (value) {
if (value == NULL)
return def;
else {
switch (value[0]) {
case 'y':
case 'Y':
@ -364,14 +199,28 @@ int ini_get_bool(const char *section, const char *key, int def)
default:
return def;
}
} else {
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)
{
ASSERT_OR_RET(fp, 1);
if (!fp)
return 1;
char *line = NULL;
size_t line_len = 0;
@ -383,13 +232,16 @@ int load_ini_file(FILE *fp)
char *start = g_strstrip(line);
if (*start == ';' || *start == '#' || STR_EMPTY(start))
if (*start == ';' || *start == '#' || strlen(start) == 0)
continue;
if (*start == '[') {
char *end = strchr(start + 1, ']');
if (!end) {
LOG_W("Invalid config file at line %d: Missing ']'.", line_num);
fprintf(stderr,
"Warning: invalid config file at line %d\n",
line_num);
fprintf(stderr, "Missing ']'\n");
continue;
}
@ -403,7 +255,10 @@ int load_ini_file(FILE *fp)
char *equal = strchr(start + 1, '=');
if (!equal) {
LOG_W("Invalid config file at line %d: Missing '='.", line_num);
fprintf(stderr,
"Warning: invalid config file at line %d\n",
line_num);
fprintf(stderr, "Missing '='\n");
continue;
}
@ -412,25 +267,27 @@ int load_ini_file(FILE *fp)
char *value = g_strstrip(equal + 1);
char *quote = strchr(value, '"');
char *value_end = NULL;
if (quote) {
value_end = strchr(quote + 1, '"');
if (!value_end) {
LOG_W("Invalid config file at line %d: Missing '\"'.", line_num);
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 {
value_end = value;
char *comment = strpbrk(value, "#;");
if (comment)
*comment = '\0';
}
char *comment = strpbrk(value_end, "#;");
if (comment)
*comment = '\0';
value = g_strstrip(value);
if (!current_section) {
LOG_W("Invalid config file at line %d: Key value pair without a section.", line_num);
fprintf(stderr,
"Warning: invalid config file at line %d\n",
line_num);
fprintf(stderr, "Key value pair without a section\n");
continue;
}
@ -449,8 +306,9 @@ void cmdline_load(int argc, char *argv[])
int cmdline_find_option(const char *key)
{
ASSERT_OR_RET(key, -1);
if (!key) {
return -1;
}
char *key1 = g_strdup(key);
char *key2 = strchr(key1, '/');
@ -461,7 +319,7 @@ int cmdline_find_option(const char *key)
/* look for first key */
for (int i = 0; i < cmdline_argc; i++) {
if (STR_EQ(key1, cmdline_argv[i])) {
if (strcmp(key1, cmdline_argv[i]) == 0) {
g_free(key1);
return i;
}
@ -470,7 +328,7 @@ int cmdline_find_option(const char *key)
/* look for second key if one was specified */
if (key2) {
for (int i = 0; i < cmdline_argc; i++) {
if (STR_EQ(key2, cmdline_argv[i])) {
if (strcmp(key2, cmdline_argv[i]) == 0) {
g_free(key1);
return i;
}
@ -490,7 +348,8 @@ static const char *cmdline_get_value(const char *key)
if (idx + 1 >= cmdline_argc) {
/* the argument is missing */
LOG_W("%s: Missing argument. Ignoring.", key);
fprintf(stderr, "Warning: %s, missing argument. Ignoring\n",
key);
return NULL;
}
return cmdline_argv[idx + 1];
@ -503,10 +362,10 @@ char *cmdline_get_string(const char *key, const char *def, const char *descripti
if (str)
return g_strdup(str);
if (def)
return g_strdup(def);
else
if (def == NULL)
return NULL;
else
return g_strdup(def);
}
char *cmdline_get_path(const char *key, const char *def, const char *description)
@ -520,17 +379,6 @@ char *cmdline_get_path(const char *key, const char *def, const char *description
return string_to_path(g_strdup(def));
}
char **cmdline_get_list(const char *key, const char *def, const char *description)
{
cmdline_usage_append(key, "list", description);
const char *str = cmdline_get_value(key);
if (str)
return string_to_array(str);
else
return string_to_array(def);
}
gint64 cmdline_get_time(const char *key, gint64 def, const char *description)
{
cmdline_usage_append(key, "time", description);
@ -549,10 +397,10 @@ 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)
return atoi(str);
else
if (str == NULL)
return def;
else
return atoi(str);
}
double cmdline_get_double(const char *key, double def, const char *description)
@ -560,10 +408,10 @@ 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)
return atof(str);
else
if (str == NULL)
return def;
else
return atof(str);
}
int cmdline_get_bool(const char *key, int def, const char *description)
@ -630,23 +478,6 @@ gint64 option_get_time(const char *ini_section,
return cmdline_get_time(cmdline_key, ini_val, description);
}
char **option_get_list(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_list(cmdline_key, NULL, description);
if (val)
return val;
else
return ini_get_list(ini_section, ini_key, def);
}
int option_get_int(const char *ini_section,
const char *ini_key,
const char *cmdline_key,
@ -704,7 +535,7 @@ int option_get_bool(const char *ini_section,
void cmdline_usage_append(const char *key, const char *type, const char *description)
{
char *key_type;
if (STR_FULL(type))
if (type && strlen(type) > 0)
key_type = g_strdup_printf("%s (%s)", key, type);
else
key_type = g_strdup(key);
@ -731,4 +562,4 @@ const char *cmdline_create_usage(void)
return usage_str;
}
/* vim: set ft=c tabstop=8 shiftwidth=8 expandtab textwidth=0: */
/* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */

View File

@ -6,27 +6,10 @@
#include <stdbool.h>
#include <stdio.h>
#include "dunst.h"
#include "settings.h"
bool string_parse_alignment(const char *s, enum alignment *ret);
bool string_parse_ellipsize(const char *s, enum ellipsize *ret);
bool string_parse_follow_mode(const char *s, enum follow_mode *ret);
bool string_parse_fullscreen(const char *s, enum behavior_fullscreen *ret);
bool string_parse_icon_position(const char *s, enum icon_position *ret);
bool string_parse_vertical_alignment(const char *s, enum vertical_alignment *ret);
bool string_parse_markup_mode(const char *s, enum markup_mode *ret);
bool string_parse_mouse_action(const char *s, enum mouse_action *ret);
bool string_parse_mouse_action_list(char **s, enum mouse_action **ret);
bool string_parse_sepcolor(const char *s, struct separator_color_data *ret);
bool string_parse_urgency(const char *s, enum urgency *ret);
bool string_parse_layer(const char *s, enum zwlr_layer_shell_v1_layer *ret);
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);
char **ini_get_list(const char *section, const char *key, const char *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);
@ -37,7 +20,6 @@ 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);
char **cmdline_get_list(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);
@ -59,11 +41,6 @@ gint64 option_get_time(const char *ini_section,
const char *cmdline_key,
gint64 def,
const char *description);
char **option_get_list(const char *ini_section,
const char *ini_key,
const char *cmdline_key,
const char *def,
const char *description);
int option_get_int(const char *ini_section,
const char *ini_key,
const char *cmdline_key,
@ -87,4 +64,4 @@ int option_get_bool(const char *ini_section,
const char *next_section(const char *section);
#endif
/* vim: set ft=c tabstop=8 shiftwidth=8 expandtab textwidth=0: */
/* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */

View File

@ -1,96 +0,0 @@
#include "output.h"
#include "log.h"
#include "x11/x.h"
#include "x11/screen.h"
#ifdef ENABLE_WAYLAND
#include "wayland/wl.h"
#endif
bool is_running_wayland(void) {
char* wayland_display = getenv("WAYLAND_DISPLAY");
return !(wayland_display == NULL);
}
const struct output output_x11 = {
x_setup,
x_free,
x_win_create,
x_win_destroy,
x_win_show,
x_win_hide,
x_display_surface,
x_win_get_context,
get_active_screen,
x_is_idle,
have_fullscreen_window,
x_get_scale,
};
#ifdef ENABLE_WAYLAND
const struct output output_wl = {
wl_init,
wl_deinit,
wl_win_create,
wl_win_destroy,
wl_win_show,
wl_win_hide,
wl_display_surface,
wl_win_get_context,
wl_get_active_screen,
wl_is_idle,
wl_have_fullscreen_window,
wl_get_scale,
};
#endif
const struct output* get_x11_output() {
const struct output* output = &output_x11;
if (output->init()) {
return output;
} else {
LOG_E("Couldn't initialize X11 output. Aborting...");
}
}
#ifdef ENABLE_WAYLAND
const struct output* get_wl_output() {
const struct output* output = &output_wl;
if (output->init()) {
return output;
} else {
LOG_W("Couldn't initialize wayland output. Falling back to X11 output.");
output->deinit();
return get_x11_output();
}
}
#endif
const struct output* output_create(bool force_xwayland)
{
#ifdef ENABLE_WAYLAND
if (!force_xwayland && is_running_wayland()) {
LOG_I("Using Wayland output");
return get_wl_output();
} else {
LOG_I("Using X11 output");
return get_x11_output();
}
#else
return get_x11_output();
#endif
}
/* vim: set ft=c tabstop=8 shiftwidth=8 expandtab textwidth=0: */

View File

@ -1,61 +0,0 @@
#ifndef DUNST_OUTPUT_H
#define DUNST_OUTPUT_H
#include <stdbool.h>
#include <glib.h>
#include <cairo.h>
typedef gpointer window;
struct dimensions {
int x;
int y;
int w;
int h;
int corner_radius;
};
struct screen_info {
int id;
int x;
int y;
unsigned int h;
unsigned int mmh;
unsigned int w;
int dpi;
};
struct output {
bool (*init)(void);
void (*deinit)(void);
window (*win_create)(void);
void (*win_destroy)(window);
void (*win_show)(window);
void (*win_hide)(window);
void (*display_surface)(cairo_surface_t *srf, window win, const struct dimensions*);
cairo_t* (*win_get_context)(window);
const struct screen_info* (*get_active_screen)(void);
bool (*is_idle)(void);
bool (*have_fullscreen_window)(void);
int (*get_scale)(void);
};
/**
* return an initialized output, selecting the correct output type from either
* wayland or X11 according to the settings and environment.
* When the wayland output fails to initilize, it falls back to X11 output.
*/
const struct output* output_create(bool force_xwayland);
bool is_running_wayland(void);
#endif
/* vim: set ft=c tabstop=8 shiftwidth=8 expandtab textwidth=0: */

View File

@ -1,18 +1,5 @@
/* copyright 2013 Sascha Kruse and contributors (see LICENSE for licensing information) */
/**
* @file src/queues.c
* @brief All important functions to handle the notification queues for
* history, entrance and currently displayed ones.
*
* Every method requires to have executed queues_init() at the start.
*
* A read only representation of the queue with the current notifications
* can get acquired by calling queues_get_displayed().
*
* When ending the program or resetting the queues, tear down the stack with
* queues_teardown(). (And reinit with queues_init() if needed.)
*/
#include "queues.h"
#include <assert.h>
@ -20,24 +7,20 @@
#include <stdio.h>
#include <string.h>
#include "dunst.h"
#include "log.h"
#include "notification.h"
#include "settings.h"
#include "utils.h"
#include "output.h" // For checking if wayland is active.
/* 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 */
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(struct notification *n);
static bool queues_stack_by_tag(struct notification *n);
static bool queues_stack_duplicate(notification *n);
/* see queues.h */
void queues_init(void)
{
history = g_queue_new();
@ -45,314 +28,222 @@ void queues_init(void)
waiting = g_queue_new();
}
/* see queues.h */
GList *queues_get_displayed(void)
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);
}
/* see queues.h */
const struct notification *queues_get_head_waiting(void)
{
if (waiting->length == 0)
return NULL;
return g_queue_peek_head(waiting);
}
/* see queues.h */
unsigned int queues_length_waiting(void)
unsigned int queues_length_waiting()
{
return waiting->length;
}
/* see queues.h */
unsigned int queues_length_displayed(void)
unsigned int queues_length_displayed()
{
return displayed->length;
}
/* see queues.h */
unsigned int queues_length_history(void)
unsigned int queues_length_history()
{
return history->length;
}
/**
* Swap two given queue elements. The element's data has to be a notification.
*
* @pre { elemA has to be part of queueA. }
* @pre { elemB has to be part of queueB. }
*
* @param queueA The queue, which elemB's data will get inserted
* @param elemA The element, which will get removed from queueA
* @param queueB The queue, which elemA's data will get inserted
* @param elemB The element, which will get removed from queueB
*/
static void queues_swap_notifications(GQueue *queueA,
GList *elemA,
GQueue *queueB,
GList *elemB)
int queues_notification_insert(notification *n, int replaces_id)
{
struct notification *toB = elemA->data;
struct notification *toA = elemB->data;
g_queue_delete_link(queueA, elemA);
g_queue_delete_link(queueB, elemB);
if (toA)
g_queue_insert_sorted(queueA, toA, notification_cmp_data, NULL);
if (toB)
g_queue_insert_sorted(queueB, toB, notification_cmp_data, NULL);
}
/**
* Check if a notification is eligible to get shown.
*
* @param n The notification to check
* @param status The current status of dunst
* @param shown True if the notification is currently displayed
*/
static bool queues_notification_is_ready(const struct notification *n, struct dunst_status status, bool shown)
{
ASSERT_OR_RET(status.running, false);
if (status.fullscreen && shown)
return n && n->fullscreen != FS_PUSHBACK;
else if (status.fullscreen && !shown)
return n && n->fullscreen == FS_SHOW;
else
return true;
}
/**
* Check if a notification has timed out
*
* @param n the notification to check
* @param status the current status of dunst
* @retval true: the notification is timed out
* @retval false: otherwise
*/
static bool queues_notification_is_finished(struct notification *n, struct dunst_status status)
{
assert(n);
if (n->skip_display && !n->redisplayed)
return true;
if (n->timeout == 0) // sticky
return false;
bool is_idle = status.fullscreen ? false : status.idle;
/* don't timeout when user is idle */
if (is_idle && !n->transient) {
n->start = time_monotonic_now();
return false;
}
/* remove old message */
if (time_monotonic_now() - n->start > n->timeout) {
return true;
}
return false;
}
/* see queues.h */
int queues_notification_insert(struct notification *n)
{
/* do not display the message, if the message is empty */
if (STR_EMPTY(n->msg)) {
if (strlen(n->msg) == 0) {
if (settings.always_run_script) {
notification_run_script(n);
}
LOG_M("Skipping notification: '%s' '%s'", n->body, n->summary);
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;
}
bool inserted = false;
if (n->id != 0) {
if (!queues_notification_replace_id(n)) {
// Requested id was not valid, but play nice and assign it anyway
g_queue_insert_sorted(waiting, n, notification_cmp_data, NULL);
}
inserted = true;
} else {
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 (!inserted && STR_FULL(n->stack_tag) && queues_stack_by_tag(n))
inserted = true;
if (!inserted && settings.stack_duplicates && queues_stack_duplicate(n))
inserted = true;
if (!inserted)
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
*
* @retval true: notification got stacked
* @retval false: notification did not get stacked
* Returns %true, if notification got stacked
* Returns %false, if notification did not get stacked
*/
static bool queues_stack_duplicate(struct notification *n)
static bool queues_stack_duplicate(notification *n)
{
GQueue *allqueues[] = { displayed, waiting };
for (int i = 0; i < sizeof(allqueues)/sizeof(GQueue*); i++) {
for (GList *iter = g_queue_peek_head_link(allqueues[i]); iter;
iter = iter->next) {
struct 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;
signal_notification_closed(orig, 1);
if (allqueues[i] == displayed)
n->start = time_monotonic_now();
notification_unref(orig);
return true;
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;
}
/**
* Replaces the first notification of the same stack_tag
*
* @retval true: notification got stacked
* @retval false: notification did not get stacked
*/
static bool queues_stack_by_tag(struct notification *new)
bool queues_notification_replace_id(notification *new)
{
GQueue *allqueues[] = { displayed, waiting };
for (int i = 0; i < sizeof(allqueues)/sizeof(GQueue*); i++) {
for (GList *iter = g_queue_peek_head_link(allqueues[i]); iter;
iter = iter->next) {
struct notification *old = iter->data;
if (STR_FULL(old->stack_tag) && STR_EQ(old->stack_tag, new->stack_tag)) {
iter->data = new;
new->dup_count = old->dup_count;
signal_notification_closed(old, 1);
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;
}
}
if (allqueues[i] == displayed) {
new->start = time_monotonic_now();
notification_run_script(new);
}
notification_unref(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;
}
/* see queues.h */
bool queues_notification_replace_id(struct notification *new)
int queues_notification_close_id(int id, enum reason reason)
{
GQueue *allqueues[] = { displayed, waiting };
for (int i = 0; i < sizeof(allqueues)/sizeof(GQueue*); i++) {
for (GList *iter = g_queue_peek_head_link(allqueues[i]);
iter;
iter = iter->next) {
struct notification *old = iter->data;
if (old->id == new->id) {
iter->data = new;
new->dup_count = old->dup_count;
notification *target = NULL;
if (allqueues[i] == displayed) {
new->start = time_monotonic_now();
notification_run_script(new);
}
notification_unref(old);
return true;
}
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;
}
}
return false;
}
/* see queues.h */
void queues_notification_close_id(int id, enum reason reason)
{
struct notification *target = NULL;
GQueue *allqueues[] = { displayed, waiting };
for (int i = 0; i < sizeof(allqueues)/sizeof(GQueue*); i++) {
for (GList *iter = g_queue_peek_head_link(allqueues[i]); iter;
iter = iter->next) {
struct notification *n = iter->data;
if (n->id == id) {
g_queue_remove(allqueues[i], 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) {
//Don't notify clients if notification was pulled from history
if (!target->redisplayed)
signal_notification_closed(target, reason);
notification_closed(target, reason);
queues_history_push(target);
}
return reason;
}
/* see queues.h */
void queues_notification_close(struct notification *n, enum reason reason)
int queues_notification_close(notification *n, enum reason reason)
{
assert(n != NULL);
queues_notification_close_id(n->id, reason);
return queues_notification_close_id(n->id, reason);
}
/* see queues.h */
void queues_history_pop(void)
{
if (g_queue_is_empty(history))
return;
struct notification *n = g_queue_pop_tail(history);
notification *n = g_queue_pop_tail(history);
n->redisplayed = true;
n->start = 0;
n->timeout = settings.sticky_history ? 0 : n->timeout;
g_queue_insert_sorted(waiting, n, notification_cmp_data, NULL);
g_queue_push_head(waiting, n);
}
/* see queues.h */
void queues_history_push(struct notification *n)
void queues_history_push(notification *n)
{
if (!n->history_ignore) {
if (settings.history_length > 0 && history->length >= settings.history_length) {
struct notification *to_free = g_queue_pop_head(history);
notification_unref(to_free);
notification *to_free = g_queue_pop_head(history);
notification_free(to_free);
}
g_queue_push_tail(history, n);
} else {
notification_unref(n);
notification_free(n);
}
}
/* see queues.h */
void queues_history_push_all(void)
{
while (displayed->length > 0) {
@ -364,125 +255,81 @@ void queues_history_push_all(void)
}
}
/* see queues.h */
void queues_update(struct dunst_status status)
void queues_check_timeouts(bool idle)
{
GList *iter, *nextiter;
/* nothing to do */
if (displayed->length == 0)
return;
/* Move back all notifications, which aren't eligible to get shown anymore
* Will move the notifications back to waiting, if dunst isn't running or fullscreen
* and notifications is not eligible to get shown anymore */
iter = g_queue_peek_head_link(displayed);
GList *iter = g_queue_peek_head_link(displayed);
while (iter) {
struct notification *n = iter->data;
nextiter = iter->next;
notification *n = iter->data;
if (notification_is_locked(n)) {
iter = nextiter;
/*
* 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;
}
if (n->marked_for_closure) {
queues_notification_close(n, n->marked_for_closure);
n->marked_for_closure = 0;
iter = nextiter;
/* skip hidden and sticky messages */
if (n->start == 0 || n->timeout == 0) {
continue;
}
if (queues_notification_is_finished(n, status)){
/* remove old message */
if (g_get_monotonic_time() - n->start > n->timeout) {
queues_notification_close(n, REASON_TIME);
iter = nextiter;
continue;
}
if (!queues_notification_is_ready(n, status, true)) {
g_queue_delete_link(displayed, iter);
g_queue_insert_sorted(waiting, n, notification_cmp_data, NULL);
iter = nextiter;
continue;
}
iter = nextiter;
}
int cur_displayed_limit;
if (settings.geometry.h == 0)
cur_displayed_limit = INT_MAX;
else if ( settings.indicate_hidden
&& settings.geometry.h > 1
&& displayed->length + waiting->length > settings.geometry.h)
cur_displayed_limit = settings.geometry.h-1;
else
cur_displayed_limit = settings.geometry.h;
/* move notifications from queue to displayed */
iter = g_queue_peek_head_link(waiting);
while (displayed->length < cur_displayed_limit && iter) {
struct notification *n = iter->data;
nextiter = iter->next;
ASSERT_OR_RET(n,);
if (!queues_notification_is_ready(n, status, false)) {
iter = nextiter;
continue;
}
n->start = time_monotonic_now();
notification_run_script(n);
if (n->skip_display && !n->redisplayed) {
queues_notification_close(n, REASON_USER);
} else {
g_queue_delete_link(waiting, iter);
g_queue_insert_sorted(displayed, n, notification_cmp_data, NULL);
}
iter = nextiter;
}
/* if necessary, push the overhanging notifications from displayed to waiting again */
while (displayed->length > cur_displayed_limit) {
struct notification *n = g_queue_pop_tail(displayed);
g_queue_insert_sorted(waiting, n, notification_cmp_data, NULL); //TODO: actually it should be on the head if unsorted
}
/* If displayed is actually full, let the more important notifications
* from waiting seep into displayed.
*/
if (settings.sort && displayed->length == cur_displayed_limit) {
GList *i_waiting, *i_displayed;
while ( (i_waiting = g_queue_peek_head_link(waiting))
&& (i_displayed = g_queue_peek_tail_link(displayed))) {
while (i_waiting && ! queues_notification_is_ready(i_waiting->data, status, false)) {
i_waiting = i_waiting->prev;
}
if (i_waiting && notification_cmp(i_displayed->data, i_waiting->data) > 0) {
struct notification *todisp = i_waiting->data;
todisp->start = time_monotonic_now();
notification_run_script(todisp);
queues_swap_notifications(displayed, i_displayed, waiting, i_waiting);
} else {
break;
}
}
}
}
/* see queues.h */
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) {
struct notification *n = iter->data;
notification *n = iter->data;
gint64 ttl = n->timeout - (time - n->start);
if (n->timeout > 0) {
@ -496,59 +343,42 @@ gint64 queues_get_next_datachange(gint64 time)
if (settings.show_age_threshold >= 0) {
gint64 age = time - n->timestamp;
// sleep exactly until the next shift of the second happens
if (age > settings.show_age_threshold - S2US(1))
sleep = MIN(sleep, (S2US(1) - (age % S2US(1))));
else
sleep = MIN(sleep, settings.show_age_threshold - age);
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;
}
/* see queues.h */
struct notification* queues_get_by_id(int id)
void queues_pause_on(void)
{
assert(id > 0);
GQueue *recqueues[] = { displayed, waiting, history };
for (int i = 0; i < sizeof(recqueues)/sizeof(GQueue*); i++) {
for (GList *iter = g_queue_peek_head_link(recqueues[i]); iter;
iter = iter->next) {
struct notification *cur = iter->data;
if (cur->id == id)
return cur;
}
}
return NULL;
pause_displayed = true;
}
void queues_pause_off(void)
{
pause_displayed = false;
}
bool queues_pause_status(void)
{
return pause_displayed;
}
/**
* Helper function for queues_teardown() to free a single notification
*
* @param data The notification to free
*/
static void teardown_notification(gpointer data)
{
struct notification *n = data;
notification_unref(n);
notification *n = data;
notification_free(n);
}
/* see queues.h */
void queues_teardown(void)
void teardown_queues(void)
{
g_queue_free_full(history, teardown_notification);
history = NULL;
g_queue_free_full(displayed, teardown_notification);
displayed = NULL;
g_queue_free_full(waiting, teardown_notification);
waiting = NULL;
}
/* vim: set ft=c tabstop=8 shiftwidth=8 expandtab textwidth=0: */
/* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */

View File

@ -1,167 +1,133 @@
/* copyright 2013 Sascha Kruse and contributors (see LICENSE for licensing information) */
/**
* @file src/queues.h
*/
#ifndef DUNST_QUEUE_H
#define DUNST_QUEUE_H
#include "dbus.h"
#include "dunst.h"
#include "notification.h"
/**
* Initialise necessary queues
*
* @pre Do not call consecutively to avoid memory leaks
* or assure to have queues_teardown() executed before
/*
* Initialise neccessary queues
*/
void queues_init(void);
/**
* Receive the current list of displayed notifications
*
* @return read only list of notifications
/*
* Set maximum notification count to display
* and store in displayed queue
*/
GList *queues_get_displayed(void);
void queues_displayed_limit(unsigned int limit);
/**
* Get the highest notification in line
*
* @returns the first notification in waiting
* @retval NULL: waiting is empty
/*
* Return read only list of notifications
*/
const struct notification *queues_get_head_waiting(void);
const GList *queues_get_displayed();
/**
/*
* Returns the current amount of notifications,
* which are waiting to get displayed
* which are shown, waiting or already in history
*/
unsigned int queues_length_waiting(void);
unsigned int queues_length_waiting();
unsigned int queues_length_displayed();
unsigned int queues_length_history();
/**
* Returns the current amount of notifications,
* which are shown in the UI
*/
unsigned int queues_length_displayed(void);
/**
* Returns the current amount of notifications,
* which are already in history
*/
unsigned int queues_length_history(void);
/**
/*
* Insert a fully initialized notification into queues
*
* Respects stack_duplicates, and notification replacement
*
* @param n the notification to insert
* If replaces_id != 0, n replaces notification with id replaces_id
* If replaces_id == 0, n gets occupies a new position
*
* - If n->id != 0, n replaces notification n with id n->id
* - If n->id == 0, n gets a new id assigned
*
* @returns The new value of `n->id`
* @retval 0: the notification was dismissed and freed
* Returns the assigned notification id
* If returned id == 0, the message was dismissed
*/
int queues_notification_insert(struct notification *n);
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.
*
* @param new replacement for the old notification
*
* @retval true: a matching notification has been found and is replaced
* @retval false: otherwise
* Returns true, if a matching notification has been found
* and is replaced. Else false.
*/
bool queues_notification_replace_id(struct notification *new);
bool queues_notification_replace_id(notification *new);
/**
/*
* Close the notification that has n->id == id
*
* Sends a signal and pushes the selected notification automatically to history.
* Sends a signal and pushes it automatically to history.
*
* @param id The id of the notification to close
* @param reason The #reason to close
*
* @post Call wake_up() to synchronize the queues with the UI
* (which closes the notification on screen)
* After closing, call wake_up to synchronize the queues with the UI
* (which closes the notification on screen)
*/
void queues_notification_close_id(int id, enum reason reason);
int queues_notification_close_id(int id, enum reason reason);
/**
* Close the given notification. \see queues_notification_close_id().
/* Close the given notification. SEE queues_notification_close_id.
*
* @param n (transfer full) The notification to close
* @param reason The #reason to close
* @n: (transfer full): The notification to close
* @reason: The reason to close
* */
void queues_notification_close(struct notification *n, enum reason reason);
int queues_notification_close(notification *n, enum reason reason);
/**
* Pushes the latest notification of history to the displayed queue
/*
* 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
*
* @param n (transfer full) The notification to push to history
* @n: (transfer full): The notification to push to history
*/
void queues_history_push(struct notification *n);
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()
*
* @post Call wake_up() to synchronize the queues with the UI
* (which closes old and shows new notifications on screen)
*
* @param status the current status of dunst
* to the amount set via queues_displayed_limit
*/
void queues_update(struct dunst_status status);
void queues_update();
/**
* Calculate the distance to the next event, when an element in the
* queues changes
/*
* Return the distance to the next event in the queue,
* which forces an update visible to the user
*
* @param time the current time
* This may be:
*
* @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
* - notification hits timeout
* - notification's age second changes
* - notification's age threshold is hit
*/
gint64 queues_get_next_datachange(gint64 time);
/**
* Get the notification which has the given id in the displayed and waiting queue or
* NULL if not found
/*
* Pause queue-management of dunst
* pause_on = paused (no notifications displayed)
* pause_off = running
*
* @param id the id searched for.
*
* @return the `id` notification or NULL
* Calling update_lists is neccessary
*/
struct notification* queues_get_by_id(int id);
void queues_pause_on(void);
void queues_pause_off(void);
bool queues_pause_status(void);
/**
* Remove all notifications from all list and free the notifications
*
* @pre At least one time queues_init() called
/*
* Remove all notifications from all lists
* and free the notifications
*/
void queues_teardown(void);
void teardown_queues(void);
#endif
/* vim: set ft=c tabstop=8 shiftwidth=8 expandtab textwidth=0: */
/* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */

View File

@ -7,111 +7,85 @@
#include "dunst.h"
GSList *rules = NULL;
/*
* Apply rule to notification.
*/
void rule_apply(struct rule *r, struct notification *n)
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->fullscreen != FS_NULL)
n->fullscreen = r->fullscreen;
if (r->history_ignore != -1)
n->history_ignore = r->history_ignore;
if (r->set_transient != -1)
n->transient = r->set_transient;
if (r->skip_display != -1)
n->skip_display = r->skip_display;
if (r->action_name) {
g_free(n->default_action_name);
n->default_action_name = g_strdup(r->action_name);
}
if (r->markup != MARKUP_NULL)
n->markup = r->markup;
if (r->new_icon)
notification_icon_replace_path(n, r->new_icon);
if (r->fg) {
g_free(n->colors.fg);
n->colors.fg = g_strdup(r->fg);
}
if (r->bg) {
g_free(n->colors.bg);
n->colors.bg = g_strdup(r->bg);
}
if (r->highlight) {
g_free(n->colors.highlight);
n->colors.highlight = g_strdup(r->highlight);
}
if (r->fc) {
g_free(n->colors.frame);
n->colors.frame = g_strdup(r->fc);
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->scripts = g_renew(const char*,n->scripts,n->script_count + 1);
n->scripts[n->script_count] = r->script;
n->script_count++;
}
if (r->set_stack_tag) {
g_free(n->stack_tag);
n->stack_tag = g_strdup(r->set_stack_tag);
}
if (r->script)
n->script = r->script;
}
/*
* Check all rules if they match n and apply.
*/
void rule_apply_all(struct notification *n)
void rule_apply_all(notification *n)
{
for (GSList *iter = rules; iter; iter = iter->next) {
struct rule *r = iter->data;
rule_t *r = iter->data;
if (rule_matches_notification(r, n)) {
rule_apply(r, n);
}
}
}
struct rule *rule_new(void)
/*
* Initialize rule with default values.
*/
void rule_init(rule_t *r)
{
struct rule *r = g_malloc0(sizeof(struct rule));
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->fullscreen = FS_NULL;
r->markup = MARKUP_NULL;
r->history_ignore = -1;
r->new_icon = NULL;
r->history_ignore = false;
r->match_transient = -1;
r->set_transient = -1;
r->skip_display = -1;
return r;
}
static inline bool rule_field_matches_string(const char *value, const char *pattern)
{
return !pattern || (value && !fnmatch(pattern, value, 0));
r->fg = NULL;
r->bg = NULL;
r->format = NULL;
}
/*
* Check whether rule should be applied to n.
*/
bool rule_matches_notification(struct rule *r, struct notification *n)
bool rule_matches_notification(rule_t *r, notification *n)
{
return (r->msg_urgency == URG_NONE || r->msg_urgency == n->urgency)
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))
&& rule_field_matches_string(n->appname, r->appname)
&& rule_field_matches_string(n->desktop_entry, r->desktop_entry)
&& rule_field_matches_string(n->summary, r->summary)
&& rule_field_matches_string(n->body, r->body)
&& rule_field_matches_string(n->iconname, r->icon)
&& rule_field_matches_string(n->category, r->category)
&& rule_field_matches_string(n->stack_tag, r->stack_tag);
&& (r->msg_urgency == URG_NONE || r->msg_urgency == n->urgency));
}
/* vim: set ft=c tabstop=8 shiftwidth=8 expandtab textwidth=0: */
/* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */

View File

@ -8,7 +8,7 @@
#include "notification.h"
#include "settings.h"
struct rule {
typedef struct _rule_t {
char *name;
/* filters */
char *appname;
@ -16,42 +16,28 @@ struct rule {
char *body;
char *icon;
char *category;
char *stack_tag;
char *desktop_entry;
int msg_urgency;
/* actions */
gint64 timeout;
enum urgency urgency;
char *action_name;
enum markup_mode markup;
int history_ignore;
int match_transient;
int set_transient;
int skip_display;
char *new_icon;
char *fg;
char *bg;
char *highlight;
char *fc;
const char *format;
const char *script;
enum behavior_fullscreen fullscreen;
char *set_stack_tag;
};
} rule_t;
extern GSList *rules;
/**
* Allocate a new rule. The rule is fully initialised.
*
* @returns A new initialised rule.
*/
struct rule *rule_new(void);
void rule_apply(struct rule *r, struct notification *n);
void rule_apply_all(struct notification *n);
bool rule_matches_notification(struct rule *r, struct notification *n);
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 ft=c tabstop=8 shiftwidth=8 expandtab textwidth=0: */
/* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */

View File

@ -5,112 +5,100 @@
#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 "log.h"
#include "notification.h"
#include "option_parser.h"
#include "rules.h"
#include "utils.h"
#include "x11/x.h"
#include "output.h"
#include "../config.h"
settings_t settings;
struct settings settings;
static enum urgency ini_get_urgency(const char *section, const char *key, const enum urgency def)
static void parse_follow_mode(const char *mode)
{
enum urgency ret;
char *c = ini_get_string(section, key, NULL);
if (!string_parse_urgency(c, &ret)) {
if (c)
LOG_W("Unknown urgency: '%s'", c);
ret = def;
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;
}
g_free(c);
return ret;
}
static FILE *xdg_config(const char *filename)
static enum markup_mode parse_markup_mode(const char *mode)
{
const gchar * const * systemdirs = g_get_system_config_dirs();
const gchar * userdir = g_get_user_config_dir();
FILE *f;
char *path;
path = g_strconcat(userdir, filename, NULL);
f = fopen(path, "r");
g_free(path);
for (const gchar * const *d = systemdirs;
!f && *d;
d++) {
path = g_strconcat(*d, filename, NULL);
f = fopen(path, "r");
g_free(path);
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;
}
}
if (!f) {
f = fopen("/etc/dunst/dunstrc", "r");
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);
}
return f;
g_free(urg);
return ret;
}
void load_settings(char *cmdline_config_path)
{
#ifndef STATIC_CONFIG
xdgHandle xdg;
FILE *config_file = NULL;
if (cmdline_config_path) {
if (STR_EQ(cmdline_config_path, "-")) {
config_file = stdin;
} else {
config_file = fopen(cmdline_config_path, "r");
}
xdgInitHandle(&xdg);
if (!config_file) {
DIE("Cannot find config file: '%s'", cmdline_config_path);
}
if (cmdline_config_path != NULL) {
config_file = fopen(cmdline_config_path, "r");
}
if (!config_file) {
config_file = xdg_config("/dunst/dunstrc");
if (config_file == NULL) {
config_file = xdgConfigOpen("dunst/dunstrc", "r", &xdg);
}
if (!config_file) {
if (config_file == NULL) {
/* Fall back to just "dunstrc", which was used before 2013-06-23
* (before v0.2). */
config_file = xdg_config("/dunstrc");
}
if (!config_file) {
LOG_W("No dunstrc found.");
config_file = xdgConfigOpen("dunstrc", "r", &xdg);
if (config_file == NULL) {
puts("no dunstrc found -> skipping\n");
xdgWipeHandle(&xdg);
}
}
load_ini_file(config_file);
#else
LOG_M("dunstrc parsing disabled. "
"Using STATIC_CONFIG is deprecated behavior.");
fprintf(stderr, "Warning: dunstrc parsing disabled. "
"Using STATIC_CONFIG is deprecated behavior.\n");
#endif
{
char *loglevel = option_get_string(
"global",
"verbosity", "-verbosity", NULL,
"The verbosity to log (one of 'crit', 'warn', 'mesg', 'info', 'debug')"
);
log_set_level_from_string(loglevel);
g_free(loglevel);
}
settings.per_monitor_dpi = option_get_bool(
"experimental",
"per_monitor_dpi", NULL, false,
@ -123,12 +111,6 @@ void load_settings(char *cmdline_config_path)
"Force the use of the Xinerama extension"
);
settings.force_xwayland = option_get_bool(
"global",
"force_xwayland", "-force_xwayland", false,
"Force the use of the xwayland output"
);
settings.font = option_get_string(
"global",
"font", "-font/-fn", defaults.font,
@ -145,8 +127,7 @@ void load_settings(char *cmdline_config_path)
);
settings.markup = (allow_markup ? MARKUP_FULL : MARKUP_STRIP);
LOG_M("'allow_markup' is deprecated, please "
"use 'markup' instead.");
fprintf(stderr, "Warning: 'allow_markup' is deprecated, please use 'markup' instead.\n");
}
char *c = option_get_string(
@ -155,11 +136,13 @@ void load_settings(char *cmdline_config_path)
"Specify how markup should be handled"
);
if (!string_parse_markup_mode(c, &settings.markup)) {
if (c)
LOG_W("Cannot parse markup mode value: '%s'", c);
if (!settings.markup)
settings.markup = defaults.markup;
//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);
}
@ -179,7 +162,7 @@ void load_settings(char *cmdline_config_path)
settings.indicate_hidden = option_get_bool(
"global",
"indicate_hidden", "-indicate_hidden", defaults.indicate_hidden,
"Show how many notifications are hidden"
"Show how many notificaitons are hidden?"
);
settings.word_wrap = option_get_bool(
@ -187,22 +170,24 @@ void load_settings(char *cmdline_config_path)
"word_wrap", "-word_wrap", defaults.word_wrap,
"Truncating long lines or do word wrap"
);
settings.ignore_dbusclose = option_get_bool(
"global",
"ignore_dbusclose", "-ignore_dbusclose", defaults.ignore_dbusclose,
"Ignore dbus CloseNotification events"
);
{
char *c = option_get_string(
"global",
"ellipsize", "-ellipsize", NULL,
"ellipsize", "-ellipsize", "",
"Ellipsize truncated lines on the start/middle/end"
);
if (!string_parse_ellipsize(c, &settings.ellipsize)) {
if (c)
LOG_W("Unknown ellipsize value: '%s'", c);
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);
@ -220,22 +205,6 @@ void load_settings(char *cmdline_config_path)
"Don't timeout notifications if user is longer idle than threshold"
);
#ifndef ENABLE_WAYLAND
if (is_running_wayland()){
/* We are using xwayland now. Setting force_xwayland to make sure
* the idle workaround below is activated */
settings.force_xwayland = true;
}
#endif
if (settings.force_xwayland && is_running_wayland()) {
if (settings.idle_threshold > 0)
LOG_W("Using xwayland. Disabling idle.");
/* There is no way to detect if the user is idle
* on xwayland, so turn this feature off */
settings.idle_threshold = 0;
}
settings.monitor = option_get_int(
"global",
"monitor", "-mon/-monitor", defaults.monitor,
@ -245,16 +214,14 @@ void load_settings(char *cmdline_config_path)
{
char *c = option_get_string(
"global",
"follow", "-follow", NULL,
"follow", "-follow", "",
"Follow mouse, keyboard or none?"
);
if (!string_parse_follow_mode(c, &settings.f_mode)) {
if (c)
LOG_W("Cannot parse follow mode: %s", c);
settings.f_mode = defaults.f_mode;
if (strlen(c) > 0) {
parse_follow_mode(c);
g_free(c);
}
g_free(c);
}
settings.title = option_get_string(
@ -269,24 +236,11 @@ void load_settings(char *cmdline_config_path)
"Define the class of windows spawned by dunst."
);
{
char *c = option_get_string(
"global",
"geometry", "-geom/-geometry", NULL,
"Geometry for the window"
);
if (c) {
// TODO: Implement own geometry parsing to get rid of
// the include dependency on X11
settings.geometry = x_parse_geometry(c);
g_free(c);
} else {
settings.geometry = defaults.geometry;
}
}
settings.geom = option_get_string(
"global",
"geometry", "-geom/-geometry", defaults.geom,
"Geometry for the window"
);
settings.shrink = option_get_bool(
"global",
@ -309,17 +263,22 @@ void load_settings(char *cmdline_config_path)
{
char *c = option_get_string(
"global",
"alignment", "-align/-alignment", NULL,
"alignment", "-align/-alignment", "",
"Text alignment left/center/right"
);
if (!string_parse_alignment(c, &settings.align)) {
if (c)
LOG_W("Unknown alignment value: '%s'", c);
settings.align = defaults.align;
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);
}
g_free(c);
}
settings.show_age_threshold = option_get_time(
@ -331,7 +290,7 @@ void load_settings(char *cmdline_config_path)
settings.hide_duplicate_count = option_get_bool(
"global",
"hide_duplicate_count", "-hide_duplicate_count", false,
"Hide the count of stacked notifications with the same content"
"Hide the count of merged notifications with the same content"
);
settings.sticky_history = option_get_bool(
@ -370,67 +329,12 @@ void load_settings(char *cmdline_config_path)
"horizontal padding"
);
settings.text_icon_padding = option_get_int(
"global",
"text_icon_padding", "-text_icon_padding", defaults.text_icon_padding,
"Padding between text and icon"
);
settings.transparency = option_get_int(
"global",
"transparency", "-transparency", defaults.transparency,
"Transparency. Range 0-100"
"Transparency. range 0-100"
);
settings.corner_radius = option_get_int(
"global",
"corner_radius", "-corner_radius", defaults.corner_radius,
"Window corner radius"
);
settings.progress_bar_height = option_get_int(
"global",
"progress_bar_height", "-progress_bar_height", defaults.progress_bar_height,
"Height of the progress bar"
);
settings.progress_bar_min_width = option_get_int(
"global",
"progress_bar_min_width", "-progress_bar_min_width", defaults.progress_bar_min_width,
"Minimum width of the progress bar"
);
settings.progress_bar_max_width = option_get_int(
"global",
"progress_bar_max_width", "-progress_bar_max_width", defaults.progress_bar_max_width,
"Maximum width of the progress bar"
);
settings.progress_bar_frame_width = option_get_int(
"global",
"progress_bar_frame_width", "-progress_bar_frame_width", defaults.progress_bar_frame_width,
"Frame width of the progress bar"
);
settings.progress_bar = option_get_bool(
"global",
"progress_bar", "-progress_bar", true,
"Show the progress bar"
);
// check sanity of the progress bar options
{
if (settings.progress_bar_height < (2 * settings.progress_bar_frame_width)){
LOG_E("setting progress_bar_frame_width is bigger than half of progress_bar_height");
}
if (settings.progress_bar_max_width < (2 * settings.progress_bar_frame_width)){
LOG_E("setting progress_bar_frame_width is bigger than half of progress_bar_max_width");
}
if (settings.progress_bar_max_width < settings.progress_bar_min_width){
LOG_E("setting progress_bar_max_width is smaller than progress_bar_min_width");
}
}
{
char *c = option_get_string(
"global",
@ -438,8 +342,17 @@ void load_settings(char *cmdline_config_path)
"Color of the separator line (or 'auto')"
);
if (!string_parse_sepcolor(c, &settings.sep_color)) {
settings.sep_color = defaults.sep_color;
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);
}
@ -447,7 +360,7 @@ void load_settings(char *cmdline_config_path)
settings.stack_duplicates = option_get_bool(
"global",
"stack_duplicates", "-stack_duplicates", true,
"Stack together notifications with the same content"
"Merge multiple notifications with the same content"
);
settings.startup_notification = option_get_bool(
@ -465,8 +378,8 @@ void load_settings(char *cmdline_config_path)
{
GError *error = NULL;
if (!g_shell_parse_argv(settings.dmenu, NULL, &settings.dmenu_cmd, &error)) {
LOG_W("Unable to parse dmenu command: '%s'."
"dmenu functionality will be disabled.", error->message);
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;
}
@ -479,92 +392,33 @@ void load_settings(char *cmdline_config_path)
"path to browser"
);
{
GError *error = NULL;
if (!g_shell_parse_argv(settings.browser, NULL, &settings.browser_cmd, &error)) {
LOG_W("Unable to parse browser command: '%s'."
" URL functionality will be disabled.", error->message);
g_error_free(error);
settings.browser_cmd = NULL;
}
}
{
char *c = option_get_string(
"global",
"icon_position", "-icon_position", "left",
"icon_position", "-icon_position", "off",
"Align icons left/right/off"
);
if (!string_parse_icon_position(c, &settings.icon_position)) {
if (c)
LOG_W("Unknown icon position: '%s'", c);
settings.icon_position = defaults.icon_position;
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);
}
g_free(c);
}
{
char *c = option_get_string(
"global",
"vertical_alignment", "-vertical_alignment", "center",
"Align icon and text top/center/bottom"
);
if (!string_parse_vertical_alignment(c, &settings.vertical_alignment)) {
if (c)
LOG_W("Unknown vertical alignment: '%s'", c);
settings.vertical_alignment = defaults.vertical_alignment;
}
g_free(c);
}
{
char *c = option_get_string(
"global",
"layer", "-layer", "overlay",
"Select the layer where notifications should be placed"
);
if (!string_parse_layer(c, &settings.layer)) {
if (c)
LOG_W("Unknown layer: '%s'", c);
settings.layer = defaults.layer;
}
g_free(c);
}
settings.min_icon_size = option_get_int(
"global",
"min_icon_size", "-min_icon_size", defaults.min_icon_size,
"Scale smaller icons up to this size, set to 0 to disable. If max_icon_size also specified, that has the final say."
);
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"
);
// restrict the icon size to a reasonable limit if we have a fixed width.
// Otherwise the layout will be broken by too large icons.
// See https://github.com/dunst-project/dunst/issues/540
if (settings.geometry.width_set && settings.geometry.w != 0) {
const int icon_size_limit = settings.geometry.w / 2;
if ( settings.max_icon_size == 0
|| settings.max_icon_size > icon_size_limit) {
if (settings.max_icon_size != 0) {
LOG_W("Max width was set to %d but got a max_icon_size of %d, too large to use. Setting max_icon_size=%d",
settings.geometry.w, settings.max_icon_size, icon_size_limit);
} else {
LOG_I("Max width was set but max_icon_size is unlimited. Limiting icons to %d pixels", icon_size_limit);
}
settings.max_icon_size = icon_size_limit;
}
}
// 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")) {
@ -573,7 +427,7 @@ void load_settings(char *cmdline_config_path)
"icon_folders", "-icon_folders", defaults.icon_path,
"folders to default icons (deprecated, please use 'icon_path' instead)"
);
LOG_M("The option 'icon_folders' is 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.
@ -593,9 +447,7 @@ void load_settings(char *cmdline_config_path)
"width", NULL, defaults.frame_width,
"Width of frame around the window"
);
LOG_M("The frame section is deprecated, width has "
"been renamed to frame_width and moved to "
"the global section.");
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(
@ -611,9 +463,7 @@ void load_settings(char *cmdline_config_path)
"color", NULL, defaults.frame_color,
"Color of the frame around the window"
);
LOG_M("The frame section is deprecated, color "
"has been renamed to frame_color and moved "
"to the global section.");
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(
@ -624,67 +474,21 @@ void load_settings(char *cmdline_config_path)
);
}
{
char **c = option_get_list(
"global",
"mouse_left_click", "-mouse_left_click", NULL,
"Action of Left click event"
);
if (!string_parse_mouse_action_list(c, &settings.mouse_left_click)) {
settings.mouse_left_click = defaults.mouse_left_click;
}
free_string_array(c);
}
{
char **c = option_get_list(
"global",
"mouse_middle_click", "-mouse_middle_click", NULL,
"Action of middle click event"
);
if (!string_parse_mouse_action_list(c, &settings.mouse_middle_click)) {
settings.mouse_middle_click = defaults.mouse_middle_click;
}
free_string_array(c);
}
{
char **c = option_get_list(
"global",
"mouse_right_click", "-mouse_right_click", NULL,
"Action of right click event"
);
if (!string_parse_mouse_action_list(c, &settings.mouse_right_click)) {
settings.mouse_right_click = defaults.mouse_right_click;
}
free_string_array(c);
}
settings.colors_low.bg = option_get_string(
settings.lowbgcolor = option_get_string(
"urgency_low",
"background", "-lb", defaults.colors_low.bg,
"background", "-lb", defaults.lowbgcolor,
"Background color for notifications with low urgency"
);
settings.colors_low.fg = option_get_string(
settings.lowfgcolor = option_get_string(
"urgency_low",
"foreground", "-lf", defaults.colors_low.fg,
"foreground", "-lf", defaults.lowfgcolor,
"Foreground color for notifications with low urgency"
);
settings.colors_low.highlight = option_get_string(
settings.lowframecolor = option_get_string(
"urgency_low",
"highlight", "-lh", defaults.colors_low.highlight,
"Highlight color for notifications with low urgency"
);
settings.colors_low.frame = option_get_string(
"urgency_low",
"frame_color", "-lfr", settings.frame_color ? settings.frame_color : defaults.colors_low.frame,
"frame_color", "-lfr", NULL,
"Frame color for notifications with low urgency"
);
@ -700,27 +504,21 @@ void load_settings(char *cmdline_config_path)
"Icon for notifications with low urgency"
);
settings.colors_norm.bg = option_get_string(
settings.normbgcolor = option_get_string(
"urgency_normal",
"background", "-nb", defaults.colors_norm.bg,
"background", "-nb", defaults.normbgcolor,
"Background color for notifications with normal urgency"
);
settings.colors_norm.fg = option_get_string(
settings.normfgcolor = option_get_string(
"urgency_normal",
"foreground", "-nf", defaults.colors_norm.fg,
"foreground", "-nf", defaults.normfgcolor,
"Foreground color for notifications with normal urgency"
);
settings.colors_norm.highlight = option_get_string(
settings.normframecolor = option_get_string(
"urgency_normal",
"highlight", "-nh", defaults.colors_norm.highlight,
"Highlight color for notifications with normal urgency"
);
settings.colors_norm.frame = option_get_string(
"urgency_normal",
"frame_color", "-nfr", settings.frame_color ? settings.frame_color : defaults.colors_norm.frame,
"frame_color", "-nfr", NULL,
"Frame color for notifications with normal urgency"
);
@ -736,27 +534,21 @@ void load_settings(char *cmdline_config_path)
"Icon for notifications with normal urgency"
);
settings.colors_crit.bg = option_get_string(
settings.critbgcolor = option_get_string(
"urgency_critical",
"background", "-cb", defaults.colors_crit.bg,
"background", "-cb", defaults.critbgcolor,
"Background color for notifications with critical urgency"
);
settings.colors_crit.fg = option_get_string(
settings.critfgcolor = option_get_string(
"urgency_critical",
"foreground", "-cf", defaults.colors_crit.fg,
"foreground", "-cf", defaults.critfgcolor,
"Foreground color for notifications with ciritical urgency"
);
settings.colors_crit.highlight = option_get_string(
settings.critframecolor = option_get_string(
"urgency_critical",
"highlight", "-ch", defaults.colors_crit.highlight,
"Highlight color for notifications with ciritical urgency"
);
settings.colors_crit.frame = option_get_string(
"urgency_critical",
"frame_color", "-cfr", settings.frame_color ? settings.frame_color : defaults.colors_crit.frame,
"frame_color", "-cfr", NULL,
"Frame color for notifications with critical urgency"
);
@ -807,6 +599,27 @@ void load_settings(char *cmdline_config_path)
"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);
@ -817,26 +630,27 @@ void load_settings(char *cmdline_config_path)
cur_section = next_section(cur_section);
if (!cur_section)
break;
if (STR_EQ(cur_section, "global")
|| STR_EQ(cur_section, "frame")
|| STR_EQ(cur_section, "experimental")
|| STR_EQ(cur_section, "shortcuts")
|| STR_EQ(cur_section, "urgency_low")
|| STR_EQ(cur_section, "urgency_normal")
|| STR_EQ(cur_section, "urgency_critical"))
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 */
struct rule *r = NULL;
rule_t *r = NULL;
for (GSList *iter = rules; iter; iter = iter->next) {
struct rule *match = iter->data;
rule_t *match = iter->data;
if (match->name &&
STR_EQ(match->name, cur_section))
strcmp(match->name, cur_section) == 0)
r = match;
}
if (!r) {
r = rule_new();
if (r == NULL) {
r = g_malloc(sizeof(rule_t));
rule_init(r);
rules = g_slist_insert(rules, r, -1);
}
@ -846,7 +660,6 @@ void load_settings(char *cmdline_config_path)
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->stack_tag = ini_get_string(cur_section, "stack_tag", r->stack_tag);
r->timeout = ini_get_time(cur_section, "timeout", r->timeout);
{
@ -855,48 +668,30 @@ void load_settings(char *cmdline_config_path)
"markup", NULL
);
if (!string_parse_markup_mode(c, &r->markup)) {
if (c)
LOG_W("Invalid markup mode value: %s", c);
if (c != NULL) {
r->markup = parse_markup_mode(c);
g_free(c);
}
g_free(c);
}
r->action_name = ini_get_string(cur_section, "action_name", NULL);
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->highlight = ini_get_string(cur_section, "highlight", r->highlight);
r->fc = ini_get_string(cur_section, "frame_color", r->fc);
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->desktop_entry = ini_get_string(cur_section, "desktop_entry", r->desktop_entry);
r->skip_display = ini_get_bool(cur_section, "skip_display", r->skip_display);
{
char *c = ini_get_string(
cur_section,
"fullscreen", NULL
);
if (!string_parse_fullscreen(c, &r->fullscreen)) {
if (c)
LOG_W("Invalid fullscreen value: %s", c);
}
g_free(c);
}
r->script = ini_get_path(cur_section, "script", NULL);
r->set_stack_tag = ini_get_string(cur_section, "set_stack_tag", r->set_stack_tag);
}
#ifndef STATIC_CONFIG
if (config_file) {
fclose(config_file);
free_ini();
xdgWipeHandle(&xdg);
}
#endif
}
/* vim: set ft=c tabstop=8 shiftwidth=8 expandtab textwidth=0: */
/* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */

View File

@ -4,63 +4,38 @@
#include <stdbool.h>
#ifdef ENABLE_WAYLAND
#include "wayland/protocols/wlr-layer-shell-unstable-v1-client-header.h"
#endif
#include "markup.h"
#include "notification.h"
#include "x11/x.h"
enum alignment { ALIGN_LEFT, ALIGN_CENTER, ALIGN_RIGHT };
enum ellipsize { ELLIPSE_START, ELLIPSE_MIDDLE, ELLIPSE_END };
enum icon_position { ICON_LEFT, ICON_RIGHT, ICON_OFF };
enum vertical_alignment { VERTICAL_TOP, VERTICAL_CENTER, VERTICAL_BOTTOM };
enum separator_color { SEP_FOREGROUND, SEP_AUTO, SEP_FRAME, SEP_CUSTOM };
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 mouse_action { MOUSE_NONE, MOUSE_DO_ACTION, MOUSE_CLOSE_CURRENT, MOUSE_CLOSE_ALL, MOUSE_CONTEXT, MOUSE_CONTEXT_ALL, MOUSE_OPEN_URL };
#ifndef ZWLR_LAYER_SHELL_V1_LAYER_ENUM
#define ZWLR_LAYER_SHELL_V1_LAYER_ENUM
// Needed for compiling without wayland dependency
enum zwlr_layer_shell_v1_layer {
ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND = 0,
ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM = 1,
ZWLR_LAYER_SHELL_V1_LAYER_TOP = 2,
ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY = 3,
};
#endif /* ZWLR_LAYER_SHELL_V1_LAYER_ENUM */
enum markup_mode { MARKUP_NULL, MARKUP_NO, MARKUP_STRIP, MARKUP_FULL };
enum centering { CENTERING_OFF, CENTERING_HORIZONTAL, CENTERING_VERTICAL, CENTERING_BOTH };
struct separator_color_data {
enum separator_color type;
char *sep_color;
};
struct geometry {
int x;
int y;
unsigned int w;
unsigned int h;
bool negative_x;
bool negative_y;
bool negative_width;
bool width_set;
};
struct settings {
typedef struct _settings {
bool print_notifications;
bool per_monitor_dpi;
enum markup_mode markup;
bool stack_duplicates;
bool hide_duplicate_count;
char *font;
struct notification_colors colors_low;
struct notification_colors colors_norm;
struct notification_colors colors_crit;
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;
struct geometry geometry;
char *geom;
enum centering centering;
char *title;
char *class;
int shrink;
@ -73,7 +48,6 @@ struct settings {
int history_length;
int show_indicators;
int word_wrap;
int ignore_dbusclose;
enum ellipsize ellipsize;
int ignore_newline;
int line_height;
@ -81,8 +55,8 @@ struct settings {
int separator_height;
int padding;
int h_padding;
int text_icon_padding;
struct separator_color_data sep_color;
enum separator_color sep_color;
char *sep_custom_color_str;
int frame_width;
char *frame_color;
int startup_notification;
@ -90,35 +64,21 @@ struct settings {
char *dmenu;
char **dmenu_cmd;
char *browser;
char **browser_cmd;
enum icon_position icon_position;
enum vertical_alignment vertical_alignment;
int min_icon_size;
enum icon_position_t icon_position;
int max_icon_size;
char *icon_path;
enum follow_mode f_mode;
bool always_run_script;
struct keyboard_shortcut close_ks;
struct keyboard_shortcut close_all_ks;
struct keyboard_shortcut history_ks;
struct keyboard_shortcut context_ks;
keyboard_shortcut close_ks;
keyboard_shortcut close_all_ks;
keyboard_shortcut history_ks;
keyboard_shortcut context_ks;
bool force_xinerama;
bool force_xwayland;
int corner_radius;
enum mouse_action *mouse_left_click;
enum mouse_action *mouse_middle_click;
enum mouse_action *mouse_right_click;
int progress_bar_height;
int progress_bar_min_width;
int progress_bar_max_width;
int progress_bar_frame_width;
bool progress_bar;
enum zwlr_layer_shell_v1_layer layer;
};
} settings_t;
extern struct settings settings;
extern settings_t settings;
void load_settings(char *cmdline_config_path);
#endif
/* vim: set ft=c tabstop=8 shiftwidth=8 expandtab textwidth=0: */
/* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */

View File

@ -2,45 +2,22 @@
#include "utils.h"
#include <assert.h>
#include <ctype.h>
#include <errno.h>
#include <glib.h>
#include <pwd.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <unistd.h>
#include <string.h>
#include "log.h"
/* see utils.h */
void free_string_array(char **arr)
{
if (arr){
for (int i = 0; arr[i]; i++){
g_free(arr[i]);
}
}
g_free(arr);
}
/* see utils.h */
char *string_replace_char(char needle, char replacement, char *haystack)
{
ASSERT_OR_RET(haystack, NULL);
char *current = haystack;
while ((current = strchr(current, needle)))
while ((current = strchr(current, needle)) != NULL)
*current++ = replacement;
return haystack;
}
/* see utils.h */
char *string_replace_at(char *buf, int pos, int len, const char *repl)
{
assert(buf);
assert(repl);
char *tmp;
int size, buf_len, repl_len;
@ -52,9 +29,9 @@ char *string_replace_at(char *buf, int pos, int len, const char *repl)
tmp = buf;
} else {
tmp = g_malloc(size);
memcpy(tmp, buf, pos);
}
memcpy(tmp, buf, pos);
memcpy(tmp + pos, repl, repl_len);
memmove(tmp + pos + repl_len, buf + pos + len, buf_len - (pos + len) + 1);
@ -65,13 +42,19 @@ char *string_replace_at(char *buf, int pos, int len, const char *repl)
return tmp;
}
/* see utils.h */
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)
{
ASSERT_OR_RET(haystack, NULL);
assert(needle);
assert(replacement);
char *start;
int needle_pos;
int needle_len, repl_len;
@ -84,7 +67,7 @@ char *string_replace_all(const char *needle, const char *replacement, char *hays
start = strstr(haystack, needle);
repl_len = strlen(replacement);
while (start) {
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);
@ -92,14 +75,13 @@ char *string_replace_all(const char *needle, const char *replacement, char *hays
return haystack;
}
/* see utils.h */
char *string_append(char *a, const char *b, const char *sep)
{
if (STR_EMPTY(a)) {
if (!a || *a == '\0') {
g_free(a);
return g_strdup(b);
}
if (STR_EMPTY(b))
if (!b || *b == '\0')
return a;
char *new;
@ -110,29 +92,11 @@ char *string_append(char *a, const char *b, const char *sep)
g_free(a);
return new;
}
/* see utils.h */
char *string_strip_quotes(const char *value)
{
ASSERT_OR_RET(value, NULL);
size_t len = strlen(value);
char *s;
if (value[0] == '"' && value[len-1] == '"')
s = g_strndup(value + 1, len-2);
else
s = g_strdup(value);
return s;
}
/* see utils.h */
void string_strip_delimited(char *str, char a, char b)
{
assert(str);
int iread=-1, iwrite=0, copen=0;
while (str[++iread] != 0) {
if (str[iread] == a) {
@ -146,27 +110,13 @@ void string_strip_delimited(char *str, char a, char b)
str[iwrite] = 0;
}
/* see utils.h */
char **string_to_array(const char *string)
{
char **arr = NULL;
if (string) {
arr = g_strsplit(string, ",", 0);
for (int i = 0; arr[i]; i++){
g_strstrip(arr[i]);
}
}
return arr;
}
/* see utils.h */
char *string_to_path(char *string)
{
if (string && STRN_EQ(string, "~/", 2)) {
char *home = g_strconcat(user_get_home(), "/", NULL);
if (string && 0 == strncmp(string, "~/", 2)) {
char *home = g_strconcat(getenv("HOME"), "/", NULL);
string = string_replace_at(string, 0, 2, home);
string = string_replace("~/", home, string);
g_free(home);
}
@ -174,9 +124,9 @@ char *string_to_path(char *string)
return string;
}
/* see utils.h */
gint64 string_to_time(const char *string)
{
assert(string);
errno = 0;
@ -184,79 +134,40 @@ gint64 string_to_time(const char *string)
gint64 val = strtoll(string, &endptr, 10);
if (errno != 0) {
LOG_W("Time: '%s': %s.", string, strerror(errno));
fprintf(stderr, "ERROR: Time: '%s': %s.\n", string, strerror(errno));
return 0;
} else if (string == endptr) {
LOG_W("Time: '%s': No digits found.", string);
fprintf(stderr, "ERROR: Time: No digits found.\n");
return 0;
} else if (errno != 0 && val == 0) {
LOG_W("Time: '%s': Unknown error.", string);
fprintf(stderr, "ERROR: Time: '%s' unknown error.\n", string);
return 0;
} else if (errno == 0 && !*endptr) {
return S2US(val);
return val * G_USEC_PER_SEC;
}
// endptr may point to a separating space
while (isspace(*endptr))
while (*endptr == ' ')
endptr++;
if (STRN_EQ(endptr, "ms", 2))
if (0 == strncmp(endptr, "ms", 2))
return val * 1000;
else if (STRN_EQ(endptr, "s", 1))
return S2US(val);
else if (STRN_EQ(endptr, "m", 1))
return S2US(val) * 60;
else if (STRN_EQ(endptr, "h", 1))
return S2US(val) * 60 * 60;
else if (STRN_EQ(endptr, "d", 1))
return S2US(val) * 60 * 60 * 24;
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;
}
/* see utils.h */
gint64 time_monotonic_now(void)
void die(char *text, int exit_value)
{
struct timespec tv_now;
/* On Linux, BOOTTIME is the correct monotonic time,
* as BOOTTIME counts onwards during sleep. For all other
* POSIX compliant OSes, MONOTONIC should also count onwards
* during system sleep. */
#ifdef __linux__
clock_gettime(CLOCK_BOOTTIME, &tv_now);
#else
clock_gettime(CLOCK_MONOTONIC, &tv_now);
#endif
return S2US(tv_now.tv_sec) + tv_now.tv_nsec / 1000;
fputs(text, stderr);
exit(exit_value);
}
/* see utils.h */
const char *user_get_home(void)
{
static const char *home_directory = NULL;
ASSERT_OR_RET(!home_directory, home_directory);
// Check the HOME variable for the user's home
home_directory = getenv("HOME");
ASSERT_OR_RET(!home_directory, home_directory);
// Check the /etc/passwd entry for the user's home
home_directory = getpwuid(getuid())->pw_dir;
return home_directory;
}
bool safe_setenv(const char* key, const char* value){
if (!key)
return false;
if (!value)
setenv(key, "", 1);
else
setenv(key, value, 1);
return true;
}
/* vim: set ft=c tabstop=8 shiftwidth=8 expandtab textwidth=0: */
/* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */

View File

@ -3,151 +3,32 @@
#define DUNST_UTILS_H
#include <glib.h>
#include <string.h>
#include <stdbool.h>
//! Test if a string is NULL or empty
#define STR_EMPTY(s) (!s || (*s == '\0'))
//! Test if a string is non-NULL and not empty
#define STR_FULL(s) !(STR_EMPTY(s))
//! Test if string a and b contain the same chars
#define STR_EQ(a, b) (g_strcmp0(a, b) == 0)
//! Test if string a and b are same up to n chars
#define STRN_EQ(a, b, n) (strncmp(a, b, n) == 0)
//! Test if string a and b are the same case-insensitively
#define STR_CASEQ(a, b) (strcasecmp(a, b) == 0)
//! Assert that expr evaluates to true, if not return with val
#define ASSERT_OR_RET(expr, val) if (!(expr)) return val;
//! Convert a second into the internal time representation
#define S2US(s) (((gint64)(s)) * 1000 * 1000)
/**
* Frees an array of strings whose last element is a NULL pointer.
*
* Assumes the last element is a NULL pointer, otherwise will result in a buffer overflow.
* @param arr The array of strings to free
*/
void free_string_array(char **arr);
/**
* Replaces all occurrences of the char \p needle with the char \p replacement in \p haystack.
*
* Does not allocate a new string.
*
* @param needle The char to replace with replacement
* @param replacement The char which is the new one
* @param haystack (nullable) The string to replace
*
* @returns The exact value of the haystack paramater (to allow nesting)
*/
/* replace all occurrences of the character needle with the character replacement in haystack */
char *string_replace_char(char needle, char replacement, char *haystack);
/**
* Replace a substring inside a string with another string.
*
* May reallocate memory. Free the result with `g_free`.
*
* @param buf The string to replace
* @param pos The position of the substring to replace
* @param len The length of the substring to replace
* @param repl The new contents of the substring.
*/
char *string_replace_at(char *buf, int pos, int len, const char *repl);
/**
* Replace all occurences of a substring.
*
* @param needle The substring to search
* @param replacement The substring to replace
* @param haystack (nullable) The string to search the substring for
*/
/* replace all occurrences of needle with replacement in haystack */
char *string_replace_all(const char *needle, const char *replacement, char *haystack);
/**
* Append \p b to string \p a. And concatenate both strings with \p concat, if both are non-empty.
*
* @param a (nullable) The left side of the string
* @param b (nullable) The right side of the string
* @param sep (nullable) The concatenator to concatenate if a and b given
*/
/* 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 quotes from a string. Won't touch inner quotes.
*
* @param value The string to strip the quotes from
* @returns A copy of the string value with the outer quotes removed (if any)
*/
char *string_strip_quotes(const char *value);
/**
* Strip content between two delimiter characters
*
* @param str The string to operate in place
* @param a Starting delimiter
* @param b Ending delimiter
*/
/* strip content between two delimiter characters (inplace) */
void string_strip_delimited(char *str, char a, char b);
/**
* Parse a comma-delimited string into a dynamic array of tokens
*
* The string is split on commas and strips spaces from tokens. The last element
* of the array is NULL in order to avoid passing around a length variable.
*
* @param string The string to convert to an array
* @returns The array of tokens.
*/
char **string_to_array(const char *string);
/* exit with an error message */
void die(char *msg, int exit_value);
/**
* Replace tilde and path-specific values with its equivalents
*
* The string gets invalidated. The new valid representation is the return value.
*
* @param string (nullable) The string to convert to a path.
* @returns The tilde-replaced string.
*/
/* replace tilde and path-specific values with its equivalents */
char *string_to_path(char *string);
/**
* Convert time units (ms, s, m) to the internal `gint64` microseconds format
*
* If no unit is given, 's' (seconds) is assumed by default.
*
* @param string The string to parse the time format from.
*/
/* convert time units (ms, s, m) to internal gint64 microseconds */
gint64 string_to_time(const char *string);
/**
* Get the current monotonic time. In contrast to `g_get_monotonic_time`,
* this function respects the real monotonic time of the system and
* counts onwards during system sleep.
*
* @returns: A `gint64` monotonic time representation
*/
gint64 time_monotonic_now(void);
/**
* Retrieve the HOME directory of the user running dunst
*
* @returns: A string of the current home directory
*/
const char *user_get_home(void);
/**
* Try to set an environment variable safely. If an environment variable with
* name `key` exists, it will be overwritten.
* If `value` is null, `key` will be set to an empty string.
*
* @param key (nullable) The environment variable to change
* @param value (nullable) The value to change it to.
* @returns: A bool that is true when it succeeds
*/
bool safe_setenv(const char* key, const char* value);
#endif
/* vim: set ft=c tabstop=8 shiftwidth=8 expandtab textwidth=0: */
/* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */

View File

@ -1,192 +0,0 @@
/*
* libgwater-wayland - Wayland GSource
*
* Copyright © 2014-2017 Quentin "Sardem FF7" Glidic
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif /* HAVE_CONFIG_H */
#ifdef G_LOG_DOMAIN
#undef G_LOG_DOMAIN
#endif /* G_LOG_DOMAIN */
#define G_LOG_DOMAIN "GWaterWayland"
#include <errno.h>
#include <glib.h>
#include <wayland-client.h>
#include "libgwater-wayland.h"
struct _GWaterWaylandSource {
GSource source;
gboolean display_owned;
struct wl_display *display;
gpointer fd;
int error;
};
static gboolean
_g_water_wayland_source_prepare(GSource *source, gint *timeout)
{
GWaterWaylandSource *self = (GWaterWaylandSource *)source;
*timeout = 0;
if ( wl_display_prepare_read(self->display) != 0 )
return TRUE;
else if ( wl_display_flush(self->display) < 0 )
{
self->error = errno;
return TRUE;
}
*timeout = -1;
return FALSE;
}
static gboolean
_g_water_wayland_source_check(GSource *source)
{
GWaterWaylandSource *self = (GWaterWaylandSource *)source;
if ( self->error > 0 )
return TRUE;
GIOCondition revents;
revents = g_source_query_unix_fd(source, self->fd);
if ( revents & G_IO_IN )
{
if ( wl_display_read_events(self->display) < 0 )
self->error = errno;
}
else
wl_display_cancel_read(self->display);
return ( revents > 0 );
}
static gboolean
_g_water_wayland_source_dispatch(GSource *source, GSourceFunc callback, gpointer user_data)
{
GWaterWaylandSource *self = (GWaterWaylandSource *)source;
GIOCondition revents;
revents = g_source_query_unix_fd(source, self->fd);
if ( ( self->error > 0 ) || ( revents & (G_IO_ERR | G_IO_HUP) ) )
{
errno = self->error;
self->error = 0;
if ( callback != NULL )
return callback(user_data);
return G_SOURCE_REMOVE;
}
if ( wl_display_dispatch_pending(self->display) < 0 )
{
if ( callback != NULL )
return callback(user_data);
return G_SOURCE_REMOVE;
}
return G_SOURCE_CONTINUE;
}
static void
_g_water_wayland_source_finalize(GSource *source)
{
GWaterWaylandSource *self = (GWaterWaylandSource *)source;
if ( self->display_owned )
wl_display_disconnect(self->display);
}
static GSourceFuncs _g_water_wayland_source_funcs = {
.prepare = _g_water_wayland_source_prepare,
.check = _g_water_wayland_source_check,
.dispatch = _g_water_wayland_source_dispatch,
.finalize = _g_water_wayland_source_finalize,
};
GWaterWaylandSource *
g_water_wayland_source_new(GMainContext *context, const gchar *name)
{
struct wl_display *display;
GWaterWaylandSource *self;
display = wl_display_connect(name);
if ( display == NULL )
return NULL;
self = g_water_wayland_source_new_for_display(context, display);
self->display_owned = TRUE;
return self;
}
GWaterWaylandSource *
g_water_wayland_source_new_for_display(GMainContext *context, struct wl_display *display)
{
g_return_val_if_fail(display != NULL, NULL);
GSource *source;
GWaterWaylandSource *self;
source = g_source_new(&_g_water_wayland_source_funcs, sizeof(GWaterWaylandSource));
self = (GWaterWaylandSource *)source;
self->display = display;
self->fd = g_source_add_unix_fd(source, wl_display_get_fd(self->display), G_IO_IN | G_IO_ERR | G_IO_HUP);
g_source_attach(source, context);
return self;
}
void
g_water_wayland_source_free(GWaterWaylandSource *self)
{
GSource * source = (GSource *)self;
g_return_if_fail(self != NULL);
g_source_destroy(source);
g_source_unref(source);
}
void
g_water_wayland_source_set_error_callback(GWaterWaylandSource *self, GSourceFunc callback, gpointer user_data, GDestroyNotify destroy_notify)
{
g_return_if_fail(self != NULL);
g_source_set_callback((GSource *)self, callback, user_data, destroy_notify);
}
struct wl_display *
g_water_wayland_source_get_display(GWaterWaylandSource *self)
{
g_return_val_if_fail(self != NULL, NULL);
return self->display;
}

View File

@ -1,42 +0,0 @@
/*
* libgwater-wayland - Wayland GSource
*
* Copyright © 2014-2017 Quentin "Sardem FF7" Glidic
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
*/
#ifndef __G_WATER_WAYLAND_H__
#define __G_WATER_WAYLAND_H__
G_BEGIN_DECLS
typedef struct _GWaterWaylandSource GWaterWaylandSource;
GWaterWaylandSource *g_water_wayland_source_new(GMainContext *context, const gchar *name);
GWaterWaylandSource *g_water_wayland_source_new_for_display(GMainContext *context, struct wl_display *display);
void g_water_wayland_source_free(GWaterWaylandSource *self);
void g_water_wayland_source_set_error_callback(GWaterWaylandSource *self, GSourceFunc callback, gpointer user_data, GDestroyNotify destroy_notify);
struct wl_display *g_water_wayland_source_get_display(GWaterWaylandSource *source);
G_END_DECLS
#endif /* __G_WATER_WAYLAND_H__ */

View File

@ -1,150 +0,0 @@
#define _POSIX_C_SOURCE 200112L
#include <cairo/cairo.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <time.h>
#include <unistd.h>
#include <wayland-client.h>
#include <string.h>
#include "pool-buffer.h"
static void randname(char *buf) {
struct timespec ts;
clock_gettime(CLOCK_REALTIME, &ts);
long r = ts.tv_nsec;
for (int i = 0; i < 6; ++i) {
buf[i] = 'A'+(r&15)+(r&16)*2;
r >>= 5;
}
}
static int anonymous_shm_open(void) {
char name[] = "/dunst-XXXXXX";
int retries = 100;
do {
randname(name + strlen(name) - 6);
--retries;
// shm_open guarantees that O_CLOEXEC is set
int fd = shm_open(name, O_RDWR | O_CREAT | O_EXCL, 0600);
if (fd >= 0) {
shm_unlink(name);
return fd;
}
} while (retries > 0 && errno == EEXIST);
return -1;
}
static int create_shm_file(off_t size) {
int fd = anonymous_shm_open();
if (fd < 0) {
return fd;
}
if (ftruncate(fd, size) < 0) {
close(fd);
return -1;
}
return fd;
}
static void buffer_handle_release(void *data, struct wl_buffer *wl_buffer) {
struct pool_buffer *buffer = data;
buffer->busy = false;
}
static const struct wl_buffer_listener buffer_listener = {
.release = buffer_handle_release,
};
static struct pool_buffer *create_buffer(struct wl_shm *shm,
struct pool_buffer *buf, int32_t width, int32_t height) {
const enum wl_shm_format wl_fmt = WL_SHM_FORMAT_ARGB8888;
const cairo_format_t cairo_fmt = CAIRO_FORMAT_ARGB32;
uint32_t stride = cairo_format_stride_for_width(cairo_fmt, width);
size_t size = stride * height;
void *data = NULL;
if (size > 0) {
int fd = create_shm_file(size);
if (fd == -1) {
return NULL;
}
data = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (data == MAP_FAILED) {
close(fd);
return NULL;
}
struct wl_shm_pool *pool = wl_shm_create_pool(shm, fd, size);
buf->buffer =
wl_shm_pool_create_buffer(pool, 0, width, height, stride, wl_fmt);
wl_buffer_add_listener(buf->buffer, &buffer_listener, buf);
wl_shm_pool_destroy(pool);
close(fd);
}
buf->data = data;
buf->size = size;
buf->width = width;
buf->height = height;
buf->surface = cairo_image_surface_create_for_data(data, cairo_fmt, width,
height, stride);
buf->cairo = cairo_create(buf->surface);
buf->pango = pango_cairo_create_context(buf->cairo);
return buf;
}
void finish_buffer(struct pool_buffer *buffer) {
if (buffer->buffer) {
wl_buffer_destroy(buffer->buffer);
}
if (buffer->cairo) {
cairo_destroy(buffer->cairo);
}
if (buffer->surface) {
cairo_surface_destroy(buffer->surface);
}
if (buffer->pango) {
g_object_unref(buffer->pango);
}
if (buffer->data) {
munmap(buffer->data, buffer->size);
}
memset(buffer, 0, sizeof(struct pool_buffer));
}
struct pool_buffer *get_next_buffer(struct wl_shm *shm,
struct pool_buffer pool[static 2], uint32_t width, uint32_t height) {
struct pool_buffer *buffer = NULL;
for (size_t i = 0; i < 2; ++i) {
if (pool[i].busy) {
continue;
}
buffer = &pool[i];
}
if (!buffer) {
return NULL;
}
if (buffer->width != width || buffer->height != height) {
finish_buffer(buffer);
}
if (!buffer->buffer) {
if (!create_buffer(shm, buffer, width, height)) {
return NULL;
}
}
return buffer;
}
/* vim: set ft=c tabstop=8 shiftwidth=8 expandtab textwidth=0: */

View File

@ -1,26 +0,0 @@
#ifndef DUNST_POOL_BUFFER_H
#define DUNST_POOL_BUFFER_H
#include <cairo/cairo.h>
#include <pango/pangocairo.h>
#include <stdbool.h>
#include <stdint.h>
#include <wayland-client.h>
struct pool_buffer {
struct wl_buffer *buffer;
cairo_surface_t *surface;
cairo_t *cairo;
PangoContext *pango;
uint32_t width, height;
void *data;
size_t size;
bool busy;
};
struct pool_buffer *get_next_buffer(struct wl_shm *shm,
struct pool_buffer pool[static 2], uint32_t width, uint32_t height);
void finish_buffer(struct pool_buffer *buffer);
#endif
/* vim: set ft=c tabstop=8 shiftwidth=8 expandtab textwidth=0: */

View File

@ -1,233 +0,0 @@
/* Generated by wayland-scanner 1.18.0 */
#ifndef IDLE_CLIENT_PROTOCOL_H
#define IDLE_CLIENT_PROTOCOL_H
#include <stdint.h>
#include <stddef.h>
#include "wayland-client.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @page page_idle The idle protocol
* @section page_ifaces_idle Interfaces
* - @subpage page_iface_org_kde_kwin_idle - User idle time manager
* - @subpage page_iface_org_kde_kwin_idle_timeout -
* @section page_copyright_idle Copyright
* <pre>
*
* Copyright (C) 2015 Martin Gräßlin
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* </pre>
*/
struct org_kde_kwin_idle;
struct org_kde_kwin_idle_timeout;
struct wl_seat;
/**
* @page page_iface_org_kde_kwin_idle org_kde_kwin_idle
* @section page_iface_org_kde_kwin_idle_desc Description
*
* This interface allows to monitor user idle time on a given seat. The interface
* allows to register timers which trigger after no user activity was registered
* on the seat for a given interval. It notifies when user activity resumes.
*
* This is useful for applications wanting to perform actions when the user is not
* interacting with the system, e.g. chat applications setting the user as away, power
* management features to dim screen, etc..
* @section page_iface_org_kde_kwin_idle_api API
* See @ref iface_org_kde_kwin_idle.
*/
/**
* @defgroup iface_org_kde_kwin_idle The org_kde_kwin_idle interface
*
* This interface allows to monitor user idle time on a given seat. The interface
* allows to register timers which trigger after no user activity was registered
* on the seat for a given interval. It notifies when user activity resumes.
*
* This is useful for applications wanting to perform actions when the user is not
* interacting with the system, e.g. chat applications setting the user as away, power
* management features to dim screen, etc..
*/
extern const struct wl_interface org_kde_kwin_idle_interface;
/**
* @page page_iface_org_kde_kwin_idle_timeout org_kde_kwin_idle_timeout
* @section page_iface_org_kde_kwin_idle_timeout_api API
* See @ref iface_org_kde_kwin_idle_timeout.
*/
/**
* @defgroup iface_org_kde_kwin_idle_timeout The org_kde_kwin_idle_timeout interface
*/
extern const struct wl_interface org_kde_kwin_idle_timeout_interface;
#define ORG_KDE_KWIN_IDLE_GET_IDLE_TIMEOUT 0
/**
* @ingroup iface_org_kde_kwin_idle
*/
#define ORG_KDE_KWIN_IDLE_GET_IDLE_TIMEOUT_SINCE_VERSION 1
/** @ingroup iface_org_kde_kwin_idle */
static inline void
org_kde_kwin_idle_set_user_data(struct org_kde_kwin_idle *org_kde_kwin_idle, void *user_data)
{
wl_proxy_set_user_data((struct wl_proxy *) org_kde_kwin_idle, user_data);
}
/** @ingroup iface_org_kde_kwin_idle */
static inline void *
org_kde_kwin_idle_get_user_data(struct org_kde_kwin_idle *org_kde_kwin_idle)
{
return wl_proxy_get_user_data((struct wl_proxy *) org_kde_kwin_idle);
}
static inline uint32_t
org_kde_kwin_idle_get_version(struct org_kde_kwin_idle *org_kde_kwin_idle)
{
return wl_proxy_get_version((struct wl_proxy *) org_kde_kwin_idle);
}
/** @ingroup iface_org_kde_kwin_idle */
static inline void
org_kde_kwin_idle_destroy(struct org_kde_kwin_idle *org_kde_kwin_idle)
{
wl_proxy_destroy((struct wl_proxy *) org_kde_kwin_idle);
}
/**
* @ingroup iface_org_kde_kwin_idle
*/
static inline struct org_kde_kwin_idle_timeout *
org_kde_kwin_idle_get_idle_timeout(struct org_kde_kwin_idle *org_kde_kwin_idle, struct wl_seat *seat, uint32_t timeout)
{
struct wl_proxy *id;
id = wl_proxy_marshal_constructor((struct wl_proxy *) org_kde_kwin_idle,
ORG_KDE_KWIN_IDLE_GET_IDLE_TIMEOUT, &org_kde_kwin_idle_timeout_interface, NULL, seat, timeout);
return (struct org_kde_kwin_idle_timeout *) id;
}
/**
* @ingroup iface_org_kde_kwin_idle_timeout
* @struct org_kde_kwin_idle_timeout_listener
*/
struct org_kde_kwin_idle_timeout_listener {
/**
* Triggered when there has not been any user activity in the requested idle time interval
*
*
*/
void (*idle)(void *data,
struct org_kde_kwin_idle_timeout *org_kde_kwin_idle_timeout);
/**
* Triggered on the first user activity after an idle event
*
*
*/
void (*resumed)(void *data,
struct org_kde_kwin_idle_timeout *org_kde_kwin_idle_timeout);
};
/**
* @ingroup iface_org_kde_kwin_idle_timeout
*/
static inline int
org_kde_kwin_idle_timeout_add_listener(struct org_kde_kwin_idle_timeout *org_kde_kwin_idle_timeout,
const struct org_kde_kwin_idle_timeout_listener *listener, void *data)
{
return wl_proxy_add_listener((struct wl_proxy *) org_kde_kwin_idle_timeout,
(void (**)(void)) listener, data);
}
#define ORG_KDE_KWIN_IDLE_TIMEOUT_RELEASE 0
#define ORG_KDE_KWIN_IDLE_TIMEOUT_SIMULATE_USER_ACTIVITY 1
/**
* @ingroup iface_org_kde_kwin_idle_timeout
*/
#define ORG_KDE_KWIN_IDLE_TIMEOUT_IDLE_SINCE_VERSION 1
/**
* @ingroup iface_org_kde_kwin_idle_timeout
*/
#define ORG_KDE_KWIN_IDLE_TIMEOUT_RESUMED_SINCE_VERSION 1
/**
* @ingroup iface_org_kde_kwin_idle_timeout
*/
#define ORG_KDE_KWIN_IDLE_TIMEOUT_RELEASE_SINCE_VERSION 1
/**
* @ingroup iface_org_kde_kwin_idle_timeout
*/
#define ORG_KDE_KWIN_IDLE_TIMEOUT_SIMULATE_USER_ACTIVITY_SINCE_VERSION 1
/** @ingroup iface_org_kde_kwin_idle_timeout */
static inline void
org_kde_kwin_idle_timeout_set_user_data(struct org_kde_kwin_idle_timeout *org_kde_kwin_idle_timeout, void *user_data)
{
wl_proxy_set_user_data((struct wl_proxy *) org_kde_kwin_idle_timeout, user_data);
}
/** @ingroup iface_org_kde_kwin_idle_timeout */
static inline void *
org_kde_kwin_idle_timeout_get_user_data(struct org_kde_kwin_idle_timeout *org_kde_kwin_idle_timeout)
{
return wl_proxy_get_user_data((struct wl_proxy *) org_kde_kwin_idle_timeout);
}
static inline uint32_t
org_kde_kwin_idle_timeout_get_version(struct org_kde_kwin_idle_timeout *org_kde_kwin_idle_timeout)
{
return wl_proxy_get_version((struct wl_proxy *) org_kde_kwin_idle_timeout);
}
/** @ingroup iface_org_kde_kwin_idle_timeout */
static inline void
org_kde_kwin_idle_timeout_destroy(struct org_kde_kwin_idle_timeout *org_kde_kwin_idle_timeout)
{
wl_proxy_destroy((struct wl_proxy *) org_kde_kwin_idle_timeout);
}
/**
* @ingroup iface_org_kde_kwin_idle_timeout
*/
static inline void
org_kde_kwin_idle_timeout_release(struct org_kde_kwin_idle_timeout *org_kde_kwin_idle_timeout)
{
wl_proxy_marshal((struct wl_proxy *) org_kde_kwin_idle_timeout,
ORG_KDE_KWIN_IDLE_TIMEOUT_RELEASE);
wl_proxy_destroy((struct wl_proxy *) org_kde_kwin_idle_timeout);
}
/**
* @ingroup iface_org_kde_kwin_idle_timeout
*/
static inline void
org_kde_kwin_idle_timeout_simulate_user_activity(struct org_kde_kwin_idle_timeout *org_kde_kwin_idle_timeout)
{
wl_proxy_marshal((struct wl_proxy *) org_kde_kwin_idle_timeout,
ORG_KDE_KWIN_IDLE_TIMEOUT_SIMULATE_USER_ACTIVITY);
}
#ifdef __cplusplus
}
#endif
#endif

View File

@ -1,68 +0,0 @@
/* Generated by wayland-scanner 1.18.0 */
/*
* Copyright (C) 2015 Martin Gräßlin
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <stdlib.h>
#include <stdint.h>
#include "wayland-util.h"
#ifndef __has_attribute
# define __has_attribute(x) 0 /* Compatibility with non-clang compilers. */
#endif
#if (__has_attribute(visibility) || defined(__GNUC__) && __GNUC__ >= 4)
#define WL_PRIVATE __attribute__ ((visibility("hidden")))
#else
#define WL_PRIVATE
#endif
extern const struct wl_interface org_kde_kwin_idle_timeout_interface;
extern const struct wl_interface wl_seat_interface;
static const struct wl_interface *idle_types[] = {
&org_kde_kwin_idle_timeout_interface,
&wl_seat_interface,
NULL,
};
static const struct wl_message org_kde_kwin_idle_requests[] = {
{ "get_idle_timeout", "nou", idle_types + 0 },
};
WL_PRIVATE const struct wl_interface org_kde_kwin_idle_interface = {
"org_kde_kwin_idle", 1,
1, org_kde_kwin_idle_requests,
0, NULL,
};
static const struct wl_message org_kde_kwin_idle_timeout_requests[] = {
{ "release", "", idle_types + 0 },
{ "simulate_user_activity", "", idle_types + 0 },
};
static const struct wl_message org_kde_kwin_idle_timeout_events[] = {
{ "idle", "", idle_types + 0 },
{ "resumed", "", idle_types + 0 },
};
WL_PRIVATE const struct wl_interface org_kde_kwin_idle_timeout_interface = {
"org_kde_kwin_idle_timeout", 1,
2, org_kde_kwin_idle_timeout_requests,
2, org_kde_kwin_idle_timeout_events,
};

View File

@ -1,49 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<protocol name="idle">
<copyright><![CDATA[
Copyright (C) 2015 Martin Gräßlin
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 2.1 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
]]></copyright>
<interface name="org_kde_kwin_idle" version="1">
<description summary="User idle time manager">
This interface allows to monitor user idle time on a given seat. The interface
allows to register timers which trigger after no user activity was registered
on the seat for a given interval. It notifies when user activity resumes.
This is useful for applications wanting to perform actions when the user is not
interacting with the system, e.g. chat applications setting the user as away, power
management features to dim screen, etc..
</description>
<request name="get_idle_timeout">
<arg name="id" type="new_id" interface="org_kde_kwin_idle_timeout"/>
<arg name="seat" type="object" interface="wl_seat"/>
<arg name="timeout" type="uint" summary="The idle timeout in msec"/>
</request>
</interface>
<interface name="org_kde_kwin_idle_timeout" version="1">
<request name="release" type="destructor">
<description summary="release the timeout object"/>
</request>
<request name="simulate_user_activity">
<description summary="Simulates user activity for this timeout, behaves just like real user activity on the seat"/>
</request>
<event name="idle">
<description summary="Triggered when there has not been any user activity in the requested idle time interval"/>
</event>
<event name="resumed">
<description summary="Triggered on the first user activity after an idle event"/>
</event>
</interface>
</protocol>

View File

@ -1,611 +0,0 @@
/* Generated by wayland-scanner 1.18.0 */
#ifndef WLR_FOREIGN_TOPLEVEL_MANAGEMENT_UNSTABLE_V1_CLIENT_PROTOCOL_H
#define WLR_FOREIGN_TOPLEVEL_MANAGEMENT_UNSTABLE_V1_CLIENT_PROTOCOL_H
#include <stdint.h>
#include <stddef.h>
#include "wayland-client.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @page page_wlr_foreign_toplevel_management_unstable_v1 The wlr_foreign_toplevel_management_unstable_v1 protocol
* @section page_ifaces_wlr_foreign_toplevel_management_unstable_v1 Interfaces
* - @subpage page_iface_zwlr_foreign_toplevel_manager_v1 - list and control opened apps
* - @subpage page_iface_zwlr_foreign_toplevel_handle_v1 - an opened toplevel
* @section page_copyright_wlr_foreign_toplevel_management_unstable_v1 Copyright
* <pre>
*
* Copyright © 2018 Ilia Bozhinov
*
* Permission to use, copy, modify, distribute, and sell this
* software and its documentation for any purpose is hereby granted
* without fee, provided that the above copyright notice appear in
* all copies and that both that copyright notice and this permission
* notice appear in supporting documentation, and that the name of
* the copyright holders not be used in advertising or publicity
* pertaining to distribution of the software without specific,
* written prior permission. The copyright holders make no
* representations about the suitability of this software for any
* purpose. It is provided "as is" without express or implied
* warranty.
*
* THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS
* SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY
* SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
* AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
* ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
* THIS SOFTWARE.
* </pre>
*/
struct wl_output;
struct wl_seat;
struct wl_surface;
struct zwlr_foreign_toplevel_handle_v1;
struct zwlr_foreign_toplevel_manager_v1;
/**
* @page page_iface_zwlr_foreign_toplevel_manager_v1 zwlr_foreign_toplevel_manager_v1
* @section page_iface_zwlr_foreign_toplevel_manager_v1_desc Description
*
* The purpose of this protocol is to enable the creation of taskbars
* and docks by providing them with a list of opened applications and
* letting them request certain actions on them, like maximizing, etc.
*
* After a client binds the zwlr_foreign_toplevel_manager_v1, each opened
* toplevel window will be sent via the toplevel event
* @section page_iface_zwlr_foreign_toplevel_manager_v1_api API
* See @ref iface_zwlr_foreign_toplevel_manager_v1.
*/
/**
* @defgroup iface_zwlr_foreign_toplevel_manager_v1 The zwlr_foreign_toplevel_manager_v1 interface
*
* The purpose of this protocol is to enable the creation of taskbars
* and docks by providing them with a list of opened applications and
* letting them request certain actions on them, like maximizing, etc.
*
* After a client binds the zwlr_foreign_toplevel_manager_v1, each opened
* toplevel window will be sent via the toplevel event
*/
extern const struct wl_interface zwlr_foreign_toplevel_manager_v1_interface;
/**
* @page page_iface_zwlr_foreign_toplevel_handle_v1 zwlr_foreign_toplevel_handle_v1
* @section page_iface_zwlr_foreign_toplevel_handle_v1_desc Description
*
* A zwlr_foreign_toplevel_handle_v1 object represents an opened toplevel
* window. Each app may have multiple opened toplevels.
*
* Each toplevel has a list of outputs it is visible on, conveyed to the
* client with the output_enter and output_leave events.
* @section page_iface_zwlr_foreign_toplevel_handle_v1_api API
* See @ref iface_zwlr_foreign_toplevel_handle_v1.
*/
/**
* @defgroup iface_zwlr_foreign_toplevel_handle_v1 The zwlr_foreign_toplevel_handle_v1 interface
*
* A zwlr_foreign_toplevel_handle_v1 object represents an opened toplevel
* window. Each app may have multiple opened toplevels.
*
* Each toplevel has a list of outputs it is visible on, conveyed to the
* client with the output_enter and output_leave events.
*/
extern const struct wl_interface zwlr_foreign_toplevel_handle_v1_interface;
/**
* @ingroup iface_zwlr_foreign_toplevel_manager_v1
* @struct zwlr_foreign_toplevel_manager_v1_listener
*/
struct zwlr_foreign_toplevel_manager_v1_listener {
/**
* a toplevel has been created
*
* This event is emitted whenever a new toplevel window is
* created. It is emitted for all toplevels, regardless of the app
* that has created them.
*
* All initial details of the toplevel(title, app_id, states, etc.)
* will be sent immediately after this event via the corresponding
* events in zwlr_foreign_toplevel_handle_v1.
*/
void (*toplevel)(void *data,
struct zwlr_foreign_toplevel_manager_v1 *zwlr_foreign_toplevel_manager_v1,
struct zwlr_foreign_toplevel_handle_v1 *toplevel);
/**
* the compositor has finished with the toplevel manager
*
* This event indicates that the compositor is done sending
* events to the zwlr_foreign_toplevel_manager_v1. The server will
* destroy the object immediately after sending this request, so it
* will become invalid and the client should free any resources
* associated with it.
*/
void (*finished)(void *data,
struct zwlr_foreign_toplevel_manager_v1 *zwlr_foreign_toplevel_manager_v1);
};
/**
* @ingroup iface_zwlr_foreign_toplevel_manager_v1
*/
static inline int
zwlr_foreign_toplevel_manager_v1_add_listener(struct zwlr_foreign_toplevel_manager_v1 *zwlr_foreign_toplevel_manager_v1,
const struct zwlr_foreign_toplevel_manager_v1_listener *listener, void *data)
{
return wl_proxy_add_listener((struct wl_proxy *) zwlr_foreign_toplevel_manager_v1,
(void (**)(void)) listener, data);
}
#define ZWLR_FOREIGN_TOPLEVEL_MANAGER_V1_STOP 0
/**
* @ingroup iface_zwlr_foreign_toplevel_manager_v1
*/
#define ZWLR_FOREIGN_TOPLEVEL_MANAGER_V1_TOPLEVEL_SINCE_VERSION 1
/**
* @ingroup iface_zwlr_foreign_toplevel_manager_v1
*/
#define ZWLR_FOREIGN_TOPLEVEL_MANAGER_V1_FINISHED_SINCE_VERSION 1
/**
* @ingroup iface_zwlr_foreign_toplevel_manager_v1
*/
#define ZWLR_FOREIGN_TOPLEVEL_MANAGER_V1_STOP_SINCE_VERSION 1
/** @ingroup iface_zwlr_foreign_toplevel_manager_v1 */
static inline void
zwlr_foreign_toplevel_manager_v1_set_user_data(struct zwlr_foreign_toplevel_manager_v1 *zwlr_foreign_toplevel_manager_v1, void *user_data)
{
wl_proxy_set_user_data((struct wl_proxy *) zwlr_foreign_toplevel_manager_v1, user_data);
}
/** @ingroup iface_zwlr_foreign_toplevel_manager_v1 */
static inline void *
zwlr_foreign_toplevel_manager_v1_get_user_data(struct zwlr_foreign_toplevel_manager_v1 *zwlr_foreign_toplevel_manager_v1)
{
return wl_proxy_get_user_data((struct wl_proxy *) zwlr_foreign_toplevel_manager_v1);
}
static inline uint32_t
zwlr_foreign_toplevel_manager_v1_get_version(struct zwlr_foreign_toplevel_manager_v1 *zwlr_foreign_toplevel_manager_v1)
{
return wl_proxy_get_version((struct wl_proxy *) zwlr_foreign_toplevel_manager_v1);
}
/** @ingroup iface_zwlr_foreign_toplevel_manager_v1 */
static inline void
zwlr_foreign_toplevel_manager_v1_destroy(struct zwlr_foreign_toplevel_manager_v1 *zwlr_foreign_toplevel_manager_v1)
{
wl_proxy_destroy((struct wl_proxy *) zwlr_foreign_toplevel_manager_v1);
}
/**
* @ingroup iface_zwlr_foreign_toplevel_manager_v1
*
* Indicates the client no longer wishes to receive events for new toplevels.
* However the compositor may emit further toplevel_created events, until
* the finished event is emitted.
*
* The client must not send any more requests after this one.
*/
static inline void
zwlr_foreign_toplevel_manager_v1_stop(struct zwlr_foreign_toplevel_manager_v1 *zwlr_foreign_toplevel_manager_v1)
{
wl_proxy_marshal((struct wl_proxy *) zwlr_foreign_toplevel_manager_v1,
ZWLR_FOREIGN_TOPLEVEL_MANAGER_V1_STOP);
}
#ifndef ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_ENUM
#define ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_ENUM
/**
* @ingroup iface_zwlr_foreign_toplevel_handle_v1
* types of states on the toplevel
*
* The different states that a toplevel can have. These have the same meaning
* as the states with the same names defined in xdg-toplevel
*/
enum zwlr_foreign_toplevel_handle_v1_state {
/**
* the toplevel is maximized
*/
ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_MAXIMIZED = 0,
/**
* the toplevel is minimized
*/
ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_MINIMIZED = 1,
/**
* the toplevel is active
*/
ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_ACTIVATED = 2,
/**
* the toplevel is fullscreen
* @since 2
*/
ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_FULLSCREEN = 3,
};
/**
* @ingroup iface_zwlr_foreign_toplevel_handle_v1
*/
#define ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_FULLSCREEN_SINCE_VERSION 2
#endif /* ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_ENUM */
#ifndef ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_ERROR_ENUM
#define ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_ERROR_ENUM
enum zwlr_foreign_toplevel_handle_v1_error {
/**
* the provided rectangle is invalid
*/
ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_ERROR_INVALID_RECTANGLE = 0,
};
#endif /* ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_ERROR_ENUM */
/**
* @ingroup iface_zwlr_foreign_toplevel_handle_v1
* @struct zwlr_foreign_toplevel_handle_v1_listener
*/
struct zwlr_foreign_toplevel_handle_v1_listener {
/**
* title change
*
* This event is emitted whenever the title of the toplevel
* changes.
*/
void (*title)(void *data,
struct zwlr_foreign_toplevel_handle_v1 *zwlr_foreign_toplevel_handle_v1,
const char *title);
/**
* app-id change
*
* This event is emitted whenever the app-id of the toplevel
* changes.
*/
void (*app_id)(void *data,
struct zwlr_foreign_toplevel_handle_v1 *zwlr_foreign_toplevel_handle_v1,
const char *app_id);
/**
* toplevel entered an output
*
* This event is emitted whenever the toplevel becomes visible on
* the given output. A toplevel may be visible on multiple outputs.
*/
void (*output_enter)(void *data,
struct zwlr_foreign_toplevel_handle_v1 *zwlr_foreign_toplevel_handle_v1,
struct wl_output *output);
/**
* toplevel left an output
*
* This event is emitted whenever the toplevel stops being
* visible on the given output. It is guaranteed that an
* entered-output event with the same output has been emitted
* before this event.
*/
void (*output_leave)(void *data,
struct zwlr_foreign_toplevel_handle_v1 *zwlr_foreign_toplevel_handle_v1,
struct wl_output *output);
/**
* the toplevel state changed
*
* This event is emitted immediately after the
* zlw_foreign_toplevel_handle_v1 is created and each time the
* toplevel state changes, either because of a compositor action or
* because of a request in this protocol.
*/
void (*state)(void *data,
struct zwlr_foreign_toplevel_handle_v1 *zwlr_foreign_toplevel_handle_v1,
struct wl_array *state);
/**
* all information about the toplevel has been sent
*
* This event is sent after all changes in the toplevel state
* have been sent.
*
* This allows changes to the zwlr_foreign_toplevel_handle_v1
* properties to be seen as atomic, even if they happen via
* multiple events.
*/
void (*done)(void *data,
struct zwlr_foreign_toplevel_handle_v1 *zwlr_foreign_toplevel_handle_v1);
/**
* this toplevel has been destroyed
*
* This event means the toplevel has been destroyed. It is
* guaranteed there won't be any more events for this
* zwlr_foreign_toplevel_handle_v1. The toplevel itself becomes
* inert so any requests will be ignored except the destroy
* request.
*/
void (*closed)(void *data,
struct zwlr_foreign_toplevel_handle_v1 *zwlr_foreign_toplevel_handle_v1);
/**
* parent change
*
* This event is emitted whenever the parent of the toplevel
* changes.
*
* No event is emitted when the parent handle is destroyed by the
* client.
* @since 3
*/
void (*parent)(void *data,
struct zwlr_foreign_toplevel_handle_v1 *zwlr_foreign_toplevel_handle_v1,
struct zwlr_foreign_toplevel_handle_v1 *parent);
};
/**
* @ingroup iface_zwlr_foreign_toplevel_handle_v1
*/
static inline int
zwlr_foreign_toplevel_handle_v1_add_listener(struct zwlr_foreign_toplevel_handle_v1 *zwlr_foreign_toplevel_handle_v1,
const struct zwlr_foreign_toplevel_handle_v1_listener *listener, void *data)
{
return wl_proxy_add_listener((struct wl_proxy *) zwlr_foreign_toplevel_handle_v1,
(void (**)(void)) listener, data);
}
#define ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_SET_MAXIMIZED 0
#define ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_UNSET_MAXIMIZED 1
#define ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_SET_MINIMIZED 2
#define ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_UNSET_MINIMIZED 3
#define ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_ACTIVATE 4
#define ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_CLOSE 5
#define ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_SET_RECTANGLE 6
#define ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_DESTROY 7
#define ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_SET_FULLSCREEN 8
#define ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_UNSET_FULLSCREEN 9
/**
* @ingroup iface_zwlr_foreign_toplevel_handle_v1
*/
#define ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_TITLE_SINCE_VERSION 1
/**
* @ingroup iface_zwlr_foreign_toplevel_handle_v1
*/
#define ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_APP_ID_SINCE_VERSION 1
/**
* @ingroup iface_zwlr_foreign_toplevel_handle_v1
*/
#define ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_OUTPUT_ENTER_SINCE_VERSION 1
/**
* @ingroup iface_zwlr_foreign_toplevel_handle_v1
*/
#define ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_OUTPUT_LEAVE_SINCE_VERSION 1
/**
* @ingroup iface_zwlr_foreign_toplevel_handle_v1
*/
#define ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_SINCE_VERSION 1
/**
* @ingroup iface_zwlr_foreign_toplevel_handle_v1
*/
#define ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_DONE_SINCE_VERSION 1
/**
* @ingroup iface_zwlr_foreign_toplevel_handle_v1
*/
#define ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_CLOSED_SINCE_VERSION 1
/**
* @ingroup iface_zwlr_foreign_toplevel_handle_v1
*/
#define ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_PARENT_SINCE_VERSION 3
/**
* @ingroup iface_zwlr_foreign_toplevel_handle_v1
*/
#define ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_SET_MAXIMIZED_SINCE_VERSION 1
/**
* @ingroup iface_zwlr_foreign_toplevel_handle_v1
*/
#define ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_UNSET_MAXIMIZED_SINCE_VERSION 1
/**
* @ingroup iface_zwlr_foreign_toplevel_handle_v1
*/
#define ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_SET_MINIMIZED_SINCE_VERSION 1
/**
* @ingroup iface_zwlr_foreign_toplevel_handle_v1
*/
#define ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_UNSET_MINIMIZED_SINCE_VERSION 1
/**
* @ingroup iface_zwlr_foreign_toplevel_handle_v1
*/
#define ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_ACTIVATE_SINCE_VERSION 1
/**
* @ingroup iface_zwlr_foreign_toplevel_handle_v1
*/
#define ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_CLOSE_SINCE_VERSION 1
/**
* @ingroup iface_zwlr_foreign_toplevel_handle_v1
*/
#define ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_SET_RECTANGLE_SINCE_VERSION 1
/**
* @ingroup iface_zwlr_foreign_toplevel_handle_v1
*/
#define ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_DESTROY_SINCE_VERSION 1
/**
* @ingroup iface_zwlr_foreign_toplevel_handle_v1
*/
#define ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_SET_FULLSCREEN_SINCE_VERSION 2
/**
* @ingroup iface_zwlr_foreign_toplevel_handle_v1
*/
#define ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_UNSET_FULLSCREEN_SINCE_VERSION 2
/** @ingroup iface_zwlr_foreign_toplevel_handle_v1 */
static inline void
zwlr_foreign_toplevel_handle_v1_set_user_data(struct zwlr_foreign_toplevel_handle_v1 *zwlr_foreign_toplevel_handle_v1, void *user_data)
{
wl_proxy_set_user_data((struct wl_proxy *) zwlr_foreign_toplevel_handle_v1, user_data);
}
/** @ingroup iface_zwlr_foreign_toplevel_handle_v1 */
static inline void *
zwlr_foreign_toplevel_handle_v1_get_user_data(struct zwlr_foreign_toplevel_handle_v1 *zwlr_foreign_toplevel_handle_v1)
{
return wl_proxy_get_user_data((struct wl_proxy *) zwlr_foreign_toplevel_handle_v1);
}
static inline uint32_t
zwlr_foreign_toplevel_handle_v1_get_version(struct zwlr_foreign_toplevel_handle_v1 *zwlr_foreign_toplevel_handle_v1)
{
return wl_proxy_get_version((struct wl_proxy *) zwlr_foreign_toplevel_handle_v1);
}
/**
* @ingroup iface_zwlr_foreign_toplevel_handle_v1
*
* Requests that the toplevel be maximized. If the maximized state actually
* changes, this will be indicated by the state event.
*/
static inline void
zwlr_foreign_toplevel_handle_v1_set_maximized(struct zwlr_foreign_toplevel_handle_v1 *zwlr_foreign_toplevel_handle_v1)
{
wl_proxy_marshal((struct wl_proxy *) zwlr_foreign_toplevel_handle_v1,
ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_SET_MAXIMIZED);
}
/**
* @ingroup iface_zwlr_foreign_toplevel_handle_v1
*
* Requests that the toplevel be unmaximized. If the maximized state actually
* changes, this will be indicated by the state event.
*/
static inline void
zwlr_foreign_toplevel_handle_v1_unset_maximized(struct zwlr_foreign_toplevel_handle_v1 *zwlr_foreign_toplevel_handle_v1)
{
wl_proxy_marshal((struct wl_proxy *) zwlr_foreign_toplevel_handle_v1,
ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_UNSET_MAXIMIZED);
}
/**
* @ingroup iface_zwlr_foreign_toplevel_handle_v1
*
* Requests that the toplevel be minimized. If the minimized state actually
* changes, this will be indicated by the state event.
*/
static inline void
zwlr_foreign_toplevel_handle_v1_set_minimized(struct zwlr_foreign_toplevel_handle_v1 *zwlr_foreign_toplevel_handle_v1)
{
wl_proxy_marshal((struct wl_proxy *) zwlr_foreign_toplevel_handle_v1,
ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_SET_MINIMIZED);
}
/**
* @ingroup iface_zwlr_foreign_toplevel_handle_v1
*
* Requests that the toplevel be unminimized. If the minimized state actually
* changes, this will be indicated by the state event.
*/
static inline void
zwlr_foreign_toplevel_handle_v1_unset_minimized(struct zwlr_foreign_toplevel_handle_v1 *zwlr_foreign_toplevel_handle_v1)
{
wl_proxy_marshal((struct wl_proxy *) zwlr_foreign_toplevel_handle_v1,
ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_UNSET_MINIMIZED);
}
/**
* @ingroup iface_zwlr_foreign_toplevel_handle_v1
*
* Request that this toplevel be activated on the given seat.
* There is no guarantee the toplevel will be actually activated.
*/
static inline void
zwlr_foreign_toplevel_handle_v1_activate(struct zwlr_foreign_toplevel_handle_v1 *zwlr_foreign_toplevel_handle_v1, struct wl_seat *seat)
{
wl_proxy_marshal((struct wl_proxy *) zwlr_foreign_toplevel_handle_v1,
ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_ACTIVATE, seat);
}
/**
* @ingroup iface_zwlr_foreign_toplevel_handle_v1
*
* Send a request to the toplevel to close itself. The compositor would
* typically use a shell-specific method to carry out this request, for
* example by sending the xdg_toplevel.close event. However, this gives
* no guarantees the toplevel will actually be destroyed. If and when
* this happens, the zwlr_foreign_toplevel_handle_v1.closed event will
* be emitted.
*/
static inline void
zwlr_foreign_toplevel_handle_v1_close(struct zwlr_foreign_toplevel_handle_v1 *zwlr_foreign_toplevel_handle_v1)
{
wl_proxy_marshal((struct wl_proxy *) zwlr_foreign_toplevel_handle_v1,
ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_CLOSE);
}
/**
* @ingroup iface_zwlr_foreign_toplevel_handle_v1
*
* The rectangle of the surface specified in this request corresponds to
* the place where the app using this protocol represents the given toplevel.
* It can be used by the compositor as a hint for some operations, e.g
* minimizing. The client is however not required to set this, in which
* case the compositor is free to decide some default value.
*
* If the client specifies more than one rectangle, only the last one is
* considered.
*
* The dimensions are given in surface-local coordinates.
* Setting width=height=0 removes the already-set rectangle.
*/
static inline void
zwlr_foreign_toplevel_handle_v1_set_rectangle(struct zwlr_foreign_toplevel_handle_v1 *zwlr_foreign_toplevel_handle_v1, struct wl_surface *surface, int32_t x, int32_t y, int32_t width, int32_t height)
{
wl_proxy_marshal((struct wl_proxy *) zwlr_foreign_toplevel_handle_v1,
ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_SET_RECTANGLE, surface, x, y, width, height);
}
/**
* @ingroup iface_zwlr_foreign_toplevel_handle_v1
*
* Destroys the zwlr_foreign_toplevel_handle_v1 object.
*
* This request should be called either when the client does not want to
* use the toplevel anymore or after the closed event to finalize the
* destruction of the object.
*/
static inline void
zwlr_foreign_toplevel_handle_v1_destroy(struct zwlr_foreign_toplevel_handle_v1 *zwlr_foreign_toplevel_handle_v1)
{
wl_proxy_marshal((struct wl_proxy *) zwlr_foreign_toplevel_handle_v1,
ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_DESTROY);
wl_proxy_destroy((struct wl_proxy *) zwlr_foreign_toplevel_handle_v1);
}
/**
* @ingroup iface_zwlr_foreign_toplevel_handle_v1
*
* Requests that the toplevel be fullscreened on the given output. If the
* fullscreen state and/or the outputs the toplevel is visible on actually
* change, this will be indicated by the state and output_enter/leave
* events.
*
* The output parameter is only a hint to the compositor. Also, if output
* is NULL, the compositor should decide which output the toplevel will be
* fullscreened on, if at all.
*/
static inline void
zwlr_foreign_toplevel_handle_v1_set_fullscreen(struct zwlr_foreign_toplevel_handle_v1 *zwlr_foreign_toplevel_handle_v1, struct wl_output *output)
{
wl_proxy_marshal((struct wl_proxy *) zwlr_foreign_toplevel_handle_v1,
ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_SET_FULLSCREEN, output);
}
/**
* @ingroup iface_zwlr_foreign_toplevel_handle_v1
*
* Requests that the toplevel be unfullscreened. If the fullscreen state
* actually changes, this will be indicated by the state event.
*/
static inline void
zwlr_foreign_toplevel_handle_v1_unset_fullscreen(struct zwlr_foreign_toplevel_handle_v1 *zwlr_foreign_toplevel_handle_v1)
{
wl_proxy_marshal((struct wl_proxy *) zwlr_foreign_toplevel_handle_v1,
ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_UNSET_FULLSCREEN);
}
#ifdef __cplusplus
}
#endif
#endif

View File

@ -1,106 +0,0 @@
/* Generated by wayland-scanner 1.18.0 */
/*
* Copyright © 2018 Ilia Bozhinov
*
* Permission to use, copy, modify, distribute, and sell this
* software and its documentation for any purpose is hereby granted
* without fee, provided that the above copyright notice appear in
* all copies and that both that copyright notice and this permission
* notice appear in supporting documentation, and that the name of
* the copyright holders not be used in advertising or publicity
* pertaining to distribution of the software without specific,
* written prior permission. The copyright holders make no
* representations about the suitability of this software for any
* purpose. It is provided "as is" without express or implied
* warranty.
*
* THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS
* SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY
* SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
* AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
* ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
* THIS SOFTWARE.
*/
#include <stdlib.h>
#include <stdint.h>
#include "wayland-util.h"
#ifndef __has_attribute
# define __has_attribute(x) 0 /* Compatibility with non-clang compilers. */
#endif
#if (__has_attribute(visibility) || defined(__GNUC__) && __GNUC__ >= 4)
#define WL_PRIVATE __attribute__ ((visibility("hidden")))
#else
#define WL_PRIVATE
#endif
extern const struct wl_interface wl_output_interface;
extern const struct wl_interface wl_seat_interface;
extern const struct wl_interface wl_surface_interface;
extern const struct wl_interface zwlr_foreign_toplevel_handle_v1_interface;
static const struct wl_interface *wlr_foreign_toplevel_management_unstable_v1_types[] = {
NULL,
&zwlr_foreign_toplevel_handle_v1_interface,
&wl_seat_interface,
&wl_surface_interface,
NULL,
NULL,
NULL,
NULL,
&wl_output_interface,
&wl_output_interface,
&wl_output_interface,
&zwlr_foreign_toplevel_handle_v1_interface,
};
static const struct wl_message zwlr_foreign_toplevel_manager_v1_requests[] = {
{ "stop", "", wlr_foreign_toplevel_management_unstable_v1_types + 0 },
};
static const struct wl_message zwlr_foreign_toplevel_manager_v1_events[] = {
{ "toplevel", "n", wlr_foreign_toplevel_management_unstable_v1_types + 1 },
{ "finished", "", wlr_foreign_toplevel_management_unstable_v1_types + 0 },
};
WL_PRIVATE const struct wl_interface zwlr_foreign_toplevel_manager_v1_interface = {
"zwlr_foreign_toplevel_manager_v1", 3,
1, zwlr_foreign_toplevel_manager_v1_requests,
2, zwlr_foreign_toplevel_manager_v1_events,
};
static const struct wl_message zwlr_foreign_toplevel_handle_v1_requests[] = {
{ "set_maximized", "", wlr_foreign_toplevel_management_unstable_v1_types + 0 },
{ "unset_maximized", "", wlr_foreign_toplevel_management_unstable_v1_types + 0 },
{ "set_minimized", "", wlr_foreign_toplevel_management_unstable_v1_types + 0 },
{ "unset_minimized", "", wlr_foreign_toplevel_management_unstable_v1_types + 0 },
{ "activate", "o", wlr_foreign_toplevel_management_unstable_v1_types + 2 },
{ "close", "", wlr_foreign_toplevel_management_unstable_v1_types + 0 },
{ "set_rectangle", "oiiii", wlr_foreign_toplevel_management_unstable_v1_types + 3 },
{ "destroy", "", wlr_foreign_toplevel_management_unstable_v1_types + 0 },
{ "set_fullscreen", "2?o", wlr_foreign_toplevel_management_unstable_v1_types + 8 },
{ "unset_fullscreen", "2", wlr_foreign_toplevel_management_unstable_v1_types + 0 },
};
static const struct wl_message zwlr_foreign_toplevel_handle_v1_events[] = {
{ "title", "s", wlr_foreign_toplevel_management_unstable_v1_types + 0 },
{ "app_id", "s", wlr_foreign_toplevel_management_unstable_v1_types + 0 },
{ "output_enter", "o", wlr_foreign_toplevel_management_unstable_v1_types + 9 },
{ "output_leave", "o", wlr_foreign_toplevel_management_unstable_v1_types + 10 },
{ "state", "a", wlr_foreign_toplevel_management_unstable_v1_types + 0 },
{ "done", "", wlr_foreign_toplevel_management_unstable_v1_types + 0 },
{ "closed", "", wlr_foreign_toplevel_management_unstable_v1_types + 0 },
{ "parent", "3?o", wlr_foreign_toplevel_management_unstable_v1_types + 11 },
};
WL_PRIVATE const struct wl_interface zwlr_foreign_toplevel_handle_v1_interface = {
"zwlr_foreign_toplevel_handle_v1", 3,
10, zwlr_foreign_toplevel_handle_v1_requests,
8, zwlr_foreign_toplevel_handle_v1_events,
};

View File

@ -1,270 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<protocol name="wlr_foreign_toplevel_management_unstable_v1">
<copyright>
Copyright © 2018 Ilia Bozhinov
Permission to use, copy, modify, distribute, and sell this
software and its documentation for any purpose is hereby granted
without fee, provided that the above copyright notice appear in
all copies and that both that copyright notice and this permission
notice appear in supporting documentation, and that the name of
the copyright holders not be used in advertising or publicity
pertaining to distribution of the software without specific,
written prior permission. The copyright holders make no
representations about the suitability of this software for any
purpose. It is provided "as is" without express or implied
warranty.
THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS
SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY
SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
THIS SOFTWARE.
</copyright>
<interface name="zwlr_foreign_toplevel_manager_v1" version="3">
<description summary="list and control opened apps">
The purpose of this protocol is to enable the creation of taskbars
and docks by providing them with a list of opened applications and
letting them request certain actions on them, like maximizing, etc.
After a client binds the zwlr_foreign_toplevel_manager_v1, each opened
toplevel window will be sent via the toplevel event
</description>
<event name="toplevel">
<description summary="a toplevel has been created">
This event is emitted whenever a new toplevel window is created. It
is emitted for all toplevels, regardless of the app that has created
them.
All initial details of the toplevel(title, app_id, states, etc.) will
be sent immediately after this event via the corresponding events in
zwlr_foreign_toplevel_handle_v1.
</description>
<arg name="toplevel" type="new_id" interface="zwlr_foreign_toplevel_handle_v1"/>
</event>
<request name="stop">
<description summary="stop sending events">
Indicates the client no longer wishes to receive events for new toplevels.
However the compositor may emit further toplevel_created events, until
the finished event is emitted.
The client must not send any more requests after this one.
</description>
</request>
<event name="finished">
<description summary="the compositor has finished with the toplevel manager">
This event indicates that the compositor is done sending events to the
zwlr_foreign_toplevel_manager_v1. The server will destroy the object
immediately after sending this request, so it will become invalid and
the client should free any resources associated with it.
</description>
</event>
</interface>
<interface name="zwlr_foreign_toplevel_handle_v1" version="3">
<description summary="an opened toplevel">
A zwlr_foreign_toplevel_handle_v1 object represents an opened toplevel
window. Each app may have multiple opened toplevels.
Each toplevel has a list of outputs it is visible on, conveyed to the
client with the output_enter and output_leave events.
</description>
<event name="title">
<description summary="title change">
This event is emitted whenever the title of the toplevel changes.
</description>
<arg name="title" type="string"/>
</event>
<event name="app_id">
<description summary="app-id change">
This event is emitted whenever the app-id of the toplevel changes.
</description>
<arg name="app_id" type="string"/>
</event>
<event name="output_enter">
<description summary="toplevel entered an output">
This event is emitted whenever the toplevel becomes visible on
the given output. A toplevel may be visible on multiple outputs.
</description>
<arg name="output" type="object" interface="wl_output"/>
</event>
<event name="output_leave">
<description summary="toplevel left an output">
This event is emitted whenever the toplevel stops being visible on
the given output. It is guaranteed that an entered-output event
with the same output has been emitted before this event.
</description>
<arg name="output" type="object" interface="wl_output"/>
</event>
<request name="set_maximized">
<description summary="requests that the toplevel be maximized">
Requests that the toplevel be maximized. If the maximized state actually
changes, this will be indicated by the state event.
</description>
</request>
<request name="unset_maximized">
<description summary="requests that the toplevel be unmaximized">
Requests that the toplevel be unmaximized. If the maximized state actually
changes, this will be indicated by the state event.
</description>
</request>
<request name="set_minimized">
<description summary="requests that the toplevel be minimized">
Requests that the toplevel be minimized. If the minimized state actually
changes, this will be indicated by the state event.
</description>
</request>
<request name="unset_minimized">
<description summary="requests that the toplevel be unminimized">
Requests that the toplevel be unminimized. If the minimized state actually
changes, this will be indicated by the state event.
</description>
</request>
<request name="activate">
<description summary="activate the toplevel">
Request that this toplevel be activated on the given seat.
There is no guarantee the toplevel will be actually activated.
</description>
<arg name="seat" type="object" interface="wl_seat"/>
</request>
<enum name="state">
<description summary="types of states on the toplevel">
The different states that a toplevel can have. These have the same meaning
as the states with the same names defined in xdg-toplevel
</description>
<entry name="maximized" value="0" summary="the toplevel is maximized"/>
<entry name="minimized" value="1" summary="the toplevel is minimized"/>
<entry name="activated" value="2" summary="the toplevel is active"/>
<entry name="fullscreen" value="3" summary="the toplevel is fullscreen" since="2"/>
</enum>
<event name="state">
<description summary="the toplevel state changed">
This event is emitted immediately after the zlw_foreign_toplevel_handle_v1
is created and each time the toplevel state changes, either because of a
compositor action or because of a request in this protocol.
</description>
<arg name="state" type="array"/>
</event>
<event name="done">
<description summary="all information about the toplevel has been sent">
This event is sent after all changes in the toplevel state have been
sent.
This allows changes to the zwlr_foreign_toplevel_handle_v1 properties
to be seen as atomic, even if they happen via multiple events.
</description>
</event>
<request name="close">
<description summary="request that the toplevel be closed">
Send a request to the toplevel to close itself. The compositor would
typically use a shell-specific method to carry out this request, for
example by sending the xdg_toplevel.close event. However, this gives
no guarantees the toplevel will actually be destroyed. If and when
this happens, the zwlr_foreign_toplevel_handle_v1.closed event will
be emitted.
</description>
</request>
<request name="set_rectangle">
<description summary="the rectangle which represents the toplevel">
The rectangle of the surface specified in this request corresponds to
the place where the app using this protocol represents the given toplevel.
It can be used by the compositor as a hint for some operations, e.g
minimizing. The client is however not required to set this, in which
case the compositor is free to decide some default value.
If the client specifies more than one rectangle, only the last one is
considered.
The dimensions are given in surface-local coordinates.
Setting width=height=0 removes the already-set rectangle.
</description>
<arg name="surface" type="object" interface="wl_surface"/>
<arg name="x" type="int"/>
<arg name="y" type="int"/>
<arg name="width" type="int"/>
<arg name="height" type="int"/>
</request>
<enum name="error">
<entry name="invalid_rectangle" value="0"
summary="the provided rectangle is invalid"/>
</enum>
<event name="closed">
<description summary="this toplevel has been destroyed">
This event means the toplevel has been destroyed. It is guaranteed there
won't be any more events for this zwlr_foreign_toplevel_handle_v1. The
toplevel itself becomes inert so any requests will be ignored except the
destroy request.
</description>
</event>
<request name="destroy" type="destructor">
<description summary="destroy the zwlr_foreign_toplevel_handle_v1 object">
Destroys the zwlr_foreign_toplevel_handle_v1 object.
This request should be called either when the client does not want to
use the toplevel anymore or after the closed event to finalize the
destruction of the object.
</description>
</request>
<!-- Version 2 additions -->
<request name="set_fullscreen" since="2">
<description summary="request that the toplevel be fullscreened">
Requests that the toplevel be fullscreened on the given output. If the
fullscreen state and/or the outputs the toplevel is visible on actually
change, this will be indicated by the state and output_enter/leave
events.
The output parameter is only a hint to the compositor. Also, if output
is NULL, the compositor should decide which output the toplevel will be
fullscreened on, if at all.
</description>
<arg name="output" type="object" interface="wl_output" allow-null="true"/>
</request>
<request name="unset_fullscreen" since="2">
<description summary="request that the toplevel be unfullscreened">
Requests that the toplevel be unfullscreened. If the fullscreen state
actually changes, this will be indicated by the state event.
</description>
</request>
<!-- Version 3 additions -->
<event name="parent" since="3">
<description summary="parent change">
This event is emitted whenever the parent of the toplevel changes.
No event is emitted when the parent handle is destroyed by the client.
</description>
<arg name="parent" type="object" interface="zwlr_foreign_toplevel_handle_v1" allow-null="true"/>
</event>
</interface>
</protocol>

View File

@ -1,559 +0,0 @@
/* Generated by wayland-scanner 1.18.0 */
#ifndef WLR_LAYER_SHELL_UNSTABLE_V1_CLIENT_PROTOCOL_H
#define WLR_LAYER_SHELL_UNSTABLE_V1_CLIENT_PROTOCOL_H
#include <stdint.h>
#include <stddef.h>
#include "wayland-client.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @page page_wlr_layer_shell_unstable_v1 The wlr_layer_shell_unstable_v1 protocol
* @section page_ifaces_wlr_layer_shell_unstable_v1 Interfaces
* - @subpage page_iface_zwlr_layer_shell_v1 - create surfaces that are layers of the desktop
* - @subpage page_iface_zwlr_layer_surface_v1 - layer metadata interface
* @section page_copyright_wlr_layer_shell_unstable_v1 Copyright
* <pre>
*
* Copyright © 2017 Drew DeVault
*
* Permission to use, copy, modify, distribute, and sell this
* software and its documentation for any purpose is hereby granted
* without fee, provided that the above copyright notice appear in
* all copies and that both that copyright notice and this permission
* notice appear in supporting documentation, and that the name of
* the copyright holders not be used in advertising or publicity
* pertaining to distribution of the software without specific,
* written prior permission. The copyright holders make no
* representations about the suitability of this software for any
* purpose. It is provided "as is" without express or implied
* warranty.
*
* THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS
* SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY
* SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
* AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
* ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
* THIS SOFTWARE.
* </pre>
*/
struct wl_output;
struct wl_surface;
struct xdg_popup;
struct zwlr_layer_shell_v1;
struct zwlr_layer_surface_v1;
/**
* @page page_iface_zwlr_layer_shell_v1 zwlr_layer_shell_v1
* @section page_iface_zwlr_layer_shell_v1_desc Description
*
* Clients can use this interface to assign the surface_layer role to
* wl_surfaces. Such surfaces are assigned to a "layer" of the output and
* rendered with a defined z-depth respective to each other. They may also be
* anchored to the edges and corners of a screen and specify input handling
* semantics. This interface should be suitable for the implementation of
* many desktop shell components, and a broad number of other applications
* that interact with the desktop.
* @section page_iface_zwlr_layer_shell_v1_api API
* See @ref iface_zwlr_layer_shell_v1.
*/
/**
* @defgroup iface_zwlr_layer_shell_v1 The zwlr_layer_shell_v1 interface
*
* Clients can use this interface to assign the surface_layer role to
* wl_surfaces. Such surfaces are assigned to a "layer" of the output and
* rendered with a defined z-depth respective to each other. They may also be
* anchored to the edges and corners of a screen and specify input handling
* semantics. This interface should be suitable for the implementation of
* many desktop shell components, and a broad number of other applications
* that interact with the desktop.
*/
extern const struct wl_interface zwlr_layer_shell_v1_interface;
/**
* @page page_iface_zwlr_layer_surface_v1 zwlr_layer_surface_v1
* @section page_iface_zwlr_layer_surface_v1_desc Description
*
* An interface that may be implemented by a wl_surface, for surfaces that
* are designed to be rendered as a layer of a stacked desktop-like
* environment.
*
* Layer surface state (size, anchor, exclusive zone, margin, interactivity)
* is double-buffered, and will be applied at the time wl_surface.commit of
* the corresponding wl_surface is called.
* @section page_iface_zwlr_layer_surface_v1_api API
* See @ref iface_zwlr_layer_surface_v1.
*/
/**
* @defgroup iface_zwlr_layer_surface_v1 The zwlr_layer_surface_v1 interface
*
* An interface that may be implemented by a wl_surface, for surfaces that
* are designed to be rendered as a layer of a stacked desktop-like
* environment.
*
* Layer surface state (size, anchor, exclusive zone, margin, interactivity)
* is double-buffered, and will be applied at the time wl_surface.commit of
* the corresponding wl_surface is called.
*/
extern const struct wl_interface zwlr_layer_surface_v1_interface;
#ifndef ZWLR_LAYER_SHELL_V1_ERROR_ENUM
#define ZWLR_LAYER_SHELL_V1_ERROR_ENUM
enum zwlr_layer_shell_v1_error {
/**
* wl_surface has another role
*/
ZWLR_LAYER_SHELL_V1_ERROR_ROLE = 0,
/**
* layer value is invalid
*/
ZWLR_LAYER_SHELL_V1_ERROR_INVALID_LAYER = 1,
/**
* wl_surface has a buffer attached or committed
*/
ZWLR_LAYER_SHELL_V1_ERROR_ALREADY_CONSTRUCTED = 2,
};
#endif /* ZWLR_LAYER_SHELL_V1_ERROR_ENUM */
#ifndef ZWLR_LAYER_SHELL_V1_LAYER_ENUM
#define ZWLR_LAYER_SHELL_V1_LAYER_ENUM
/**
* @ingroup iface_zwlr_layer_shell_v1
* available layers for surfaces
*
* These values indicate which layers a surface can be rendered in. They
* are ordered by z depth, bottom-most first. Traditional shell surfaces
* will typically be rendered between the bottom and top layers.
* Fullscreen shell surfaces are typically rendered at the top layer.
* Multiple surfaces can share a single layer, and ordering within a
* single layer is undefined.
*/
enum zwlr_layer_shell_v1_layer {
ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND = 0,
ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM = 1,
ZWLR_LAYER_SHELL_V1_LAYER_TOP = 2,
ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY = 3,
};
#endif /* ZWLR_LAYER_SHELL_V1_LAYER_ENUM */
#define ZWLR_LAYER_SHELL_V1_GET_LAYER_SURFACE 0
/**
* @ingroup iface_zwlr_layer_shell_v1
*/
#define ZWLR_LAYER_SHELL_V1_GET_LAYER_SURFACE_SINCE_VERSION 1
/** @ingroup iface_zwlr_layer_shell_v1 */
static inline void
zwlr_layer_shell_v1_set_user_data(struct zwlr_layer_shell_v1 *zwlr_layer_shell_v1, void *user_data)
{
wl_proxy_set_user_data((struct wl_proxy *) zwlr_layer_shell_v1, user_data);
}
/** @ingroup iface_zwlr_layer_shell_v1 */
static inline void *
zwlr_layer_shell_v1_get_user_data(struct zwlr_layer_shell_v1 *zwlr_layer_shell_v1)
{
return wl_proxy_get_user_data((struct wl_proxy *) zwlr_layer_shell_v1);
}
static inline uint32_t
zwlr_layer_shell_v1_get_version(struct zwlr_layer_shell_v1 *zwlr_layer_shell_v1)
{
return wl_proxy_get_version((struct wl_proxy *) zwlr_layer_shell_v1);
}
/** @ingroup iface_zwlr_layer_shell_v1 */
static inline void
zwlr_layer_shell_v1_destroy(struct zwlr_layer_shell_v1 *zwlr_layer_shell_v1)
{
wl_proxy_destroy((struct wl_proxy *) zwlr_layer_shell_v1);
}
/**
* @ingroup iface_zwlr_layer_shell_v1
*
* Create a layer surface for an existing surface. This assigns the role of
* layer_surface, or raises a protocol error if another role is already
* assigned.
*
* Creating a layer surface from a wl_surface which has a buffer attached
* or committed is a client error, and any attempts by a client to attach
* or manipulate a buffer prior to the first layer_surface.configure call
* must also be treated as errors.
*
* You may pass NULL for output to allow the compositor to decide which
* output to use. Generally this will be the one that the user most
* recently interacted with.
*
* Clients can specify a namespace that defines the purpose of the layer
* surface.
*/
static inline struct zwlr_layer_surface_v1 *
zwlr_layer_shell_v1_get_layer_surface(struct zwlr_layer_shell_v1 *zwlr_layer_shell_v1, struct wl_surface *surface, struct wl_output *output, uint32_t layer, const char *namespace)
{
struct wl_proxy *id;
id = wl_proxy_marshal_constructor((struct wl_proxy *) zwlr_layer_shell_v1,
ZWLR_LAYER_SHELL_V1_GET_LAYER_SURFACE, &zwlr_layer_surface_v1_interface, NULL, surface, output, layer, namespace);
return (struct zwlr_layer_surface_v1 *) id;
}
#ifndef ZWLR_LAYER_SURFACE_V1_ERROR_ENUM
#define ZWLR_LAYER_SURFACE_V1_ERROR_ENUM
enum zwlr_layer_surface_v1_error {
/**
* provided surface state is invalid
*/
ZWLR_LAYER_SURFACE_V1_ERROR_INVALID_SURFACE_STATE = 0,
/**
* size is invalid
*/
ZWLR_LAYER_SURFACE_V1_ERROR_INVALID_SIZE = 1,
/**
* anchor bitfield is invalid
*/
ZWLR_LAYER_SURFACE_V1_ERROR_INVALID_ANCHOR = 2,
};
#endif /* ZWLR_LAYER_SURFACE_V1_ERROR_ENUM */
#ifndef ZWLR_LAYER_SURFACE_V1_ANCHOR_ENUM
#define ZWLR_LAYER_SURFACE_V1_ANCHOR_ENUM
enum zwlr_layer_surface_v1_anchor {
/**
* the top edge of the anchor rectangle
*/
ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP = 1,
/**
* the bottom edge of the anchor rectangle
*/
ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM = 2,
/**
* the left edge of the anchor rectangle
*/
ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT = 4,
/**
* the right edge of the anchor rectangle
*/
ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT = 8,
};
#endif /* ZWLR_LAYER_SURFACE_V1_ANCHOR_ENUM */
/**
* @ingroup iface_zwlr_layer_surface_v1
* @struct zwlr_layer_surface_v1_listener
*/
struct zwlr_layer_surface_v1_listener {
/**
* suggest a surface change
*
* The configure event asks the client to resize its surface.
*
* Clients should arrange their surface for the new states, and
* then send an ack_configure request with the serial sent in this
* configure event at some point before committing the new surface.
*
* The client is free to dismiss all but the last configure event
* it received.
*
* The width and height arguments specify the size of the window in
* surface-local coordinates.
*
* The size is a hint, in the sense that the client is free to
* ignore it if it doesn't resize, pick a smaller size (to satisfy
* aspect ratio or resize in steps of NxM pixels). If the client
* picks a smaller size and is anchored to two opposite anchors
* (e.g. 'top' and 'bottom'), the surface will be centered on this
* axis.
*
* If the width or height arguments are zero, it means the client
* should decide its own window dimension.
*/
void (*configure)(void *data,
struct zwlr_layer_surface_v1 *zwlr_layer_surface_v1,
uint32_t serial,
uint32_t width,
uint32_t height);
/**
* surface should be closed
*
* The closed event is sent by the compositor when the surface
* will no longer be shown. The output may have been destroyed or
* the user may have asked for it to be removed. Further changes to
* the surface will be ignored. The client should destroy the
* resource after receiving this event, and create a new surface if
* they so choose.
*/
void (*closed)(void *data,
struct zwlr_layer_surface_v1 *zwlr_layer_surface_v1);
};
/**
* @ingroup iface_zwlr_layer_surface_v1
*/
static inline int
zwlr_layer_surface_v1_add_listener(struct zwlr_layer_surface_v1 *zwlr_layer_surface_v1,
const struct zwlr_layer_surface_v1_listener *listener, void *data)
{
return wl_proxy_add_listener((struct wl_proxy *) zwlr_layer_surface_v1,
(void (**)(void)) listener, data);
}
#define ZWLR_LAYER_SURFACE_V1_SET_SIZE 0
#define ZWLR_LAYER_SURFACE_V1_SET_ANCHOR 1
#define ZWLR_LAYER_SURFACE_V1_SET_EXCLUSIVE_ZONE 2
#define ZWLR_LAYER_SURFACE_V1_SET_MARGIN 3
#define ZWLR_LAYER_SURFACE_V1_SET_KEYBOARD_INTERACTIVITY 4
#define ZWLR_LAYER_SURFACE_V1_GET_POPUP 5
#define ZWLR_LAYER_SURFACE_V1_ACK_CONFIGURE 6
#define ZWLR_LAYER_SURFACE_V1_DESTROY 7
/**
* @ingroup iface_zwlr_layer_surface_v1
*/
#define ZWLR_LAYER_SURFACE_V1_CONFIGURE_SINCE_VERSION 1
/**
* @ingroup iface_zwlr_layer_surface_v1
*/
#define ZWLR_LAYER_SURFACE_V1_CLOSED_SINCE_VERSION 1
/**
* @ingroup iface_zwlr_layer_surface_v1
*/
#define ZWLR_LAYER_SURFACE_V1_SET_SIZE_SINCE_VERSION 1
/**
* @ingroup iface_zwlr_layer_surface_v1
*/
#define ZWLR_LAYER_SURFACE_V1_SET_ANCHOR_SINCE_VERSION 1
/**
* @ingroup iface_zwlr_layer_surface_v1
*/
#define ZWLR_LAYER_SURFACE_V1_SET_EXCLUSIVE_ZONE_SINCE_VERSION 1
/**
* @ingroup iface_zwlr_layer_surface_v1
*/
#define ZWLR_LAYER_SURFACE_V1_SET_MARGIN_SINCE_VERSION 1
/**
* @ingroup iface_zwlr_layer_surface_v1
*/
#define ZWLR_LAYER_SURFACE_V1_SET_KEYBOARD_INTERACTIVITY_SINCE_VERSION 1
/**
* @ingroup iface_zwlr_layer_surface_v1
*/
#define ZWLR_LAYER_SURFACE_V1_GET_POPUP_SINCE_VERSION 1
/**
* @ingroup iface_zwlr_layer_surface_v1
*/
#define ZWLR_LAYER_SURFACE_V1_ACK_CONFIGURE_SINCE_VERSION 1
/**
* @ingroup iface_zwlr_layer_surface_v1
*/
#define ZWLR_LAYER_SURFACE_V1_DESTROY_SINCE_VERSION 1
/** @ingroup iface_zwlr_layer_surface_v1 */
static inline void
zwlr_layer_surface_v1_set_user_data(struct zwlr_layer_surface_v1 *zwlr_layer_surface_v1, void *user_data)
{
wl_proxy_set_user_data((struct wl_proxy *) zwlr_layer_surface_v1, user_data);
}
/** @ingroup iface_zwlr_layer_surface_v1 */
static inline void *
zwlr_layer_surface_v1_get_user_data(struct zwlr_layer_surface_v1 *zwlr_layer_surface_v1)
{
return wl_proxy_get_user_data((struct wl_proxy *) zwlr_layer_surface_v1);
}
static inline uint32_t
zwlr_layer_surface_v1_get_version(struct zwlr_layer_surface_v1 *zwlr_layer_surface_v1)
{
return wl_proxy_get_version((struct wl_proxy *) zwlr_layer_surface_v1);
}
/**
* @ingroup iface_zwlr_layer_surface_v1
*
* Sets the size of the surface in surface-local coordinates. The
* compositor will display the surface centered with respect to its
* anchors.
*
* If you pass 0 for either value, the compositor will assign it and
* inform you of the assignment in the configure event. You must set your
* anchor to opposite edges in the dimensions you omit; not doing so is a
* protocol error. Both values are 0 by default.
*
* Size is double-buffered, see wl_surface.commit.
*/
static inline void
zwlr_layer_surface_v1_set_size(struct zwlr_layer_surface_v1 *zwlr_layer_surface_v1, uint32_t width, uint32_t height)
{
wl_proxy_marshal((struct wl_proxy *) zwlr_layer_surface_v1,
ZWLR_LAYER_SURFACE_V1_SET_SIZE, width, height);
}
/**
* @ingroup iface_zwlr_layer_surface_v1
*
* Requests that the compositor anchor the surface to the specified edges
* and corners. If two orthoginal edges are specified (e.g. 'top' and
* 'left'), then the anchor point will be the intersection of the edges
* (e.g. the top left corner of the output); otherwise the anchor point
* will be centered on that edge, or in the center if none is specified.
*
* Anchor is double-buffered, see wl_surface.commit.
*/
static inline void
zwlr_layer_surface_v1_set_anchor(struct zwlr_layer_surface_v1 *zwlr_layer_surface_v1, uint32_t anchor)
{
wl_proxy_marshal((struct wl_proxy *) zwlr_layer_surface_v1,
ZWLR_LAYER_SURFACE_V1_SET_ANCHOR, anchor);
}
/**
* @ingroup iface_zwlr_layer_surface_v1
*
* Requests that the compositor avoids occluding an area of the surface
* with other surfaces. The compositor's use of this information is
* implementation-dependent - do not assume that this region will not
* actually be occluded.
*
* A positive value is only meaningful if the surface is anchored to an
* edge, rather than a corner. The zone is the number of surface-local
* coordinates from the edge that are considered exclusive.
*
* Surfaces that do not wish to have an exclusive zone may instead specify
* how they should interact with surfaces that do. If set to zero, the
* surface indicates that it would like to be moved to avoid occluding
* surfaces with a positive excluzive zone. If set to -1, the surface
* indicates that it would not like to be moved to accomodate for other
* surfaces, and the compositor should extend it all the way to the edges
* it is anchored to.
*
* For example, a panel might set its exclusive zone to 10, so that
* maximized shell surfaces are not shown on top of it. A notification
* might set its exclusive zone to 0, so that it is moved to avoid
* occluding the panel, but shell surfaces are shown underneath it. A
* wallpaper or lock screen might set their exclusive zone to -1, so that
* they stretch below or over the panel.
*
* The default value is 0.
*
* Exclusive zone is double-buffered, see wl_surface.commit.
*/
static inline void
zwlr_layer_surface_v1_set_exclusive_zone(struct zwlr_layer_surface_v1 *zwlr_layer_surface_v1, int32_t zone)
{
wl_proxy_marshal((struct wl_proxy *) zwlr_layer_surface_v1,
ZWLR_LAYER_SURFACE_V1_SET_EXCLUSIVE_ZONE, zone);
}
/**
* @ingroup iface_zwlr_layer_surface_v1
*
* Requests that the surface be placed some distance away from the anchor
* point on the output, in surface-local coordinates. Setting this value
* for edges you are not anchored to has no effect.
*
* The exclusive zone includes the margin.
*
* Margin is double-buffered, see wl_surface.commit.
*/
static inline void
zwlr_layer_surface_v1_set_margin(struct zwlr_layer_surface_v1 *zwlr_layer_surface_v1, int32_t top, int32_t right, int32_t bottom, int32_t left)
{
wl_proxy_marshal((struct wl_proxy *) zwlr_layer_surface_v1,
ZWLR_LAYER_SURFACE_V1_SET_MARGIN, top, right, bottom, left);
}
/**
* @ingroup iface_zwlr_layer_surface_v1
*
* Set to 1 to request that the seat send keyboard events to this layer
* surface. For layers below the shell surface layer, the seat will use
* normal focus semantics. For layers above the shell surface layers, the
* seat will always give exclusive keyboard focus to the top-most layer
* which has keyboard interactivity set to true.
*
* Layer surfaces receive pointer, touch, and tablet events normally. If
* you do not want to receive them, set the input region on your surface
* to an empty region.
*
* Events is double-buffered, see wl_surface.commit.
*/
static inline void
zwlr_layer_surface_v1_set_keyboard_interactivity(struct zwlr_layer_surface_v1 *zwlr_layer_surface_v1, uint32_t keyboard_interactivity)
{
wl_proxy_marshal((struct wl_proxy *) zwlr_layer_surface_v1,
ZWLR_LAYER_SURFACE_V1_SET_KEYBOARD_INTERACTIVITY, keyboard_interactivity);
}
/**
* @ingroup iface_zwlr_layer_surface_v1
*
* This assigns an xdg_popup's parent to this layer_surface. This popup
* should have been created via xdg_surface::get_popup with the parent set
* to NULL, and this request must be invoked before committing the popup's
* initial state.
*
* See the documentation of xdg_popup for more details about what an
* xdg_popup is and how it is used.
*/
static inline void
zwlr_layer_surface_v1_get_popup(struct zwlr_layer_surface_v1 *zwlr_layer_surface_v1, struct xdg_popup *popup)
{
wl_proxy_marshal((struct wl_proxy *) zwlr_layer_surface_v1,
ZWLR_LAYER_SURFACE_V1_GET_POPUP, popup);
}
/**
* @ingroup iface_zwlr_layer_surface_v1
*
* When a configure event is received, if a client commits the
* surface in response to the configure event, then the client
* must make an ack_configure request sometime before the commit
* request, passing along the serial of the configure event.
*
* If the client receives multiple configure events before it
* can respond to one, it only has to ack the last configure event.
*
* A client is not required to commit immediately after sending
* an ack_configure request - it may even ack_configure several times
* before its next surface commit.
*
* A client may send multiple ack_configure requests before committing, but
* only the last request sent before a commit indicates which configure
* event the client really is responding to.
*/
static inline void
zwlr_layer_surface_v1_ack_configure(struct zwlr_layer_surface_v1 *zwlr_layer_surface_v1, uint32_t serial)
{
wl_proxy_marshal((struct wl_proxy *) zwlr_layer_surface_v1,
ZWLR_LAYER_SURFACE_V1_ACK_CONFIGURE, serial);
}
/**
* @ingroup iface_zwlr_layer_surface_v1
*
* This request destroys the layer surface.
*/
static inline void
zwlr_layer_surface_v1_destroy(struct zwlr_layer_surface_v1 *zwlr_layer_surface_v1)
{
wl_proxy_marshal((struct wl_proxy *) zwlr_layer_surface_v1,
ZWLR_LAYER_SURFACE_V1_DESTROY);
wl_proxy_destroy((struct wl_proxy *) zwlr_layer_surface_v1);
}
#ifdef __cplusplus
}
#endif
#endif

View File

@ -1,91 +0,0 @@
/* Generated by wayland-scanner 1.18.0 */
/*
* Copyright © 2017 Drew DeVault
*
* Permission to use, copy, modify, distribute, and sell this
* software and its documentation for any purpose is hereby granted
* without fee, provided that the above copyright notice appear in
* all copies and that both that copyright notice and this permission
* notice appear in supporting documentation, and that the name of
* the copyright holders not be used in advertising or publicity
* pertaining to distribution of the software without specific,
* written prior permission. The copyright holders make no
* representations about the suitability of this software for any
* purpose. It is provided "as is" without express or implied
* warranty.
*
* THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS
* SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY
* SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
* AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
* ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
* THIS SOFTWARE.
*/
#include <stdlib.h>
#include <stdint.h>
#include "wayland-util.h"
#ifndef __has_attribute
# define __has_attribute(x) 0 /* Compatibility with non-clang compilers. */
#endif
#if (__has_attribute(visibility) || defined(__GNUC__) && __GNUC__ >= 4)
#define WL_PRIVATE __attribute__ ((visibility("hidden")))
#else
#define WL_PRIVATE
#endif
extern const struct wl_interface wl_output_interface;
extern const struct wl_interface wl_surface_interface;
extern const struct wl_interface xdg_popup_interface;
extern const struct wl_interface zwlr_layer_surface_v1_interface;
static const struct wl_interface *wlr_layer_shell_unstable_v1_types[] = {
NULL,
NULL,
NULL,
NULL,
&zwlr_layer_surface_v1_interface,
&wl_surface_interface,
&wl_output_interface,
NULL,
NULL,
&xdg_popup_interface,
};
static const struct wl_message zwlr_layer_shell_v1_requests[] = {
{ "get_layer_surface", "no?ous", wlr_layer_shell_unstable_v1_types + 4 },
};
WL_PRIVATE const struct wl_interface zwlr_layer_shell_v1_interface = {
"zwlr_layer_shell_v1", 1,
1, zwlr_layer_shell_v1_requests,
0, NULL,
};
static const struct wl_message zwlr_layer_surface_v1_requests[] = {
{ "set_size", "uu", wlr_layer_shell_unstable_v1_types + 0 },
{ "set_anchor", "u", wlr_layer_shell_unstable_v1_types + 0 },
{ "set_exclusive_zone", "i", wlr_layer_shell_unstable_v1_types + 0 },
{ "set_margin", "iiii", wlr_layer_shell_unstable_v1_types + 0 },
{ "set_keyboard_interactivity", "u", wlr_layer_shell_unstable_v1_types + 0 },
{ "get_popup", "o", wlr_layer_shell_unstable_v1_types + 9 },
{ "ack_configure", "u", wlr_layer_shell_unstable_v1_types + 0 },
{ "destroy", "", wlr_layer_shell_unstable_v1_types + 0 },
};
static const struct wl_message zwlr_layer_surface_v1_events[] = {
{ "configure", "uuu", wlr_layer_shell_unstable_v1_types + 0 },
{ "closed", "", wlr_layer_shell_unstable_v1_types + 0 },
};
WL_PRIVATE const struct wl_interface zwlr_layer_surface_v1_interface = {
"zwlr_layer_surface_v1", 1,
8, zwlr_layer_surface_v1_requests,
2, zwlr_layer_surface_v1_events,
};

View File

@ -1,285 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<protocol name="wlr_layer_shell_unstable_v1">
<copyright>
Copyright © 2017 Drew DeVault
Permission to use, copy, modify, distribute, and sell this
software and its documentation for any purpose is hereby granted
without fee, provided that the above copyright notice appear in
all copies and that both that copyright notice and this permission
notice appear in supporting documentation, and that the name of
the copyright holders not be used in advertising or publicity
pertaining to distribution of the software without specific,
written prior permission. The copyright holders make no
representations about the suitability of this software for any
purpose. It is provided "as is" without express or implied
warranty.
THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS
SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY
SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
THIS SOFTWARE.
</copyright>
<interface name="zwlr_layer_shell_v1" version="1">
<description summary="create surfaces that are layers of the desktop">
Clients can use this interface to assign the surface_layer role to
wl_surfaces. Such surfaces are assigned to a "layer" of the output and
rendered with a defined z-depth respective to each other. They may also be
anchored to the edges and corners of a screen and specify input handling
semantics. This interface should be suitable for the implementation of
many desktop shell components, and a broad number of other applications
that interact with the desktop.
</description>
<request name="get_layer_surface">
<description summary="create a layer_surface from a surface">
Create a layer surface for an existing surface. This assigns the role of
layer_surface, or raises a protocol error if another role is already
assigned.
Creating a layer surface from a wl_surface which has a buffer attached
or committed is a client error, and any attempts by a client to attach
or manipulate a buffer prior to the first layer_surface.configure call
must also be treated as errors.
You may pass NULL for output to allow the compositor to decide which
output to use. Generally this will be the one that the user most
recently interacted with.
Clients can specify a namespace that defines the purpose of the layer
surface.
</description>
<arg name="id" type="new_id" interface="zwlr_layer_surface_v1"/>
<arg name="surface" type="object" interface="wl_surface"/>
<arg name="output" type="object" interface="wl_output" allow-null="true"/>
<arg name="layer" type="uint" enum="layer" summary="layer to add this surface to"/>
<arg name="namespace" type="string" summary="namespace for the layer surface"/>
</request>
<enum name="error">
<entry name="role" value="0" summary="wl_surface has another role"/>
<entry name="invalid_layer" value="1" summary="layer value is invalid"/>
<entry name="already_constructed" value="2" summary="wl_surface has a buffer attached or committed"/>
</enum>
<enum name="layer">
<description summary="available layers for surfaces">
These values indicate which layers a surface can be rendered in. They
are ordered by z depth, bottom-most first. Traditional shell surfaces
will typically be rendered between the bottom and top layers.
Fullscreen shell surfaces are typically rendered at the top layer.
Multiple surfaces can share a single layer, and ordering within a
single layer is undefined.
</description>
<entry name="background" value="0"/>
<entry name="bottom" value="1"/>
<entry name="top" value="2"/>
<entry name="overlay" value="3"/>
</enum>
</interface>
<interface name="zwlr_layer_surface_v1" version="1">
<description summary="layer metadata interface">
An interface that may be implemented by a wl_surface, for surfaces that
are designed to be rendered as a layer of a stacked desktop-like
environment.
Layer surface state (size, anchor, exclusive zone, margin, interactivity)
is double-buffered, and will be applied at the time wl_surface.commit of
the corresponding wl_surface is called.
</description>
<request name="set_size">
<description summary="sets the size of the surface">
Sets the size of the surface in surface-local coordinates. The
compositor will display the surface centered with respect to its
anchors.
If you pass 0 for either value, the compositor will assign it and
inform you of the assignment in the configure event. You must set your
anchor to opposite edges in the dimensions you omit; not doing so is a
protocol error. Both values are 0 by default.
Size is double-buffered, see wl_surface.commit.
</description>
<arg name="width" type="uint"/>
<arg name="height" type="uint"/>
</request>
<request name="set_anchor">
<description summary="configures the anchor point of the surface">
Requests that the compositor anchor the surface to the specified edges
and corners. If two orthoginal edges are specified (e.g. 'top' and
'left'), then the anchor point will be the intersection of the edges
(e.g. the top left corner of the output); otherwise the anchor point
will be centered on that edge, or in the center if none is specified.
Anchor is double-buffered, see wl_surface.commit.
</description>
<arg name="anchor" type="uint" enum="anchor"/>
</request>
<request name="set_exclusive_zone">
<description summary="configures the exclusive geometry of this surface">
Requests that the compositor avoids occluding an area of the surface
with other surfaces. The compositor's use of this information is
implementation-dependent - do not assume that this region will not
actually be occluded.
A positive value is only meaningful if the surface is anchored to an
edge, rather than a corner. The zone is the number of surface-local
coordinates from the edge that are considered exclusive.
Surfaces that do not wish to have an exclusive zone may instead specify
how they should interact with surfaces that do. If set to zero, the
surface indicates that it would like to be moved to avoid occluding
surfaces with a positive excluzive zone. If set to -1, the surface
indicates that it would not like to be moved to accomodate for other
surfaces, and the compositor should extend it all the way to the edges
it is anchored to.
For example, a panel might set its exclusive zone to 10, so that
maximized shell surfaces are not shown on top of it. A notification
might set its exclusive zone to 0, so that it is moved to avoid
occluding the panel, but shell surfaces are shown underneath it. A
wallpaper or lock screen might set their exclusive zone to -1, so that
they stretch below or over the panel.
The default value is 0.
Exclusive zone is double-buffered, see wl_surface.commit.
</description>
<arg name="zone" type="int"/>
</request>
<request name="set_margin">
<description summary="sets a margin from the anchor point">
Requests that the surface be placed some distance away from the anchor
point on the output, in surface-local coordinates. Setting this value
for edges you are not anchored to has no effect.
The exclusive zone includes the margin.
Margin is double-buffered, see wl_surface.commit.
</description>
<arg name="top" type="int"/>
<arg name="right" type="int"/>
<arg name="bottom" type="int"/>
<arg name="left" type="int"/>
</request>
<request name="set_keyboard_interactivity">
<description summary="requests keyboard events">
Set to 1 to request that the seat send keyboard events to this layer
surface. For layers below the shell surface layer, the seat will use
normal focus semantics. For layers above the shell surface layers, the
seat will always give exclusive keyboard focus to the top-most layer
which has keyboard interactivity set to true.
Layer surfaces receive pointer, touch, and tablet events normally. If
you do not want to receive them, set the input region on your surface
to an empty region.
Events is double-buffered, see wl_surface.commit.
</description>
<arg name="keyboard_interactivity" type="uint"/>
</request>
<request name="get_popup">
<description summary="assign this layer_surface as an xdg_popup parent">
This assigns an xdg_popup's parent to this layer_surface. This popup
should have been created via xdg_surface::get_popup with the parent set
to NULL, and this request must be invoked before committing the popup's
initial state.
See the documentation of xdg_popup for more details about what an
xdg_popup is and how it is used.
</description>
<arg name="popup" type="object" interface="xdg_popup"/>
</request>
<request name="ack_configure">
<description summary="ack a configure event">
When a configure event is received, if a client commits the
surface in response to the configure event, then the client
must make an ack_configure request sometime before the commit
request, passing along the serial of the configure event.
If the client receives multiple configure events before it
can respond to one, it only has to ack the last configure event.
A client is not required to commit immediately after sending
an ack_configure request - it may even ack_configure several times
before its next surface commit.
A client may send multiple ack_configure requests before committing, but
only the last request sent before a commit indicates which configure
event the client really is responding to.
</description>
<arg name="serial" type="uint" summary="the serial from the configure event"/>
</request>
<request name="destroy" type="destructor">
<description summary="destroy the layer_surface">
This request destroys the layer surface.
</description>
</request>
<event name="configure">
<description summary="suggest a surface change">
The configure event asks the client to resize its surface.
Clients should arrange their surface for the new states, and then send
an ack_configure request with the serial sent in this configure event at
some point before committing the new surface.
The client is free to dismiss all but the last configure event it
received.
The width and height arguments specify the size of the window in
surface-local coordinates.
The size is a hint, in the sense that the client is free to ignore it if
it doesn't resize, pick a smaller size (to satisfy aspect ratio or
resize in steps of NxM pixels). If the client picks a smaller size and
is anchored to two opposite anchors (e.g. 'top' and 'bottom'), the
surface will be centered on this axis.
If the width or height arguments are zero, it means the client should
decide its own window dimension.
</description>
<arg name="serial" type="uint"/>
<arg name="width" type="uint"/>
<arg name="height" type="uint"/>
</event>
<event name="closed">
<description summary="surface should be closed">
The closed event is sent by the compositor when the surface will no
longer be shown. The output may have been destroyed or the user may
have asked for it to be removed. Further changes to the surface will be
ignored. The client should destroy the resource after receiving this
event, and create a new surface if they so choose.
</description>
</event>
<enum name="error">
<entry name="invalid_surface_state" value="0" summary="provided surface state is invalid"/>
<entry name="invalid_size" value="1" summary="size is invalid"/>
<entry name="invalid_anchor" value="2" summary="anchor bitfield is invalid"/>
</enum>
<enum name="anchor" bitfield="true">
<entry name="top" value="1" summary="the top edge of the anchor rectangle"/>
<entry name="bottom" value="2" summary="the bottom edge of the anchor rectangle"/>
<entry name="left" value="4" summary="the left edge of the anchor rectangle"/>
<entry name="right" value="8" summary="the right edge of the anchor rectangle"/>
</enum>
</interface>
</protocol>

View File

@ -1,409 +0,0 @@
/* Generated by wayland-scanner 1.18.0 */
#ifndef XDG_OUTPUT_UNSTABLE_V1_CLIENT_PROTOCOL_H
#define XDG_OUTPUT_UNSTABLE_V1_CLIENT_PROTOCOL_H
#include <stdint.h>
#include <stddef.h>
#include "wayland-client.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @page page_xdg_output_unstable_v1 The xdg_output_unstable_v1 protocol
* Protocol to describe output regions
*
* @section page_desc_xdg_output_unstable_v1 Description
*
* This protocol aims at describing outputs in a way which is more in line
* with the concept of an output on desktop oriented systems.
*
* Some information are more specific to the concept of an output for
* a desktop oriented system and may not make sense in other applications,
* such as IVI systems for example.
*
* Typically, the global compositor space on a desktop system is made of
* a contiguous or overlapping set of rectangular regions.
*
* Some of the information provided in this protocol might be identical
* to their counterparts already available from wl_output, in which case
* the information provided by this protocol should be preferred to their
* equivalent in wl_output. The goal is to move the desktop specific
* concepts (such as output location within the global compositor space,
* the connector name and types, etc.) out of the core wl_output protocol.
*
* Warning! The protocol described in this file is experimental and
* backward incompatible changes may be made. Backward compatible
* changes may be added together with the corresponding interface
* version bump.
* Backward incompatible changes are done by bumping the version
* number in the protocol and interface names and resetting the
* interface version. Once the protocol is to be declared stable,
* the 'z' prefix and the version number in the protocol and
* interface names are removed and the interface version number is
* reset.
*
* @section page_ifaces_xdg_output_unstable_v1 Interfaces
* - @subpage page_iface_zxdg_output_manager_v1 - manage xdg_output objects
* - @subpage page_iface_zxdg_output_v1 - compositor logical output region
* @section page_copyright_xdg_output_unstable_v1 Copyright
* <pre>
*
* Copyright © 2017 Red Hat Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice (including the next
* paragraph) shall be included in all copies or substantial portions of the
* Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
* </pre>
*/
struct wl_output;
struct zxdg_output_manager_v1;
struct zxdg_output_v1;
/**
* @page page_iface_zxdg_output_manager_v1 zxdg_output_manager_v1
* @section page_iface_zxdg_output_manager_v1_desc Description
*
* A global factory interface for xdg_output objects.
* @section page_iface_zxdg_output_manager_v1_api API
* See @ref iface_zxdg_output_manager_v1.
*/
/**
* @defgroup iface_zxdg_output_manager_v1 The zxdg_output_manager_v1 interface
*
* A global factory interface for xdg_output objects.
*/
extern const struct wl_interface zxdg_output_manager_v1_interface;
/**
* @page page_iface_zxdg_output_v1 zxdg_output_v1
* @section page_iface_zxdg_output_v1_desc Description
*
* An xdg_output describes part of the compositor geometry.
*
* This typically corresponds to a monitor that displays part of the
* compositor space.
*
* For objects version 3 onwards, after all xdg_output properties have been
* sent (when the object is created and when properties are updated), a
* wl_output.done event is sent. This allows changes to the output
* properties to be seen as atomic, even if they happen via multiple events.
* @section page_iface_zxdg_output_v1_api API
* See @ref iface_zxdg_output_v1.
*/
/**
* @defgroup iface_zxdg_output_v1 The zxdg_output_v1 interface
*
* An xdg_output describes part of the compositor geometry.
*
* This typically corresponds to a monitor that displays part of the
* compositor space.
*
* For objects version 3 onwards, after all xdg_output properties have been
* sent (when the object is created and when properties are updated), a
* wl_output.done event is sent. This allows changes to the output
* properties to be seen as atomic, even if they happen via multiple events.
*/
extern const struct wl_interface zxdg_output_v1_interface;
#define ZXDG_OUTPUT_MANAGER_V1_DESTROY 0
#define ZXDG_OUTPUT_MANAGER_V1_GET_XDG_OUTPUT 1
/**
* @ingroup iface_zxdg_output_manager_v1
*/
#define ZXDG_OUTPUT_MANAGER_V1_DESTROY_SINCE_VERSION 1
/**
* @ingroup iface_zxdg_output_manager_v1
*/
#define ZXDG_OUTPUT_MANAGER_V1_GET_XDG_OUTPUT_SINCE_VERSION 1
/** @ingroup iface_zxdg_output_manager_v1 */
static inline void
zxdg_output_manager_v1_set_user_data(struct zxdg_output_manager_v1 *zxdg_output_manager_v1, void *user_data)
{
wl_proxy_set_user_data((struct wl_proxy *) zxdg_output_manager_v1, user_data);
}
/** @ingroup iface_zxdg_output_manager_v1 */
static inline void *
zxdg_output_manager_v1_get_user_data(struct zxdg_output_manager_v1 *zxdg_output_manager_v1)
{
return wl_proxy_get_user_data((struct wl_proxy *) zxdg_output_manager_v1);
}
static inline uint32_t
zxdg_output_manager_v1_get_version(struct zxdg_output_manager_v1 *zxdg_output_manager_v1)
{
return wl_proxy_get_version((struct wl_proxy *) zxdg_output_manager_v1);
}
/**
* @ingroup iface_zxdg_output_manager_v1
*
* Using this request a client can tell the server that it is not
* going to use the xdg_output_manager object anymore.
*
* Any objects already created through this instance are not affected.
*/
static inline void
zxdg_output_manager_v1_destroy(struct zxdg_output_manager_v1 *zxdg_output_manager_v1)
{
wl_proxy_marshal((struct wl_proxy *) zxdg_output_manager_v1,
ZXDG_OUTPUT_MANAGER_V1_DESTROY);
wl_proxy_destroy((struct wl_proxy *) zxdg_output_manager_v1);
}
/**
* @ingroup iface_zxdg_output_manager_v1
*
* This creates a new xdg_output object for the given wl_output.
*/
static inline struct zxdg_output_v1 *
zxdg_output_manager_v1_get_xdg_output(struct zxdg_output_manager_v1 *zxdg_output_manager_v1, struct wl_output *output)
{
struct wl_proxy *id;
id = wl_proxy_marshal_constructor((struct wl_proxy *) zxdg_output_manager_v1,
ZXDG_OUTPUT_MANAGER_V1_GET_XDG_OUTPUT, &zxdg_output_v1_interface, NULL, output);
return (struct zxdg_output_v1 *) id;
}
/**
* @ingroup iface_zxdg_output_v1
* @struct zxdg_output_v1_listener
*/
struct zxdg_output_v1_listener {
/**
* position of the output within the global compositor space
*
* The position event describes the location of the wl_output
* within the global compositor space.
*
* The logical_position event is sent after creating an xdg_output
* (see xdg_output_manager.get_xdg_output) and whenever the
* location of the output changes within the global compositor
* space.
* @param x x position within the global compositor space
* @param y y position within the global compositor space
*/
void (*logical_position)(void *data,
struct zxdg_output_v1 *zxdg_output_v1,
int32_t x,
int32_t y);
/**
* size of the output in the global compositor space
*
* The logical_size event describes the size of the output in the
* global compositor space.
*
* For example, a surface without any buffer scale, transformation
* nor rotation set, with the size matching the logical_size will
* have the same size as the corresponding output when displayed.
*
* Most regular Wayland clients should not pay attention to the
* logical size and would rather rely on xdg_shell interfaces.
*
* Some clients such as Xwayland, however, need this to configure
* their surfaces in the global compositor space as the compositor
* may apply a different scale from what is advertised by the
* output scaling property (to achieve fractional scaling, for
* example).
*
* For example, for a wl_output mode 3840×2160 and a scale factor
* 2:
*
* - A compositor not scaling the surface buffers will advertise a
* logical size of 3840×2160,
*
* - A compositor automatically scaling the surface buffers will
* advertise a logical size of 1920×1080,
*
* - A compositor using a fractional scale of 1.5 will advertise a
* logical size to 2560×1620.
*
* For example, for a wl_output mode 1920×1080 and a 90 degree
* rotation, the compositor will advertise a logical size of
* 1080x1920.
*
* The logical_size event is sent after creating an xdg_output (see
* xdg_output_manager.get_xdg_output) and whenever the logical size
* of the output changes, either as a result of a change in the
* applied scale or because of a change in the corresponding output
* mode(see wl_output.mode) or transform (see wl_output.transform).
* @param width width in global compositor space
* @param height height in global compositor space
*/
void (*logical_size)(void *data,
struct zxdg_output_v1 *zxdg_output_v1,
int32_t width,
int32_t height);
/**
* all information about the output have been sent
*
* This event is sent after all other properties of an xdg_output
* have been sent.
*
* This allows changes to the xdg_output properties to be seen as
* atomic, even if they happen via multiple events.
*
* For objects version 3 onwards, this event is deprecated.
* Compositors are not required to send it anymore and must send
* wl_output.done instead.
*/
void (*done)(void *data,
struct zxdg_output_v1 *zxdg_output_v1);
/**
* name of this output
*
* Many compositors will assign names to their outputs, show them
* to the user, allow them to be configured by name, etc. The
* client may wish to know this name as well to offer the user
* similar behaviors.
*
* The naming convention is compositor defined, but limited to
* alphanumeric characters and dashes (-). Each name is unique
* among all wl_output globals, but if a wl_output global is
* destroyed the same name may be reused later. The names will also
* remain consistent across sessions with the same hardware and
* software configuration.
*
* Examples of names include 'HDMI-A-1', 'WL-1', 'X11-1', etc.
* However, do not assume that the name is a reflection of an
* underlying DRM connector, X11 connection, etc.
*
* The name event is sent after creating an xdg_output (see
* xdg_output_manager.get_xdg_output). This event is only sent once
* per xdg_output, and the name does not change over the lifetime
* of the wl_output global.
* @param name output name
* @since 2
*/
void (*name)(void *data,
struct zxdg_output_v1 *zxdg_output_v1,
const char *name);
/**
* human-readable description of this output
*
* Many compositors can produce human-readable descriptions of
* their outputs. The client may wish to know this description as
* well, to communicate the user for various purposes.
*
* The description is a UTF-8 string with no convention defined for
* its contents. Examples might include 'Foocorp 11" Display' or
* 'Virtual X11 output via :1'.
*
* The description event is sent after creating an xdg_output (see
* xdg_output_manager.get_xdg_output) and whenever the description
* changes. The description is optional, and may not be sent at
* all.
*
* For objects of version 2 and lower, this event is only sent once
* per xdg_output, and the description does not change over the
* lifetime of the wl_output global.
* @param description output description
* @since 2
*/
void (*description)(void *data,
struct zxdg_output_v1 *zxdg_output_v1,
const char *description);
};
/**
* @ingroup iface_zxdg_output_v1
*/
static inline int
zxdg_output_v1_add_listener(struct zxdg_output_v1 *zxdg_output_v1,
const struct zxdg_output_v1_listener *listener, void *data)
{
return wl_proxy_add_listener((struct wl_proxy *) zxdg_output_v1,
(void (**)(void)) listener, data);
}
#define ZXDG_OUTPUT_V1_DESTROY 0
/**
* @ingroup iface_zxdg_output_v1
*/
#define ZXDG_OUTPUT_V1_LOGICAL_POSITION_SINCE_VERSION 1
/**
* @ingroup iface_zxdg_output_v1
*/
#define ZXDG_OUTPUT_V1_LOGICAL_SIZE_SINCE_VERSION 1
/**
* @ingroup iface_zxdg_output_v1
*/
#define ZXDG_OUTPUT_V1_DONE_SINCE_VERSION 1
/**
* @ingroup iface_zxdg_output_v1
*/
#define ZXDG_OUTPUT_V1_NAME_SINCE_VERSION 2
/**
* @ingroup iface_zxdg_output_v1
*/
#define ZXDG_OUTPUT_V1_DESCRIPTION_SINCE_VERSION 2
/**
* @ingroup iface_zxdg_output_v1
*/
#define ZXDG_OUTPUT_V1_DESTROY_SINCE_VERSION 1
/** @ingroup iface_zxdg_output_v1 */
static inline void
zxdg_output_v1_set_user_data(struct zxdg_output_v1 *zxdg_output_v1, void *user_data)
{
wl_proxy_set_user_data((struct wl_proxy *) zxdg_output_v1, user_data);
}
/** @ingroup iface_zxdg_output_v1 */
static inline void *
zxdg_output_v1_get_user_data(struct zxdg_output_v1 *zxdg_output_v1)
{
return wl_proxy_get_user_data((struct wl_proxy *) zxdg_output_v1);
}
static inline uint32_t
zxdg_output_v1_get_version(struct zxdg_output_v1 *zxdg_output_v1)
{
return wl_proxy_get_version((struct wl_proxy *) zxdg_output_v1);
}
/**
* @ingroup iface_zxdg_output_v1
*
* Using this request a client can tell the server that it is not
* going to use the xdg_output object anymore.
*/
static inline void
zxdg_output_v1_destroy(struct zxdg_output_v1 *zxdg_output_v1)
{
wl_proxy_marshal((struct wl_proxy *) zxdg_output_v1,
ZXDG_OUTPUT_V1_DESTROY);
wl_proxy_destroy((struct wl_proxy *) zxdg_output_v1);
}
#ifdef __cplusplus
}
#endif
#endif

View File

@ -1,78 +0,0 @@
/* Generated by wayland-scanner 1.18.0 */
/*
* Copyright © 2017 Red Hat Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice (including the next
* paragraph) shall be included in all copies or substantial portions of the
* Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*/
#include <stdlib.h>
#include <stdint.h>
#include "wayland-util.h"
#ifndef __has_attribute
# define __has_attribute(x) 0 /* Compatibility with non-clang compilers. */
#endif
#if (__has_attribute(visibility) || defined(__GNUC__) && __GNUC__ >= 4)
#define WL_PRIVATE __attribute__ ((visibility("hidden")))
#else
#define WL_PRIVATE
#endif
extern const struct wl_interface wl_output_interface;
extern const struct wl_interface zxdg_output_v1_interface;
static const struct wl_interface *xdg_output_unstable_v1_types[] = {
NULL,
NULL,
&zxdg_output_v1_interface,
&wl_output_interface,
};
static const struct wl_message zxdg_output_manager_v1_requests[] = {
{ "destroy", "", xdg_output_unstable_v1_types + 0 },
{ "get_xdg_output", "no", xdg_output_unstable_v1_types + 2 },
};
WL_PRIVATE const struct wl_interface zxdg_output_manager_v1_interface = {
"zxdg_output_manager_v1", 3,
2, zxdg_output_manager_v1_requests,
0, NULL,
};
static const struct wl_message zxdg_output_v1_requests[] = {
{ "destroy", "", xdg_output_unstable_v1_types + 0 },
};
static const struct wl_message zxdg_output_v1_events[] = {
{ "logical_position", "ii", xdg_output_unstable_v1_types + 0 },
{ "logical_size", "ii", xdg_output_unstable_v1_types + 0 },
{ "done", "", xdg_output_unstable_v1_types + 0 },
{ "name", "2s", xdg_output_unstable_v1_types + 0 },
{ "description", "2s", xdg_output_unstable_v1_types + 0 },
};
WL_PRIVATE const struct wl_interface zxdg_output_v1_interface = {
"zxdg_output_v1", 3,
1, zxdg_output_v1_requests,
5, zxdg_output_v1_events,
};

File diff suppressed because it is too large Load Diff

View File

@ -1,181 +0,0 @@
/* Generated by wayland-scanner 1.18.0 */
/*
* Copyright © 2008-2013 Kristian Høgsberg
* Copyright © 2013 Rafael Antognolli
* Copyright © 2013 Jasper St. Pierre
* Copyright © 2010-2013 Intel Corporation
* Copyright © 2015-2017 Samsung Electronics Co., Ltd
* Copyright © 2015-2017 Red Hat Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice (including the next
* paragraph) shall be included in all copies or substantial portions of the
* Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*/
#include <stdlib.h>
#include <stdint.h>
#include "wayland-util.h"
#ifndef __has_attribute
# define __has_attribute(x) 0 /* Compatibility with non-clang compilers. */
#endif
#if (__has_attribute(visibility) || defined(__GNUC__) && __GNUC__ >= 4)
#define WL_PRIVATE __attribute__ ((visibility("hidden")))
#else
#define WL_PRIVATE
#endif
extern const struct wl_interface wl_output_interface;
extern const struct wl_interface wl_seat_interface;
extern const struct wl_interface wl_surface_interface;
extern const struct wl_interface xdg_popup_interface;
extern const struct wl_interface xdg_positioner_interface;
extern const struct wl_interface xdg_surface_interface;
extern const struct wl_interface xdg_toplevel_interface;
static const struct wl_interface *xdg_shell_types[] = {
NULL,
NULL,
NULL,
NULL,
&xdg_positioner_interface,
&xdg_surface_interface,
&wl_surface_interface,
&xdg_toplevel_interface,
&xdg_popup_interface,
&xdg_surface_interface,
&xdg_positioner_interface,
&xdg_toplevel_interface,
&wl_seat_interface,
NULL,
NULL,
NULL,
&wl_seat_interface,
NULL,
&wl_seat_interface,
NULL,
NULL,
&wl_output_interface,
&wl_seat_interface,
NULL,
&xdg_positioner_interface,
NULL,
};
static const struct wl_message xdg_wm_base_requests[] = {
{ "destroy", "", xdg_shell_types + 0 },
{ "create_positioner", "n", xdg_shell_types + 4 },
{ "get_xdg_surface", "no", xdg_shell_types + 5 },
{ "pong", "u", xdg_shell_types + 0 },
};
static const struct wl_message xdg_wm_base_events[] = {
{ "ping", "u", xdg_shell_types + 0 },
};
WL_PRIVATE const struct wl_interface xdg_wm_base_interface = {
"xdg_wm_base", 3,
4, xdg_wm_base_requests,
1, xdg_wm_base_events,
};
static const struct wl_message xdg_positioner_requests[] = {
{ "destroy", "", xdg_shell_types + 0 },
{ "set_size", "ii", xdg_shell_types + 0 },
{ "set_anchor_rect", "iiii", xdg_shell_types + 0 },
{ "set_anchor", "u", xdg_shell_types + 0 },
{ "set_gravity", "u", xdg_shell_types + 0 },
{ "set_constraint_adjustment", "u", xdg_shell_types + 0 },
{ "set_offset", "ii", xdg_shell_types + 0 },
{ "set_reactive", "3", xdg_shell_types + 0 },
{ "set_parent_size", "3ii", xdg_shell_types + 0 },
{ "set_parent_configure", "3u", xdg_shell_types + 0 },
};
WL_PRIVATE const struct wl_interface xdg_positioner_interface = {
"xdg_positioner", 3,
10, xdg_positioner_requests,
0, NULL,
};
static const struct wl_message xdg_surface_requests[] = {
{ "destroy", "", xdg_shell_types + 0 },
{ "get_toplevel", "n", xdg_shell_types + 7 },
{ "get_popup", "n?oo", xdg_shell_types + 8 },
{ "set_window_geometry", "iiii", xdg_shell_types + 0 },
{ "ack_configure", "u", xdg_shell_types + 0 },
};
static const struct wl_message xdg_surface_events[] = {
{ "configure", "u", xdg_shell_types + 0 },
};
WL_PRIVATE const struct wl_interface xdg_surface_interface = {
"xdg_surface", 3,
5, xdg_surface_requests,
1, xdg_surface_events,
};
static const struct wl_message xdg_toplevel_requests[] = {
{ "destroy", "", xdg_shell_types + 0 },
{ "set_parent", "?o", xdg_shell_types + 11 },
{ "set_title", "s", xdg_shell_types + 0 },
{ "set_app_id", "s", xdg_shell_types + 0 },
{ "show_window_menu", "ouii", xdg_shell_types + 12 },
{ "move", "ou", xdg_shell_types + 16 },
{ "resize", "ouu", xdg_shell_types + 18 },
{ "set_max_size", "ii", xdg_shell_types + 0 },
{ "set_min_size", "ii", xdg_shell_types + 0 },
{ "set_maximized", "", xdg_shell_types + 0 },
{ "unset_maximized", "", xdg_shell_types + 0 },
{ "set_fullscreen", "?o", xdg_shell_types + 21 },
{ "unset_fullscreen", "", xdg_shell_types + 0 },
{ "set_minimized", "", xdg_shell_types + 0 },
};
static const struct wl_message xdg_toplevel_events[] = {
{ "configure", "iia", xdg_shell_types + 0 },
{ "close", "", xdg_shell_types + 0 },
};
WL_PRIVATE const struct wl_interface xdg_toplevel_interface = {
"xdg_toplevel", 3,
14, xdg_toplevel_requests,
2, xdg_toplevel_events,
};
static const struct wl_message xdg_popup_requests[] = {
{ "destroy", "", xdg_shell_types + 0 },
{ "grab", "ou", xdg_shell_types + 22 },
{ "reposition", "3ou", xdg_shell_types + 24 },
};
static const struct wl_message xdg_popup_events[] = {
{ "configure", "iiii", xdg_shell_types + 0 },
{ "popup_done", "", xdg_shell_types + 0 },
{ "repositioned", "3u", xdg_shell_types + 0 },
};
WL_PRIVATE const struct wl_interface xdg_popup_interface = {
"xdg_popup", 3,
3, xdg_popup_requests,
3, xdg_popup_events,
};

View File

@ -1,917 +0,0 @@
#define _POSIX_C_SOURCE 200112L
#include "wl.h"
#include <assert.h>
#include <stdlib.h>
#include <stdio.h>
#include <stdbool.h>
#include <wayland-client.h>
#include <wayland-client-protocol.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <time.h>
#include <unistd.h>
#include <linux/input-event-codes.h>
#include <string.h>
#include "protocols/xdg-shell-client-header.h"
#include "protocols/xdg-shell.h"
#include "protocols/wlr-layer-shell-unstable-v1-client-header.h"
#include "protocols/wlr-layer-shell-unstable-v1.h"
#include "protocols/wlr-foreign-toplevel-management-unstable-v1-client-header.h"
#include "protocols/wlr-foreign-toplevel-management-unstable-v1.h"
#include "protocols/idle-client-header.h"
#include "protocols/idle.h"
#include "pool-buffer.h"
#include "../log.h"
#include "../settings.h"
#include "../queues.h"
#include "../input.h"
#include "libgwater-wayland.h"
#define MAX_TOUCHPOINTS 10
struct window_wl {
cairo_surface_t *c_surface;
cairo_t * c_ctx;
GWaterWaylandSource *esrc;
};
struct wl_ctx {
struct wl_display *display;
struct wl_registry *registry;
struct wl_compositor *compositor;
struct wl_shm *shm;
struct zwlr_layer_shell_v1 *layer_shell;
struct wl_seat *seat;
struct wl_list outputs;
struct wl_surface *surface;
struct dunst_output *surface_output;
struct zwlr_layer_surface_v1 *layer_surface;
struct dunst_output *layer_surface_output;
struct wl_callback *frame_callback;
struct org_kde_kwin_idle *idle_handler;
struct org_kde_kwin_idle_timeout *idle_timeout;
struct zwlr_foreign_toplevel_manager_v1 *toplevel_manager;
bool configured;
bool dirty;
bool is_idle;
bool has_idle_monitor;
struct {
struct wl_pointer *wl_pointer;
int32_t x, y;
} pointer;
struct {
struct wl_touch *wl_touch;
struct {
int32_t x, y;
} pts[MAX_TOUCHPOINTS];
} touch;
struct dimensions cur_dim;
int32_t width, height;
struct pool_buffer buffers[2];
struct pool_buffer *current_buffer;
};
struct dunst_output {
uint32_t global_name;
char *name;
struct wl_output *wl_output;
struct wl_list link;
uint32_t scale;
uint32_t subpixel; // TODO do something with it
bool fullscreen;
struct zwlr_foreign_toplevel_handle_v1 *fullscreen_toplevel; // the toplevel that is fullscreened on this output
};
struct wl_ctx ctx;
static void noop() {
// This space intentionally left blank
}
void set_dirty();
static void output_handle_geometry(void *data, struct wl_output *wl_output,
int32_t x, int32_t y, int32_t phy_width, int32_t phy_height,
int32_t subpixel, const char *make, const char *model,
int32_t transform) {
//TODO do something with the subpixel data
struct dunst_output *output = data;
output->subpixel = subpixel;
}
static void output_handle_scale(void *data, struct wl_output *wl_output,
int32_t factor) {
struct dunst_output *output = data;
output->scale = factor;
wake_up();
}
static const struct wl_output_listener output_listener = {
.geometry = output_handle_geometry,
.mode = noop,
.done = noop,
.scale = output_handle_scale,
};
static void create_output( struct wl_output *wl_output, uint32_t global_name) {
struct dunst_output *output = g_malloc0(sizeof(struct dunst_output));
if (output == NULL) {
LOG_E("allocation failed");
return;
}
static int number = 0;
LOG_I("New output found - id %i", number);
output->global_name = global_name;
output->wl_output = wl_output;
output->scale = 1;
output->fullscreen = false;
wl_list_insert(&ctx.outputs, &output->link);
wl_output_set_user_data(wl_output, output);
wl_output_add_listener(wl_output, &output_listener, output);
number++;
}
static void destroy_output(struct dunst_output *output) {
if (ctx.surface_output == output) {
ctx.surface_output = NULL;
}
if (ctx.layer_surface_output == output) {
ctx.layer_surface_output = NULL;
}
wl_list_remove(&output->link);
wl_output_destroy(output->wl_output);
free(output->name);
free(output);
}
static void touch_handle_motion(void *data, struct wl_touch *wl_touch,
uint32_t time, int32_t id,
wl_fixed_t surface_x, wl_fixed_t surface_y) {
if (id >= MAX_TOUCHPOINTS) {
return;
}
ctx.touch.pts[id].x = wl_fixed_to_int(surface_x);
ctx.touch.pts[id].y = wl_fixed_to_int(surface_y);
}
static void touch_handle_down(void *data, struct wl_touch *wl_touch,
uint32_t serial, uint32_t time, struct wl_surface *sfc, int32_t id,
wl_fixed_t surface_x, wl_fixed_t surface_y) {
if (id >= MAX_TOUCHPOINTS) {
return;
}
ctx.touch.pts[id].x = wl_fixed_to_int(surface_x);
ctx.touch.pts[id].y = wl_fixed_to_int(surface_y);
}
static void touch_handle_up(void *data, struct wl_touch *wl_touch,
uint32_t serial, uint32_t time, int32_t id) {
if (id >= MAX_TOUCHPOINTS) {
return;
}
input_handle_click(BTN_TOUCH, false,
ctx.touch.pts[id].x, ctx.touch.pts[id].y);
}
static void pointer_handle_motion(void *data, struct wl_pointer *wl_pointer,
uint32_t time, wl_fixed_t surface_x, wl_fixed_t surface_y) {
ctx.pointer.x = wl_fixed_to_int(surface_x);
ctx.pointer.y = wl_fixed_to_int(surface_y);
}
static void pointer_handle_button(void *data, struct wl_pointer *wl_pointer,
uint32_t serial, uint32_t time, uint32_t button,
uint32_t button_state) {
input_handle_click(button, button_state, ctx.pointer.x, ctx.pointer.y);
}
static const struct wl_pointer_listener pointer_listener = {
.enter = noop,
.leave = noop,
.motion = pointer_handle_motion,
.button = pointer_handle_button,
.axis = noop,
};
static const struct wl_touch_listener touch_listener = {
.down = touch_handle_down,
.up = touch_handle_up,
.motion = touch_handle_motion,
.frame = noop,
.cancel = noop,
};
static void seat_handle_capabilities(void *data, struct wl_seat *wl_seat,
uint32_t capabilities) {
if (ctx.pointer.wl_pointer != NULL) {
wl_pointer_release(ctx.pointer.wl_pointer);
ctx.pointer.wl_pointer = NULL;
}
if (capabilities & WL_SEAT_CAPABILITY_POINTER) {
ctx.pointer.wl_pointer = wl_seat_get_pointer(wl_seat);
wl_pointer_add_listener(ctx.pointer.wl_pointer,
&pointer_listener, ctx.seat);
}
if (ctx.touch.wl_touch != NULL) {
wl_touch_release(ctx.touch.wl_touch);
ctx.touch.wl_touch = NULL;
}
if (capabilities & WL_SEAT_CAPABILITY_TOUCH) {
ctx.touch.wl_touch = wl_seat_get_touch(wl_seat);
wl_touch_add_listener(ctx.touch.wl_touch,
&touch_listener, ctx.seat);
}
}
static const struct wl_seat_listener seat_listener = {
.capabilities = seat_handle_capabilities,
.name = noop,
};
static void surface_handle_enter(void *data, struct wl_surface *surface,
struct wl_output *wl_output) {
// Don't bother keeping a list of outputs, a layer surface can only be on
// one output a a time
ctx.surface_output = wl_output_get_user_data(wl_output);
set_dirty();
}
static void surface_handle_leave(void *data, struct wl_surface *surface,
struct wl_output *wl_output) {
ctx.surface_output = NULL;
}
static const struct wl_surface_listener surface_listener = {
.enter = surface_handle_enter,
.leave = surface_handle_leave,
};
static void schedule_frame_and_commit();
static void send_frame();
static void layer_surface_handle_configure(void *data,
struct zwlr_layer_surface_v1 *surface,
uint32_t serial, uint32_t width, uint32_t height) {
ctx.configured = true;
ctx.width = width;
ctx.height = height;
// not needed as it is set somewhere else
/* zwlr_layer_surface_v1_set_size(surface, width, height); */
zwlr_layer_surface_v1_ack_configure(surface, serial);
send_frame();
}
static void layer_surface_handle_closed(void *data,
struct zwlr_layer_surface_v1 *surface) {
LOG_I("Destroying layer");
if (ctx.layer_surface)
zwlr_layer_surface_v1_destroy(ctx.layer_surface);
ctx.layer_surface = NULL;
if (ctx.surface)
wl_surface_destroy(ctx.surface);
ctx.surface = NULL;
if (ctx.frame_callback) {
wl_callback_destroy(ctx.frame_callback);
ctx.frame_callback = NULL;
ctx.dirty = true;
}
if (ctx.configured) {
ctx.configured = false;
ctx.width = ctx.height = 0;
ctx.dirty = true;
}
if (ctx.dirty) {
schedule_frame_and_commit();
}
}
static const struct zwlr_layer_surface_v1_listener layer_surface_listener = {
.configure = layer_surface_handle_configure,
.closed = layer_surface_handle_closed,
};
static void idle_start (void *data, struct org_kde_kwin_idle_timeout *org_kde_kwin_idle_timeout) {
ctx.is_idle = true;
LOG_D("User went idle");
}
static void idle_stop (void *data, struct org_kde_kwin_idle_timeout *org_kde_kwin_idle_timeout) {
ctx.is_idle = false;
LOG_D("User isn't idle anymore");
}
static const struct org_kde_kwin_idle_timeout_listener idle_timeout_listener = {
.idle = idle_start,
.resumed = idle_stop,
};
static void add_seat_to_idle_handler(struct wl_seat *seat) {
if (!ctx.idle_handler){
return;
}
if (settings.idle_threshold > 0) {
uint32_t timeout_ms = settings.idle_threshold/1000;
ctx.idle_timeout = org_kde_kwin_idle_get_idle_timeout(ctx.idle_handler, seat, timeout_ms);
org_kde_kwin_idle_timeout_add_listener(ctx.idle_timeout, &idle_timeout_listener, 0);
ctx.has_idle_monitor = true;
}
}
// Warning, can return NULL
static struct dunst_output *get_configured_output() {
int n = 0;
int target_monitor = settings.monitor;
struct dunst_output *first_output = NULL, *configured_output = NULL,
*tmp_output = NULL;
wl_list_for_each(tmp_output, &ctx.outputs, link) {
if (n == 0)
first_output = tmp_output;
if (n == target_monitor)
configured_output = tmp_output;
n++;
}
// There's only 1 output, so return that
if (n == 1)
return first_output;
switch (settings.f_mode){
case FOLLOW_NONE: ; // this semicolon is neccesary
if (!configured_output) {
LOG_W("Monitor %i doesn't exist, using focused monitor", settings.monitor);
}
return configured_output;
case FOLLOW_MOUSE:
// fallthrough
case FOLLOW_KEYBOARD:
// fallthrough
default:
return NULL;
}
}
// does not do null checking
static void dunst_output_set_fullscreen(struct dunst_output *output,
struct zwlr_foreign_toplevel_handle_v1 *toplevel,
bool fullscreen) {
output->fullscreen = fullscreen;
if (fullscreen)
output->fullscreen_toplevel = toplevel;
else
output->fullscreen_toplevel = NULL;
LOG_D("Set output %i fullscreen state %i", output->global_name, fullscreen);
wake_up();
}
static void toplevel_output_leave(void *data,
struct zwlr_foreign_toplevel_handle_v1 *toplevel,
struct wl_output *output) {
zwlr_foreign_toplevel_handle_v1_set_user_data(toplevel, NULL);
}
static void toplevel_output_enter(void *data,
struct zwlr_foreign_toplevel_handle_v1 *toplevel,
struct wl_output *output) {
// FIXME toplevel can be on multiple outputs, so a list of outputs should be kept
zwlr_foreign_toplevel_handle_v1_set_user_data(toplevel, output);
}
static void toplevel_closed(void *data,
struct zwlr_foreign_toplevel_handle_v1 *toplevel) {
struct wl_output *output_wl = (struct wl_output*) data;
if (output_wl == NULL) {
return;
}
struct dunst_output *output = (struct dunst_output*) wl_output_get_user_data(output_wl);
if (output == NULL) {
return;
}
dunst_output_set_fullscreen(output, toplevel, false);
}
static void toplevel_state(void *data,
struct zwlr_foreign_toplevel_handle_v1 *toplevel,
struct wl_array *state) {
struct wl_output *output_wl = (struct wl_output*) data;
if (output_wl == NULL) {
return;
}
struct dunst_output *output = (struct dunst_output*) wl_output_get_user_data(output_wl);
if (output == NULL) {
return;
}
bool fullscreen = false;
bool activated = false;
enum zwlr_foreign_toplevel_handle_v1_state* element;
wl_array_for_each(element, state){
if (*element == ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_FULLSCREEN) {
fullscreen = true;
}
if (*element == ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_ACTIVATED) {
activated = true;
}
}
if (fullscreen && activated) {
dunst_output_set_fullscreen(output, toplevel, true);
} else {
if (output->fullscreen_toplevel == toplevel) {
// this toplevel was fullscreen, but isn't anymore
dunst_output_set_fullscreen(output, toplevel, false);
}
}
}
static const struct zwlr_foreign_toplevel_handle_v1_listener foreign_toplevel_handle_listener = {
.title = noop,
.app_id = noop,
.output_enter = toplevel_output_enter,
.output_leave = toplevel_output_leave,
.state = toplevel_state,
.done = noop,
.closed = toplevel_closed,
};
static void toplevel_created(void *data,
struct zwlr_foreign_toplevel_manager_v1 *zwlr_foreign_toplevel_manager_v1,
struct zwlr_foreign_toplevel_handle_v1 *toplevel){
zwlr_foreign_toplevel_handle_v1_add_listener(toplevel, &foreign_toplevel_handle_listener, NULL);
}
static void toplevel_finished(void *data,
struct zwlr_foreign_toplevel_manager_v1 *zwlr_foreign_toplevel_manager_v1){
}
static const struct zwlr_foreign_toplevel_manager_v1_listener foreign_toplevel_manager_listener = {
.toplevel = toplevel_created,
.finished = toplevel_finished,
};
static void handle_global(void *data, struct wl_registry *registry,
uint32_t name, const char *interface, uint32_t version) {
int *count = data;
if (*count == 0)
{
if (strcmp(interface, wl_compositor_interface.name) == 0) {
ctx.compositor = wl_registry_bind(registry, name,
&wl_compositor_interface, 4);
} else if (strcmp(interface, wl_shm_interface.name) == 0) {
ctx.shm = wl_registry_bind(registry, name,
&wl_shm_interface, 1);
} else if (strcmp(interface, zwlr_layer_shell_v1_interface.name) == 0) {
ctx.layer_shell = wl_registry_bind(registry, name,
&zwlr_layer_shell_v1_interface, 1);
} else if (strcmp(interface, wl_seat_interface.name) == 0) {
ctx.seat = wl_registry_bind(registry, name, &wl_seat_interface, 3);
wl_seat_add_listener(ctx.seat, &seat_listener, ctx.seat);
add_seat_to_idle_handler(ctx.seat);
} else if (strcmp(interface, wl_output_interface.name) == 0) {
struct wl_output *output =
wl_registry_bind(registry, name, &wl_output_interface, 3);
create_output(output, name);
} else if (strcmp(interface, org_kde_kwin_idle_interface.name) == 0 &&
version >= ORG_KDE_KWIN_IDLE_TIMEOUT_IDLE_SINCE_VERSION) {
ctx.idle_handler = wl_registry_bind(registry, name, &org_kde_kwin_idle_interface, 1);
}
} else {
if (strcmp(interface, zwlr_foreign_toplevel_manager_v1_interface.name) == 0 &&
version >= ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_FULLSCREEN_SINCE_VERSION) {
// Only bind after the second pass to bind after binding to all the outputs.
// This is because otherwise toplevel_enter evens won't be sent.
ctx.toplevel_manager = wl_registry_bind(registry, name, &zwlr_foreign_toplevel_manager_v1_interface, 2);
zwlr_foreign_toplevel_manager_v1_add_listener(ctx.toplevel_manager, &foreign_toplevel_manager_listener, NULL);
}
}
}
static void handle_global_remove(void *data, struct wl_registry *registry,
uint32_t name) {
struct dunst_output *output, *tmp;
wl_list_for_each_safe(output, tmp, &ctx.outputs, link) {
if (output->global_name == name) {
destroy_output(output);
break;
}
}
}
static const struct wl_registry_listener registry_listener = {
.global = handle_global,
.global_remove = handle_global_remove,
};
bool wl_init() {
wl_list_init(&ctx.outputs);
//wl_list_init(&ctx.seats); // TODO multi-seat support
ctx.display = wl_display_connect(NULL);
if (ctx.display == NULL) {
LOG_W("failed to create display");
return false;
}
int count = 0;
ctx.registry = wl_display_get_registry(ctx.display);
wl_registry_add_listener(ctx.registry, &registry_listener, &count);
wl_display_roundtrip(ctx.display);
count = 1;
// we need a second pass to let for foreign_toplevel (look there for more info)
ctx.registry = wl_display_get_registry(ctx.display);
wl_registry_add_listener(ctx.registry, &registry_listener, &count);
wl_display_roundtrip(ctx.display);
if (ctx.compositor == NULL) {
LOG_W("compositor doesn't support wl_compositor");
return false;
}
if (ctx.shm == NULL) {
LOG_W("compositor doesn't support wl_shm");
return false;
}
if (ctx.layer_shell == NULL) {
LOG_W("compositor doesn't support zwlr_layer_shell_v1");
return false;
}
if (ctx.seat == NULL) {
LOG_W("no seat was found, so dunst cannot see input");
} else {
if (ctx.idle_handler == NULL) {
LOG_I("compositor doesn't support org_kde_kwin_idle_interface");
}
else if (ctx.idle_timeout == NULL) {
// something went wrong in setting the timeout
LOG_W("couldn't set idle timeout");
}
}
if (ctx.toplevel_manager == NULL) {
LOG_W("compositor doesn't support zwlr_foreign_toplevel_v1. Fullscreen detection won't work");
}
return true;
}
void wl_deinit() {
// We need to check if any of these are NULL, since the initialization
// could have been aborted half way through, or the compositor doesn't
// support some of these features.
if (ctx.layer_surface != NULL) {
zwlr_layer_surface_v1_destroy(ctx.layer_surface);
}
if (ctx.surface != NULL) {
wl_surface_destroy(ctx.surface);
}
finish_buffer(&ctx.buffers[0]);
finish_buffer(&ctx.buffers[1]);
// The output list is initialized at the start of init, so no need to
// check for NULL
struct dunst_output *output, *output_tmp;
wl_list_for_each_safe(output, output_tmp, &ctx.outputs, link) {
destroy_output(output);
}
if (ctx.seat) {
if (ctx.pointer.wl_pointer)
wl_pointer_release(ctx.pointer.wl_pointer);
wl_seat_release(ctx.seat);
ctx.seat = NULL;
}
if (ctx.idle_handler)
org_kde_kwin_idle_destroy(ctx.idle_handler);
if (ctx.idle_timeout)
org_kde_kwin_idle_timeout_release(ctx.idle_timeout);
if (ctx.layer_shell)
zwlr_layer_shell_v1_destroy(ctx.layer_shell);
if (ctx.compositor)
wl_compositor_destroy(ctx.compositor);
if (ctx.shm)
wl_shm_destroy(ctx.shm);
if (ctx.registry)
wl_registry_destroy(ctx.registry);
if (ctx.display)
wl_display_disconnect(ctx.display);
}
static void schedule_frame_and_commit();
// Draw and commit a new frame.
static void send_frame() {
int scale = wl_get_scale();
struct dunst_output *output = get_configured_output();
int height = ctx.cur_dim.h;
int width = ctx.cur_dim.w;
// There are two cases where we want to tear down the surface: zero
// notifications (height = 0) or moving between outputs.
if (height == 0 || ctx.layer_surface_output != output) {
if (ctx.layer_surface != NULL) {
zwlr_layer_surface_v1_destroy(ctx.layer_surface);
ctx.layer_surface = NULL;
}
if (ctx.surface != NULL) {
wl_surface_destroy(ctx.surface);
ctx.surface = NULL;
}
ctx.width = ctx.height = 0;
ctx.surface_output = NULL;
ctx.configured = false;
}
// If there are no notifications, there's no point in recreating the
// surface right now.
if (height == 0) {
ctx.dirty = false;
return;
}
// If we've made it here, there is something to draw. If the surface
// doesn't exist (this is the first notification, or we moved to a
// different output), we need to create it.
if (ctx.layer_surface == NULL) {
struct wl_output *wl_output = NULL;
if (output != NULL) {
wl_output = output->wl_output;
}
ctx.layer_surface_output = output;
ctx.surface = wl_compositor_create_surface(ctx.compositor);
wl_surface_add_listener(ctx.surface, &surface_listener, NULL);
ctx.layer_surface = zwlr_layer_shell_v1_get_layer_surface(
ctx.layer_shell, ctx.surface, wl_output,
settings.layer, "notifications");
zwlr_layer_surface_v1_add_listener(ctx.layer_surface,
&layer_surface_listener, NULL);
// Because we're creating a new surface, we aren't going to draw
// anything into it during this call. We don't know what size the
// surface will be until we've asked the compositor for what we want
// and it has responded with what it actually gave us. We also know
// that the height we would _like_ to draw (greater than zero, or we
// would have bailed already) is different from our ctx.height
// (which has to be zero here), so we can fall through to the next
// block to let it set the size for us.
}
assert(ctx.layer_surface);
// We now want to resize the surface if it isn't the right size. If the
// surface is brand new, it doesn't even have a size yet. If it already
// exists, we might need to resize if the list of notifications has changed
// since the last time we drew.
if (ctx.height != height || ctx.width != width) {
struct dimensions dim = ctx.cur_dim;
// Set window size
zwlr_layer_surface_v1_set_size(ctx.layer_surface,
dim.w, dim.h);
// TODO Do this only once
uint32_t anchor = 0;
if (settings.geometry.negative_x) {
anchor |= ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT;
} else {
anchor |= ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT;
}
if (settings.geometry.negative_y) {
anchor |= ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM;
} else {
anchor |= ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP;
}
// Put the window at the right position
zwlr_layer_surface_v1_set_anchor(ctx.layer_surface,
anchor);
zwlr_layer_surface_v1_set_margin(ctx.layer_surface,
abs(settings.geometry.y), // top
abs(settings.geometry.x), // right
abs(settings.geometry.y), // bottom
abs(settings.geometry.x));// left
wl_surface_commit(ctx.surface);
// Now we're going to bail without drawing anything. This gives the
// compositor a chance to create the surface and tell us what size we
// were actually granted, which may be smaller than what we asked for
// depending on the screen size and layout of other layer surfaces.
// This information is provided in layer_surface_handle_configure,
// which will then call send_frame again. When that call happens, the
// layer surface will exist and the height will hopefully match what
// we asked for. That means we won't return here, and will actually
// draw into the surface down below.
// TODO: If the compositor doesn't send a configure with the size we
// requested, we'll enter an infinite loop. We need to keep track of
// the fact that a request was sent separately from what height we are.
wl_display_roundtrip(ctx.display);
return;
}
assert(ctx.configured);
// Yay we can finally draw something!
wl_surface_set_buffer_scale(ctx.surface, scale);
wl_surface_damage_buffer(ctx.surface, 0, 0, INT32_MAX, INT32_MAX);
wl_surface_attach(ctx.surface, ctx.current_buffer->buffer, 0, 0);
ctx.current_buffer->busy = true;
// Schedule a frame in case the state becomes dirty again
schedule_frame_and_commit();
ctx.dirty = false;
}
static void frame_handle_done(void *data, struct wl_callback *callback,
uint32_t time) {
wl_callback_destroy(ctx.frame_callback);
ctx.frame_callback = NULL;
// Only draw again if we need to
if (ctx.dirty) {
send_frame();
}
}
static const struct wl_callback_listener frame_listener = {
.done = frame_handle_done,
};
static void schedule_frame_and_commit() {
if (ctx.frame_callback) {
return;
}
if (ctx.surface == NULL) {
// We don't yet have a surface, create it immediately
send_frame();
return;
}
ctx.frame_callback = wl_surface_frame(ctx.surface);
wl_callback_add_listener(ctx.frame_callback, &frame_listener, NULL);
wl_surface_commit(ctx.surface);
}
void set_dirty() {
if (ctx.dirty) {
return;
}
ctx.dirty = true;
schedule_frame_and_commit();
}
window wl_win_create(void) {
struct window_wl *win = g_malloc0(sizeof(struct window_wl));
win->esrc = g_water_wayland_source_new_for_display(NULL, ctx.display);
return win;
}
void wl_win_destroy(window winptr) {
struct window_wl *win = (struct window_wl*)winptr;
g_water_wayland_source_free(win->esrc);
// FIXME: Dealloc everything
g_free(win);
}
void wl_win_show(window win) {
// This is here for compatibilty with the X11 output. The window is
// already shown in wl_display_surface.
}
void wl_win_hide(window win) {
LOG_I("Wayland: Hiding window");
ctx.cur_dim.h = 0;
set_dirty();
wl_display_roundtrip(ctx.display);
}
void wl_display_surface(cairo_surface_t *srf, window winptr, const struct dimensions* dim) {
/* struct window_wl *win = (struct window_wl*)winptr; */
int scale = wl_get_scale();
LOG_D("Buffer size (scaled) %ix%i", dim->w * scale, dim->h * scale);
ctx.current_buffer = get_next_buffer(ctx.shm, ctx.buffers,
dim->w * scale, dim->h * scale);
cairo_t *c = ctx.current_buffer->cairo;
cairo_save(c);
cairo_set_source_surface(c, srf, 0, 0);
cairo_rectangle(c, 0, 0, dim->w * scale, dim->h * scale);
cairo_fill(c);
cairo_restore(c);
ctx.cur_dim = *dim;
set_dirty();
wl_display_roundtrip(ctx.display);
}
cairo_t* wl_win_get_context(window winptr) {
struct window_wl *win = (struct window_wl*)winptr;
ctx.current_buffer = get_next_buffer(ctx.shm, ctx.buffers, 500, 500);
win->c_surface = ctx.current_buffer->surface;
win->c_ctx = ctx.current_buffer->cairo;
return win->c_ctx;
}
const struct screen_info* wl_get_active_screen(void) {
// TODO Screen size detection
static struct screen_info scr = {
.w = 1920,
.h = 1080,
.x = 0,
.y = 0,
.id = 0,
.mmh = 500
};
scr.dpi = wl_get_scale() * 96;
return &scr;
}
bool wl_is_idle(void) {
LOG_I("Idle status queried: %i", ctx.is_idle);
// When the user doesn't have a seat, or their compositor doesn't support the idle
// protocol, we'll assume that they are not idle.
if (settings.idle_threshold == 0 || ctx.has_idle_monitor == false) {
return false;
} else {
return ctx.is_idle;
}
}
bool wl_have_fullscreen_window(void) {
bool have_fullscreen = false;
struct dunst_output *current_output = get_configured_output();
if (!current_output) {
// Cannot detect focused output, so return true if any of the
// outputs is fullscreen. This will work even when unfocused
// outputs have fullscreen toplevels, since a toplevel has to
// be fullscreen and activate to consider an output fullscreen.
struct dunst_output *output;
wl_list_for_each(output, &ctx.outputs, link) {
have_fullscreen |= output->fullscreen;
}
} else {
have_fullscreen = current_output->fullscreen;
}
LOG_D("Fullscreen queried: %i", have_fullscreen);
return have_fullscreen;
}
int wl_get_scale(void) {
int scale = 0;
struct dunst_output *output = get_configured_output();
if (output) {
scale = output->scale;
} else {
// return the largest scale
struct dunst_output *output;
wl_list_for_each(output, &ctx.outputs, link) {
scale = MAX(output->scale, scale);
}
}
if (scale <= 0)
scale = 1;
return scale;
}
/* vim: set ft=c tabstop=8 shiftwidth=8 expandtab textwidth=0: */

View File

@ -1,32 +0,0 @@
#ifndef DUNST_WL_H
#define DUNST_WL_H
#include <stdbool.h>
#include <cairo.h>
#include <glib.h>
#include "../output.h"
bool wl_init(void);
void wl_deinit(void);
window wl_win_create(void);
void wl_win_destroy(window);
void wl_win_show(window);
void wl_win_hide(window);
void wl_display_surface(cairo_surface_t *srf, window win, const struct dimensions*);
cairo_t* wl_win_get_context(window);
const struct screen_info* wl_get_active_screen(void);
bool wl_is_idle(void);
bool wl_have_fullscreen_window(void);
// Return the dpi scaling of the current output. Everything that's rendered
// should be multiplied by this value, but don't use it to multiply other
// values. All sizes should be in unscaled units.
int wl_get_scale(void);
#endif
/* vim: set ft=c tabstop=8 shiftwidth=8 expandtab textwidth=0: */

View File

@ -1,5 +1,12 @@
#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>
@ -7,130 +14,96 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <X11/extensions/randr.h>
#include <X11/extensions/Xinerama.h>
#include <X11/extensions/Xrandr.h>
#include <X11/Xatom.h>
#include <X11/X.h>
#include <X11/Xlib.h>
#include <X11/Xresource.h>
#include "../log.h"
#include "../settings.h"
#include "../utils.h"
#include "src/settings.h"
#include "x.h"
struct screen_info *screens;
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);
void randr_update(void);
void xinerama_update(void);
void screen_update_fallback(void);
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);
/**
* A cache variable to cache the Xft.dpi xrdb values.
* We do not expect to change xrdb often, but there's much
* overhead to query it once.
*
* @retval -DBL_MAX: uncached
* @retval <=0: Invalid and unusable value
* @retval >0: valid
*/
double screen_dpi_xft_cache = -DBL_MAX;
void screen_dpi_xft_cache_purge(void)
static double get_xft_dpi_value()
{
screen_dpi_xft_cache = -DBL_MAX;
}
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);
static double screen_dpi_get_from_xft(void)
{
if (screen_dpi_xft_cache == -DBL_MAX) {
screen_dpi_xft_cache = 0;
if (xRMS == NULL) {
dpi = 0;
return 0;
}
XrmDatabase xDB = XrmGetStringDatabase(xRMS);
char *xrmType;
XrmValue xrmValue;
XrmDatabase db = XrmGetDatabase(xctx.dpy);
ASSERT_OR_RET(db, screen_dpi_xft_cache);
if (XrmGetResource(db, "Xft.dpi", "Xft.dpi", &xrmType, &xrmValue))
screen_dpi_xft_cache = strtod(xrmValue.addr, NULL);
if (XrmGetResource(xDB, "Xft.dpi", "Xft.dpi", &xrmType, &xrmValue)) {
dpi = strtod(xrmValue.addr, NULL);
} else {
dpi = 0;
}
XrmDestroyDatabase(xDB);
}
return screen_dpi_xft_cache;
return dpi;
}
static double screen_dpi_get_from_monitor(const struct screen_info *scr)
void init_screens()
{
return (double)scr->h * 25.4 / (double)scr->mmh;
}
double screen_dpi_get(const struct screen_info *scr)
{
if ( ! settings.force_xinerama
&& settings.per_monitor_dpi)
return screen_dpi_get_from_monitor(scr);
if (screen_dpi_get_from_xft() > 0)
return screen_dpi_get_from_xft();
// Calculate the DPI on the overall screen size.
// xrandr --dpi <DPI> does only change the overall screen's millimeters,
// but not the physical screen's sizes.
//
// The screen parameter is XDefaultScreen(), as our scr->id references
// the xrandr monitor and not the xrandr screen
return ((((double)DisplayWidth (xctx.dpy, XDefaultScreen(xctx.dpy))) * 25.4) /
((double)DisplayWidthMM(xctx.dpy, XDefaultScreen(xctx.dpy))));
}
void init_screens(void)
{
if (settings.force_xinerama) {
xinerama_update();
} else {
if (!settings.force_xinerama) {
randr_init();
randr_update();
} else {
xinerama_update();
}
}
void alloc_screen_ar(int n)
{
assert(n > 0);
g_free(screens);
screens = g_malloc0(n * sizeof(struct screen_info));
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(void)
void randr_init()
{
int ignored;
if (!XRRQueryExtension(xctx.dpy, &ignored, &ignored)) {
LOG_W("Could not initialize the RandR extension. "
"Falling back to single monitor mode.");
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(void)
void randr_update()
{
if (randr_major_version < 1
|| (randr_major_version == 1 && randr_minor_version < 5)) {
LOG_W("Server RandR version too low (%i.%i). "
"Falling back to single monitor mode.",
randr_major_version,
randr_minor_version);
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;
}
@ -139,8 +112,7 @@ void randr_update(void)
XRRMonitorInfo *m = XRRGetMonitors(xctx.dpy, RootWindow(xctx.dpy, DefaultScreen(xctx.dpy)), true, &n);
if (n < 1) {
LOG_C("Get monitors reported %i monitors. "
"Falling back to single monitor mode.", n);
fprintf(stderr, "Get monitors reported %i monitors, falling back to single monitor mode\n", n);
screen_update_fallback();
return;
}
@ -150,37 +122,34 @@ void randr_update(void)
alloc_screen_ar(n);
for (int i = 0; i < n; i++) {
screens[i].id = i;
screens[i].x = m[i].x;
screens[i].y = m[i].y;
screens[i].w = m[i].width;
screens[i].h = m[i].height;
screens[i].mmh = m[i].mheight;
screens[i].dpi = screen_dpi_get(&screens[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);
}
bool screen_check_event(XEvent *ev)
static int autodetect_dpi(screen_info *scr)
{
if (XRRUpdateConfiguration(ev)) {
LOG_D("XEvent: processing 'RRScreenChangeNotify'");
randr_update();
return true;
}
return false;
return (double)scr->dim.h * 25.4 / (double)scr->dim.mmh;
}
void xinerama_update(void)
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) {
LOG_W("Could not get xinerama screen info. "
"Falling back to single monitor mode.");
fprintf(stderr, "(Xinerama) Could not get screen info, falling back to single monitor mode\n");
screen_update_fallback();
return;
}
@ -188,16 +157,15 @@ void xinerama_update(void)
alloc_screen_ar(n);
for (int i = 0; i < n; i++) {
screens[i].id = i;
screens[i].x = info[i].x_org;
screens[i].y = info[i].y_org;
screens[i].h = info[i].height;
screens[i].w = info[i].width;
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(void)
void screen_update_fallback()
{
alloc_screen_ar(1);
@ -207,146 +175,36 @@ void screen_update_fallback(void)
else
screen = DefaultScreen(xctx.dpy);
screens[0].w = DisplayWidth(xctx.dpy, screen);
screens[0].h = DisplayHeight(xctx.dpy, screen);
}
/* see screen.h */
bool have_fullscreen_window(void)
{
return window_is_fullscreen(get_focused_window());
}
/**
* X11 ErrorHandler to mainly discard BadWindow parameter error
*/
static int XErrorHandlerFullscreen(Display *display, XErrorEvent *e)
{
/* Ignore BadWindow errors. Window may have been gone */
if (e->error_code == BadWindow) {
return 0;
}
char err_buf[BUFSIZ];
XGetErrorText(display, e->error_code, err_buf, BUFSIZ);
fputs(err_buf, stderr);
fputs("\n", stderr);
return 0;
}
/* see screen.h */
bool window_is_fullscreen(Window window)
{
bool fs = false;
ASSERT_OR_RET(window, false);
Atom has_wm_state = XInternAtom(xctx.dpy, "_NET_WM_STATE", True);
if (has_wm_state == None){
return false;
}
XFlush(xctx.dpy);
XSetErrorHandler(XErrorHandlerFullscreen);
Atom actual_type_return;
int actual_format_return;
unsigned long bytes_after_return;
unsigned char *prop_to_return;
unsigned long n_items;
int result = XGetWindowProperty(
xctx.dpy,
window,
has_wm_state,
0, /* long_offset */
sizeof(window), /* long_length */
false, /* delete */
AnyPropertyType, /* req_type */
&actual_type_return,
&actual_format_return,
&n_items,
&bytes_after_return,
&prop_to_return);
XFlush(xctx.dpy);
XSync(xctx.dpy, false);
XSetErrorHandler(NULL);
if (result == Success) {
for(int i = 0; i < n_items; i++) {
Atom atom = ((Atom*) prop_to_return)[i];
if (!atom)
continue;
char *s = XGetAtomName(xctx.dpy, atom);
if (!s)
continue;
if (STR_EQ(s, "_NET_WM_STATE_FULLSCREEN"))
fs = true;
XFree(s);
if (fs)
break;
}
}
if (prop_to_return)
XFree(prop_to_return);
return fs;
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.
*/
const struct screen_info *get_active_screen(void)
screen_info *get_active_screen()
{
int ret = 0;
bool force_follow_mouse = false;
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) {
if (settings.monitor >= 0 && settings.monitor < screens_len) {
ret = settings.monitor;
}
ret = XDefaultScreen(xctx.dpy);
goto sc_cleanup;
} else {
int x, y;
assert(settings.f_mode == FOLLOW_MOUSE
|| settings.f_mode == FOLLOW_KEYBOARD);
x_follow_setup_error_handler();
Window root =
RootWindow(xctx.dpy, DefaultScreen(xctx.dpy));
if (settings.f_mode == FOLLOW_KEYBOARD) {
Window focused = get_focused_window();
if (!focused) {
/*
* If no window is focused, or the focus is set
* to dynamically change to the root window of
* the screen the pointer is on, force following
* the mouse.
*/
force_follow_mouse = true;
} else {
Window child_return;
/*
* The window with input focus might be on a
* different X screen. Use the mouse location
* in that case.
*/
force_follow_mouse = !XTranslateCoordinates(
xctx.dpy, focused,root,
0, 0, &x, &y,
&child_return);
}
}
if (settings.f_mode == FOLLOW_MOUSE || force_follow_mouse) {
if (settings.f_mode == FOLLOW_MOUSE) {
int dummy;
unsigned int dummy_ui;
Window dummy_win;
@ -362,9 +220,24 @@ const struct screen_info *get_active_screen(void)
&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].x, screens[i].y,
screens[i].w, screens[i].h)) {
if (INRECT(x, y, screens[i].dim.x, screens[i].dim.y,
screens[i].dim.w, screens[i].dim.h)) {
ret = i;
}
}
@ -372,8 +245,8 @@ const struct screen_info *get_active_screen(void)
if (ret > 0)
goto sc_cleanup;
/* something seems to be wrong. Fall back to default */
ret = 0;
/* something seems to be wrong. Fallback to default */
ret = XDefaultScreen(xctx.dpy);
goto sc_cleanup;
}
sc_cleanup:
@ -383,19 +256,50 @@ sc_cleanup:
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;
int ignored;
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);
XGetInputFocus(xctx.dpy, &focused, &ignored);
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);
}
if (focused == None || focused == PointerRoot)
focused = 0;
return focused;
}
@ -420,8 +324,9 @@ static int FollowXErrorHandler(Display *display, XErrorEvent *e)
dunst_follow_errored = true;
char err_buf[BUFSIZ];
XGetErrorText(display, e->error_code, err_buf, BUFSIZ);
LOG_W("%s", err_buf);
fputs(err_buf, stderr);
fputs("\n", stderr);
return 0;
}
/* vim: set ft=c tabstop=8 shiftwidth=8 expandtab textwidth=0: */
/* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */

View File

@ -2,38 +2,30 @@
#ifndef DUNST_SCREEN_H
#define DUNST_SCREEN_H
#include <stdbool.h>
#include <X11/Xlib.h>
#define INRECT(x,y,rx,ry,rw,rh) ((x) >= (rx) && (x) < (rx)+(rw) && (y) >= (ry) && (y) < (ry)+(rh))
void init_screens(void);
void screen_dpi_xft_cache_purge(void);
bool screen_check_event(XEvent *ev);
typedef struct _dimension_t {
int x;
int y;
unsigned int h;
unsigned int mmh;
unsigned int w;
int mask;
int negative_width;
} dimension_t;
const struct screen_info *get_active_screen(void);
double screen_dpi_get(const struct screen_info *scr);
typedef struct _screen_info {
int scr;
dimension_t dim;
} screen_info;
/**
* Find the currently focused window and check if it's in
* fullscreen mode
*
* @see window_is_fullscreen()
* @see get_focused_window()
*
* @retval true: the focused window is in fullscreen mode
* @retval false: otherwise
*/
bool have_fullscreen_window(void);
void init_screens();
void screen_check_event(XEvent event);
/**
* Check if window is in fullscreen mode
*
* @param window the x11 window object
* @retval true: \p window is in fullscreen mode
* @retval false: otherwise
*/
bool window_is_fullscreen(Window window);
screen_info *get_active_screen();
double get_dpi_for_screen(screen_info *scr);
#endif
/* vim: set ft=c tabstop=8 shiftwidth=8 expandtab textwidth=0: */
/* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */

File diff suppressed because it is too large Load Diff

View File

@ -2,55 +2,66 @@
#ifndef DUNST_X_H
#define DUNST_X_H
#define XLIB_ILLEGAL_ACCESS
#include <cairo.h>
#include <glib.h>
#include <stdbool.h>
#include <X11/extensions/scrnsaver.h>
#include <X11/X.h>
#include <X11/Xlib.h>
#include "../output.h"
#include <X11/extensions/scrnsaver.h>
#include <glib.h>
#include <stdbool.h>
#include "screen.h"
struct keyboard_shortcut {
#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;
// Cyclical dependency
#include "../settings.h"
struct x_context {
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;
extern struct x_context xctx;
typedef struct _color_t {
double r;
double g;
double b;
} color_t;
extern xctx_t xctx;
/* window */
window x_win_create(void);
void x_win_destroy(window);
void x_win_draw(void);
void x_win_hide(void);
void x_win_show(void);
void x_win_show(window);
void x_win_hide(window);
void x_display_surface(cairo_surface_t *srf, window, const struct dimensions *dim);
cairo_t* x_win_get_context(window);
/* 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);
bool x_setup(void);
void x_setup(void);
void x_free(void);
struct geometry x_parse_geometry(const char *geom_str);
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);
int x_get_scale(void);
#endif
/* vim: set ft=c tabstop=8 shiftwidth=8 expandtab textwidth=0: */
/* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */

View File

@ -1 +0,0 @@
Got'cha! This has to be invalid!

View File

@ -1 +0,0 @@
Got'cha! This has to be invalid!

View File

@ -1 +0,0 @@
../../invalid.png

View File

@ -1 +0,0 @@
../../invalid.svg

View File

@ -1 +0,0 @@
../../valid.png

View File

@ -1 +0,0 @@
../../valid.svg

View File

@ -1 +0,0 @@
../../valid.png

View File

@ -1 +0,0 @@
../../valid.svg

Binary file not shown.

Before

Width:  |  Height:  |  Size: 193 B

View File

@ -1,65 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="16px"
height="16px"
viewBox="0 0 16 16"
version="1.1"
id="SVGRoot"
inkscape:version="0.92.2 5c3e80d, 2017-08-06"
sodipodi:docname="valid.svg">
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="16"
inkscape:cx="4.6127988"
inkscape:cy="8"
inkscape:document-units="px"
inkscape:current-layer="layer1"
showgrid="false"
inkscape:window-width="958"
inkscape:window-height="1034"
inkscape:window-x="960"
inkscape:window-y="46"
inkscape:window-maximized="0"
inkscape:grid-bbox="true" />
<defs
id="defs20" />
<metadata
id="metadata23">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
id="layer1"
inkscape:groupmode="layer"
inkscape:label="Layer 1">
<rect
style="opacity:0.98999999;fill:#cccccc;fill-opacity:1;stroke:#cccccc;stroke-width:1;stroke-miterlimit:4.19999981;stroke-dasharray:none;stroke-opacity:0.15686275"
id="rect36"
width="8"
height="15.4375"
x="3.75"
y="0.25"
ry="4.875" />
</g>
</svg>

Before

Width:  |  Height:  |  Size: 1.9 KiB

View File

@ -22,20 +22,6 @@
simple = A simple string
quoted = "A quoted string"
quoted_with_quotes = "A string "with quotes""
unquoted_with_quotes = A" string with quotes"
quoted_comment = "String with a" # comment
unquoted_comment = String with a # comment
color_comment = "#ffffff" # comment
[list]
simple = A,simple,list
spaces = A, list, with, spaces
multiword = A list, with, multiword entries
quoted = "A, quoted, list"
quoted_with_quotes = "A, list, "with quotes""
unquoted_with_quotes = A, list, "with quotes"
quoted_comment = "List, with, a" # comment
unquoted_comment = List, with, a # comment
[path]
expand_tilde = ~/.path/to/tilde

View File

@ -1,878 +0,0 @@
#define wake_up wake_up_void
#include "../src/dbus.c"
#include "greatest.h"
#include <assert.h>
#include <gdk-pixbuf/gdk-pixbuf.h>
#include <gio/gio.h>
#include "helpers.h"
#include "queues.h"
extern const char *base;
void wake_up_void(void) { }
struct signal_actioninvoked {
guint id;
gchar *key;
guint subscription_id;
GDBusConnection *conn;
};
struct signal_closed {
guint32 id;
guint32 reason;
guint subscription_id;
GDBusConnection *conn;
};
void dbus_signal_cb_actioninvoked(GDBusConnection *connection,
const gchar *sender_name,
const gchar *object_path,
const gchar *interface_name,
const gchar *signal_name,
GVariant *parameters,
gpointer user_data)
{
g_return_if_fail(user_data);
guint32 id;
gchar *key;
struct signal_actioninvoked *sig = (struct signal_actioninvoked*) user_data;
g_variant_get(parameters, "(us)", &id, &key);
if (id == sig->id) {
sig->id = id;
sig->key = key;
}
}
void dbus_signal_subscribe_actioninvoked(struct signal_actioninvoked *actioninvoked)
{
assert(actioninvoked);
actioninvoked->conn = g_bus_get_sync(G_BUS_TYPE_SESSION, NULL, NULL);
actioninvoked->subscription_id =
g_dbus_connection_signal_subscribe(
actioninvoked->conn,
FDN_NAME,
FDN_IFAC,
"ActionInvoked",
FDN_PATH,
NULL,
G_DBUS_SIGNAL_FLAGS_NONE,
dbus_signal_cb_actioninvoked,
actioninvoked,
NULL);
}
void dbus_signal_unsubscribe_actioninvoked(struct signal_actioninvoked *actioninvoked)
{
assert(actioninvoked);
g_dbus_connection_signal_unsubscribe(actioninvoked->conn, actioninvoked->subscription_id);
g_object_unref(actioninvoked->conn);
actioninvoked->conn = NULL;
actioninvoked->subscription_id = -1;
}
void dbus_signal_cb_closed(GDBusConnection *connection,
const gchar *sender_name,
const gchar *object_path,
const gchar *interface_name,
const gchar *signal_name,
GVariant *parameters,
gpointer user_data)
{
g_return_if_fail(user_data);
guint32 id;
guint32 reason;
struct signal_closed *sig = (struct signal_closed*) user_data;
g_variant_get(parameters, "(uu)", &id, &reason);
if (id == sig->id) {
sig->id = id;
sig->reason = reason;
}
}
void dbus_signal_subscribe_closed(struct signal_closed *closed)
{
assert(closed);
closed->conn = g_bus_get_sync(G_BUS_TYPE_SESSION, NULL, NULL);
closed->subscription_id =
g_dbus_connection_signal_subscribe(
closed->conn,
FDN_NAME,
FDN_IFAC,
"NotificationClosed",
FDN_PATH,
NULL,
G_DBUS_SIGNAL_FLAGS_NONE,
dbus_signal_cb_closed,
closed,
NULL);
}
void dbus_signal_unsubscribe_closed(struct signal_closed *closed)
{
assert(closed);
g_dbus_connection_signal_unsubscribe(closed->conn, closed->subscription_id);
g_object_unref(closed->conn);
closed->conn = NULL;
closed->subscription_id = -1;
}
GVariant *dbus_invoke(const char *method, GVariant *params)
{
GDBusConnection *connection_client;
GVariant *retdata;
GError *error = NULL;
connection_client = g_bus_get_sync(G_BUS_TYPE_SESSION, NULL, NULL);
retdata = g_dbus_connection_call_sync(
connection_client,
FDN_NAME,
FDN_PATH,
FDN_IFAC,
method,
params,
NULL,
G_DBUS_CALL_FLAGS_NONE,
-1,
NULL,
&error);
if (error) {
printf("Error while calling GTestDBus instance: %s\n", error->message);
g_error_free(error);
}
g_object_unref(connection_client);
return retdata;
}
struct dbus_notification {
const char* app_name;
guint replaces_id;
const char* app_icon;
const char* summary;
const char* body;
GHashTable *actions;
GHashTable *hints;
int expire_timeout;
};
void g_free_variant_value(gpointer tofree)
{
g_variant_unref((GVariant*) tofree);
}
struct dbus_notification *dbus_notification_new(void)
{
struct dbus_notification *n = g_malloc0(sizeof(struct dbus_notification));
n->expire_timeout = -1;
n->replaces_id = 0;
n->actions = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
n->hints = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free_variant_value);
return n;
}
void dbus_notification_free(struct dbus_notification *n)
{
g_hash_table_unref(n->hints);
g_hash_table_unref(n->actions);
g_free(n);
}
bool dbus_notification_fire(struct dbus_notification *n, uint *id)
{
assert(n);
assert(id);
GVariantBuilder b;
GVariantType *t;
gpointer p_key;
gpointer p_value;
GHashTableIter iter;
t = g_variant_type_new("(susssasa{sv}i)");
g_variant_builder_init(&b, t);
g_variant_type_free(t);
g_variant_builder_add(&b, "s", n->app_name);
g_variant_builder_add(&b, "u", n->replaces_id);
g_variant_builder_add(&b, "s", n->app_icon);
g_variant_builder_add(&b, "s", n->summary);
g_variant_builder_add(&b, "s", n->body);
// Add the actions
t = g_variant_type_new("as");
g_variant_builder_open(&b, t);
g_hash_table_iter_init(&iter, n->actions);
while (g_hash_table_iter_next(&iter, &p_key, &p_value)) {
g_variant_builder_add(&b, "s", (char*)p_key);
g_variant_builder_add(&b, "s", (char*)p_value);
}
// Add an invalid appendix to cover odd numbered action arrays
// Shouldn't interfere with normal testing
g_variant_builder_add(&b, "s", "invalid appendix");
g_variant_builder_close(&b);
g_variant_type_free(t);
// Add the hints
t = g_variant_type_new("a{sv}");
g_variant_builder_open(&b, t);
g_hash_table_iter_init(&iter, n->hints);
while (g_hash_table_iter_next(&iter, &p_key, &p_value)) {
g_variant_builder_add(&b, "{sv}", (char*)p_key, (GVariant*)p_value);
}
g_variant_builder_close(&b);
g_variant_type_free(t);
g_variant_builder_add(&b, "i", n->expire_timeout);
GVariant *reply = dbus_invoke("Notify", g_variant_builder_end(&b));
if (reply) {
g_variant_get(reply, "(u)", id);
g_variant_unref(reply);
return true;
} else {
return false;
}
}
void dbus_notification_set_raw_image(struct dbus_notification *n_dbus, const char *path)
{
GVariant *hint = notification_setup_raw_image(path);
if (!hint)
return;
g_hash_table_insert(n_dbus->hints,
g_strdup("image-data"),
g_variant_ref_sink(hint));
}
/////// TESTS
gint owner_id;
TEST test_dbus_init(void)
{
owner_id = dbus_init();
uint waiting = 0;
while (!dbus_conn && waiting < 2000) {
usleep(500);
waiting++;
}
ASSERTm("After 1s, there is still no dbus connection available.",
dbus_conn);
PASS();
}
TEST test_dbus_teardown(void)
{
dbus_teardown(owner_id);
PASS();
}
TEST test_invalid_notification(void)
{
GVariant *faulty = g_variant_new_boolean(true);
ASSERT(NULL == dbus_message_to_notification(":123", faulty));
ASSERT(NULL == dbus_invoke("Notify", faulty));
g_variant_unref(faulty);
PASS();
}
TEST test_empty_notification(void)
{
struct dbus_notification *n = dbus_notification_new();
gsize len = queues_length_waiting();
guint id;
ASSERT(dbus_notification_fire(n, &id));
ASSERT(id != 0);
ASSERT_EQ(queues_length_waiting(), len+1);
dbus_notification_free(n);
PASS();
}
TEST test_basic_notification(void)
{
struct dbus_notification *n = dbus_notification_new();
gsize len = queues_length_waiting();
n->app_name = "dunstteststack";
n->app_icon = "NONE";
n->summary = "Headline";
n->body = "Text";
g_hash_table_insert(n->actions, g_strdup("actionid"), g_strdup("Print this text"));
g_hash_table_insert(n->hints,
g_strdup("x-dunst-stack-tag"),
g_variant_ref_sink(g_variant_new_string("volume")));
n->replaces_id = 10;
guint id;
ASSERT(dbus_notification_fire(n, &id));
ASSERT(id != 0);
ASSERT_EQ(queues_length_waiting(), len+1);
dbus_notification_free(n);
PASS();
}
TEST test_dbus_notify_colors(void)
{
const char *color_frame = "I allow all string values for frame!";
const char *color_bg = "I allow all string values for background!";
const char *color_fg = "I allow all string values for foreground!";
struct notification *n;
struct dbus_notification *n_dbus;
gsize len = queues_length_waiting();
n_dbus = dbus_notification_new();
n_dbus->app_name = "dunstteststack";
n_dbus->app_icon = "NONE";
n_dbus->summary = "test_dbus_notify_colors";
n_dbus->body = "Summary of it";
g_hash_table_insert(n_dbus->hints,
g_strdup("frcolor"),
g_variant_ref_sink(g_variant_new_string(color_frame)));
g_hash_table_insert(n_dbus->hints,
g_strdup("bgcolor"),
g_variant_ref_sink(g_variant_new_string(color_bg)));
g_hash_table_insert(n_dbus->hints,
g_strdup("fgcolor"),
g_variant_ref_sink(g_variant_new_string(color_fg)));
guint id;
ASSERT(dbus_notification_fire(n_dbus, &id));
ASSERT(id != 0);
ASSERT_EQ(queues_length_waiting(), len+1);
n = queues_debug_find_notification_by_id(id);
ASSERT_STR_EQ(n->colors.frame, color_frame);
ASSERT_STR_EQ(n->colors.fg, color_fg);
ASSERT_STR_EQ(n->colors.bg, color_bg);
dbus_notification_free(n_dbus);
PASS();
}
TEST test_hint_transient(void)
{
static char msg[50];
struct notification *n;
struct dbus_notification *n_dbus;
gsize len = queues_length_waiting();
n_dbus = dbus_notification_new();
n_dbus->app_name = "dunstteststack";
n_dbus->app_icon = "NONE";
n_dbus->summary = "test_hint_transient";
n_dbus->body = "Summary of it";
bool values[] = { true, true, true, false, false, false, false };
GVariant *variants[] = {
g_variant_new_boolean(true),
g_variant_new_int32(1),
g_variant_new_uint32(1),
g_variant_new_boolean(false),
g_variant_new_int32(0),
g_variant_new_uint32(0),
g_variant_new_int32(-1),
};
for (size_t i = 0; i < G_N_ELEMENTS(variants); i++) {
g_hash_table_insert(n_dbus->hints,
g_strdup("transient"),
g_variant_ref_sink(variants[i]));
guint id;
ASSERT(dbus_notification_fire(n_dbus, &id));
ASSERT(id != 0);
ASSERT_EQ(queues_length_waiting(), len+1);
n = queues_debug_find_notification_by_id(id);
snprintf(msg, sizeof(msg), "In round %ld", i);
ASSERT_EQm(msg, values[i], n->transient);
}
dbus_notification_free(n_dbus);
PASS();
}
TEST test_hint_progress(void)
{
static char msg[50];
struct notification *n;
struct dbus_notification *n_dbus;
gsize len = queues_length_waiting();
n_dbus = dbus_notification_new();
n_dbus->app_name = "dunstteststack";
n_dbus->app_icon = "NONE";
n_dbus->summary = "test_hint_progress";
n_dbus->body = "Summary of it";
int values[] = { 99, 12, 123, 123, -1, -1 };
GVariant *variants[] = {
g_variant_new_int32(99),
g_variant_new_uint32(12),
g_variant_new_int32(123), // allow higher than 100
g_variant_new_uint32(123),
g_variant_new_int32(-192),
g_variant_new_uint32(-192),
};
for (size_t i = 0; i < G_N_ELEMENTS(variants); i++) {
g_hash_table_insert(n_dbus->hints,
g_strdup("value"),
g_variant_ref_sink(variants[i]));
guint id;
ASSERT(dbus_notification_fire(n_dbus, &id));
ASSERT(id != 0);
ASSERT_EQ(queues_length_waiting(), len+1);
n = queues_debug_find_notification_by_id(id);
snprintf(msg, sizeof(msg), "In round %ld", i);
ASSERT_EQm(msg, values[i], n->progress);
}
dbus_notification_free(n_dbus);
PASS();
}
TEST test_hint_icons(void)
{
struct notification *n;
struct dbus_notification *n_dbus;
const char *iconname = "NEWICON";
gsize len = queues_length_waiting();
n_dbus = dbus_notification_new();
n_dbus->app_name = "dunstteststack";
n_dbus->app_icon = "NONE";
n_dbus->summary = "test_hint_icons";
n_dbus->body = "Summary of it";
g_hash_table_insert(n_dbus->hints,
g_strdup("image-path"),
g_variant_ref_sink(g_variant_new_string(iconname)));
guint id;
ASSERT(dbus_notification_fire(n_dbus, &id));
ASSERT(id != 0);
ASSERT_EQ(queues_length_waiting(), len+1);
n = queues_debug_find_notification_by_id(id);
ASSERT_STR_EQ(iconname, n->iconname);
dbus_notification_free(n_dbus);
PASS();
}
TEST test_hint_category(void)
{
struct notification *n;
struct dbus_notification *n_dbus;
const char *category = "VOLUME";
gsize len = queues_length_waiting();
n_dbus = dbus_notification_new();
n_dbus->app_name = "dunstteststack";
n_dbus->app_icon = "NONE";
n_dbus->summary = "test_hint_category";
n_dbus->body = "Summary of it";
g_hash_table_insert(n_dbus->hints,
g_strdup("category"),
g_variant_ref_sink(g_variant_new_string(category)));
guint id;
ASSERT(dbus_notification_fire(n_dbus, &id));
ASSERT(id != 0);
ASSERT_EQ(queues_length_waiting(), len+1);
n = queues_debug_find_notification_by_id(id);
ASSERT_STR_EQ(category, n->category);
dbus_notification_free(n_dbus);
PASS();
}
TEST test_hint_desktop_entry(void)
{
struct notification *n;
struct dbus_notification *n_dbus;
const char *desktop_entry = "org.dunst-project.dunst";
gsize len = queues_length_waiting();
n_dbus = dbus_notification_new();
n_dbus->app_name = "dunstteststack";
n_dbus->app_icon = "NONE";
n_dbus->summary = "test_hint_desktopentry";
n_dbus->body = "Summary of my desktop_entry";
g_hash_table_insert(n_dbus->hints,
g_strdup("desktop-entry"),
g_variant_ref_sink(g_variant_new_string(desktop_entry)));
guint id;
ASSERT(dbus_notification_fire(n_dbus, &id));
ASSERT(id != 0);
ASSERT_EQ(queues_length_waiting(), len+1);
n = queues_debug_find_notification_by_id(id);
ASSERT_STR_EQ(desktop_entry, n->desktop_entry);
dbus_notification_free(n_dbus);
PASS();
}
TEST test_hint_urgency(void)
{
static char msg[50];
struct notification *n;
struct dbus_notification *n_dbus;
gsize len = queues_length_waiting();
n_dbus = dbus_notification_new();
n_dbus->app_name = "dunstteststack";
n_dbus->app_icon = "NONE";
n_dbus->summary = "test_hint_urgency";
n_dbus->body = "Summary of it";
enum urgency values[] = { URG_MAX, URG_LOW, URG_NORM, URG_CRIT };
GVariant *variants[] = {
g_variant_new_byte(10),
g_variant_new_byte(0),
g_variant_new_byte(1),
g_variant_new_byte(2),
};
for (size_t i = 0; i < G_N_ELEMENTS(variants); i++) {
g_hash_table_insert(n_dbus->hints,
g_strdup("urgency"),
g_variant_ref_sink(variants[i]));
guint id;
ASSERT(dbus_notification_fire(n_dbus, &id));
ASSERT(id != 0);
ASSERT_EQ(queues_length_waiting(), len+1);
n = queues_debug_find_notification_by_id(id);
snprintf(msg, sizeof(msg), "In round %ld", i);
ASSERT_EQm(msg, values[i], n->urgency);
queues_notification_close_id(id, REASON_UNDEF);
}
dbus_notification_free(n_dbus);
PASS();
}
TEST test_hint_raw_image(void)
{
guint id;
struct notification *n;
struct dbus_notification *n_dbus;
char *path = g_strconcat(base, "/data/icons/valid.png", NULL);
gsize len = queues_length_waiting();
n_dbus = dbus_notification_new();
dbus_notification_set_raw_image(n_dbus, path);
n_dbus->app_name = "dunstteststack";
n_dbus->app_icon = "NONE";
n_dbus->summary = "test_hint_raw_image";
n_dbus->body = "Summary of it";
ASSERT(dbus_notification_fire(n_dbus, &id));
ASSERT(id != 0);
ASSERT_EQ(queues_length_waiting(), len+1);
n = queues_debug_find_notification_by_id(id);
ASSERT(n->icon);
ASSERT(!STR_EQ(n->icon_id, n_dbus->app_icon));
dbus_notification_free(n_dbus);
g_free(path);
PASS();
}
/* We didn't process the timeout parameter via DBus correctly
* and it got limited to an int instead of a long int
* See: Issue #646 (The timeout value in dunst wraps around) */
TEST test_timeout_overflow(void)
{
struct notification *n;
struct dbus_notification *n_dbus;
n_dbus = dbus_notification_new();
n_dbus->app_name = "dunstteststack";
n_dbus->app_icon = "NONE";
n_dbus->summary = "test_hint_urgency";
n_dbus->body = "Summary of it";
n_dbus->expire_timeout = 2147484;
gint64 expected_timeout = G_GINT64_CONSTANT(2147484000);
guint id;
ASSERT(dbus_notification_fire(n_dbus, &id));
ASSERT(id != 0);
n = queues_debug_find_notification_by_id(id);
ASSERT_EQ_FMT(expected_timeout, n->timeout, "%" G_GINT64_FORMAT);
dbus_notification_free(n_dbus);
PASS();
}
TEST test_server_caps(enum markup_mode markup)
{
GVariant *reply;
GVariant *caps = NULL;
const char **capsarray;
settings.markup = markup;
reply = dbus_invoke("GetCapabilities", NULL);
caps = g_variant_get_child_value(reply, 0);
capsarray = g_variant_get_strv(caps, NULL);
ASSERT(capsarray);
ASSERT(g_strv_contains(capsarray, "actions"));
ASSERT(g_strv_contains(capsarray, "body"));
ASSERT(g_strv_contains(capsarray, "body-hyperlinks"));
ASSERT(g_strv_contains(capsarray, "icon-static"));
ASSERT(g_strv_contains(capsarray, "x-dunst-stack-tag"));
if (settings.markup != MARKUP_NO)
ASSERT(g_strv_contains(capsarray, "body-markup"));
else
ASSERT_FALSE(g_strv_contains(capsarray, "body-markup"));
g_free(capsarray);
g_variant_unref(caps);
g_variant_unref(reply);
PASS();
}
TEST test_signal_actioninvoked(void)
{
const struct notification *n;
struct dbus_notification *n_dbus;
struct signal_actioninvoked sig = {0, NULL, -1};
dbus_signal_subscribe_actioninvoked(&sig);
n_dbus = dbus_notification_new();
n_dbus->app_name = "dunstteststack";
n_dbus->app_icon = "NONE2";
n_dbus->summary = "Headline for New";
n_dbus->body = "Text";
g_hash_table_insert(n_dbus->actions, g_strdup("actionkey"), g_strdup("Print this text"));
dbus_notification_fire(n_dbus, &sig.id);
n = queues_debug_find_notification_by_id(sig.id);
signal_action_invoked(n, "actionkey");
uint waiting = 0;
while (!sig.key && waiting < 2000) {
usleep(500);
waiting++;
}
ASSERT_STR_EQ("actionkey", sig.key);
g_free(sig.key);
dbus_notification_free(n_dbus);
dbus_signal_unsubscribe_actioninvoked(&sig);
PASS();
}
TEST test_close_and_signal(void)
{
GVariant *data, *ret;
struct dbus_notification *n;
struct signal_closed sig = {0, REASON_MIN-1, -1};
dbus_signal_subscribe_closed(&sig);
n = dbus_notification_new();
n->app_name = "dunstteststack";
n->app_icon = "NONE2";
n->summary = "Headline for New";
n->body = "Text";
dbus_notification_fire(n, &sig.id);
data = g_variant_new("(u)", sig.id);
ret = dbus_invoke("CloseNotification", data);
ASSERT(ret);
uint waiting = 0;
while (sig.reason == REASON_MIN-1 && waiting < 2000) {
usleep(500);
waiting++;
}
ASSERT(sig.reason != REASON_MIN-1);
dbus_notification_free(n);
dbus_signal_unsubscribe_closed(&sig);
g_variant_unref(ret);
PASS();
}
TEST test_get_fdn_daemon_info(void)
{
unsigned int pid_is;
pid_t pid_should;
char *name, *vendor;
GDBusConnection *conn;
pid_should = getpid();
conn = g_bus_get_sync(G_BUS_TYPE_SESSION, NULL, NULL);
ASSERT(dbus_get_fdn_daemon_info(conn, &pid_is, &name, &vendor));
ASSERT_EQ_FMT(pid_should, pid_is, "%d");
ASSERT_STR_EQ("dunst", name);
ASSERT_STR_EQ("knopwob", vendor);
g_free(name);
g_free(vendor);
g_object_unref(conn);
PASS();
}
TEST assert_methodlists_sorted(void)
{
for (size_t i = 0; i+1 < G_N_ELEMENTS(methods_fdn); i++) {
ASSERT(0 > strcmp(
methods_fdn[i].method_name,
methods_fdn[i+1].method_name));
}
for (size_t i = 0; i+1 < G_N_ELEMENTS(methods_dunst); i++) {
ASSERT(0 > strcmp(
methods_dunst[i].method_name,
methods_dunst[i+1].method_name));
}
PASS();
}
// TESTS END
GMainLoop *loop;
GThread *thread_tests;
gpointer run_threaded_tests(gpointer data)
{
RUN_TEST(test_dbus_init);
RUN_TEST(test_get_fdn_daemon_info);
RUN_TEST(test_empty_notification);
RUN_TEST(test_basic_notification);
RUN_TEST(test_invalid_notification);
RUN_TEST(test_hint_transient);
RUN_TEST(test_hint_progress);
RUN_TEST(test_hint_icons);
RUN_TEST(test_hint_category);
RUN_TEST(test_hint_desktop_entry);
RUN_TEST(test_hint_urgency);
RUN_TEST(test_hint_raw_image);
RUN_TEST(test_dbus_notify_colors);
RUN_TESTp(test_server_caps, MARKUP_FULL);
RUN_TESTp(test_server_caps, MARKUP_STRIP);
RUN_TESTp(test_server_caps, MARKUP_NO);
RUN_TEST(test_close_and_signal);
RUN_TEST(test_signal_actioninvoked);
RUN_TEST(test_timeout_overflow);
RUN_TEST(assert_methodlists_sorted);
RUN_TEST(test_dbus_teardown);
g_main_loop_quit(loop);
return NULL;
}
SUITE(suite_dbus)
{
settings.icon_path = "";
GTestDBus *dbus_bus;
g_test_dbus_unset();
queues_init();
loop = g_main_loop_new(NULL, false);
dbus_bus = g_test_dbus_new(G_TEST_DBUS_NONE);
// workaround bug in glib where stdout output is duplicated
// See https://gitlab.gnome.org/GNOME/glib/-/issues/2322
fflush(stdout);
g_test_dbus_up(dbus_bus);
thread_tests = g_thread_new("testexecutor", run_threaded_tests, loop);
g_main_loop_run(loop);
queues_teardown();
g_test_dbus_down(dbus_bus);
g_object_unref(dbus_bus);
g_thread_unref(thread_tests);
g_main_loop_unref(loop);
settings.icon_path = NULL;
}
/* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */

View File

@ -1,32 +0,0 @@
#define dbus_signal_status_changed(status) signal_sent_stub(status)
#include "../src/dunst.c"
#include "greatest.h"
static bool signal_sent = false;
void signal_sent_stub(struct dunst_status status)
{
signal_sent = true;
return;
}
TEST test_dunst_status(void)
{
status = (struct dunst_status) {false, false, false};
dunst_status(S_FULLSCREEN, true);
ASSERT(status.fullscreen);
dunst_status(S_IDLE, true);
ASSERT(status.idle);
dunst_status(S_RUNNING, true);
ASSERT(status.running);
PASS();
}
SUITE(suite_dunst)
{
RUN_TEST(test_dunst_status);
}
/* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */

View File

@ -1,7 +1,7 @@
[global]
font = Monospace 8
allow_markup = no
format = "%s\n%b"
format = "<b>%s</b>\n<i>%b</i>"
sort = yes
indicate_hidden = yes
alignment = left

Some files were not shown because too many files have changed in this diff Show More