Compare commits

...

285 Commits

Author SHA1 Message Date
fwSmit
6ee460335d
Merge pull request #883 from FuJa0815/fixed_doxygen
fixed doxygen generation
2021-06-29 17:31:19 +02:00
Jan Führer
988b8d2747
fixed doxygen generation 2021-06-28 10:22:33 +02:00
Nikos Tsipinakis
9f4f110c53
Merge pull request #862 from fwSmit/dbus-highlight
Add dbus hightlight hint
2021-05-29 22:20:32 +02:00
fwsmit
e898fa589a docs: Document all dbus hints 2021-05-28 12:59:23 +02:00
fwsmit
702abc7a03 dbus: Add a hint for changing the highlight color 2021-05-28 12:59:23 +02:00
Nikos Tsipinakis
b77f76f02e
Merge pull request #854 from fwSmit/wayland-hidpi
wayland hidpi support
2021-05-26 20:31:50 +02:00
Nikos Tsipinakis
cf091bea37
Merge pull request #840 from fwSmit/master
Small fixes
2021-05-26 20:09:28 +02:00
Nikos Tsipinakis
1040febfb9
Merge pull request #847 from fwSmit/log-testing
logging: Set loglevel to error when testing.
2021-05-26 20:06:13 +02:00
Nikos Tsipinakis
652cb7efb4
Merge pull request #848 from lukasrad02/feature-mouse-action-context
Add mouse action to show context menu
2021-05-26 20:04:39 +02:00
Nikos Tsipinakis
7d91f978eb
Merge pull request #849 from fwSmit/wayland-surface
wayland: fix NULL pointer dereference
2021-05-26 19:52:39 +02:00
Nikos Tsipinakis
0994c57b3e
Merge pull request #856 from mgsloan/patch-1
Fix link to "Building" section in readme
2021-05-26 19:51:58 +02:00
fwSmit
cb6f2ca3b3
Merge pull request #867 from hastinbe/icon-static-capability
Add icon-static server capability
2021-05-16 11:19:19 +02:00
Beau Hastings
bd6abfcc5d
Add icon-static server capability
Dunst supports displaying the first frame of an icon, thus it should
announce that capability.
2021-05-14 16:54:51 +08:00
fwsmit
1d3822ee8b icon: Replace deprecated g_memdup with g_memdup2 where possible
This avoids a potential conversion from gsize to guint and silences a
warning.
2021-05-02 18:13:46 +02:00
fwsmit
ea531ca9ae wayland: Return the largest scale when output cannot be found
When follow mode != none dunst cannot detect what output is being used
to display the notification (yet). With this commit dunst falls back to
using the largest scale from any ouput. The compositor should scale the
buffer down again if the scale of the output is smaller that the used
scale.
2021-05-02 13:22:07 +02:00
fwsmit
e69adcefea wayland: Add better detection for the current output
When there's only one output, just return that output.
2021-05-02 13:21:48 +02:00
fwSmit
f2017eb3d6
Merge pull request #858 from bebehei/werror-on-ci
Add CFLAGS with -Werror for CI
2021-05-02 11:44:00 +02:00
Benedikt Heine
d9282b7f86 Add EXTRA_CFLAGS with -Werror for CI 2021-05-01 14:18:35 +02:00
Lukas Radermacher
efac6e70b0 Update docs
Document new mouse action open_url and rule action_name in dunstrc and docs/dunst.5.pod.
2021-04-28 21:43:21 +02:00
Lukas Radermacher
0dfc4ed738 Implement open_url and action_name 2021-04-28 21:43:20 +02:00
Lukas Radermacher
b49925c86d Add "open_url" mouse action 2021-04-28 21:43:20 +02:00
Lukas Radermacher
8e80871c50 Add rule action_name 2021-04-28 21:43:20 +02:00
Lukas Radermacher
d5ee1febca Add notification property default_action_name 2021-04-28 21:43:20 +02:00
Lukas Radermacher
941c527af9 Update docs
Document new mouse actions in dunstrc and docs/dunst.5.pod.
2021-04-28 21:43:20 +02:00
Lukas Radermacher
b66f8f362d Change input.c to handle new actions 2021-04-28 21:43:11 +02:00
Michael Sloan
85ff05062c
Fix link to "Building" section in readme 2021-04-24 16:56:35 -06:00
fwsmit
db5e6ce8f4 wayland: Add HiDPI support
This commit implements support for HiDPI rendering for wayland. X11
should be unaffected by this.
It is implemented by scaling everything that's rendered by a scale
factor that's obtained from the wayland protocol. All sizes before
rendering remain the same, so the same settings should provide the same
output for different scaling factors, only scaled by that factor.
2021-04-22 13:09:35 +02:00
fwsmit
aad4dbaf3d wayland: Some minor cleanup 2021-04-22 13:05:31 +02:00
fwsmit
98a61f0896 wayland: implement get_scale function for hidpi support 2021-04-22 13:04:49 +02:00
fwsmit
7c6620c92d wayland: Remove accidental if statement 2021-04-20 21:55:37 +02:00
fwsmit
75af42c83a doc: internal: Add note about the way tests are being compiled 2021-04-15 18:35:37 +02:00
fwsmit
c4e428e9d5 build: Clean up docs/dunst.5 on make clean 2021-04-09 16:25:06 +02:00
fwsmit
5c0d7ea662 docs: Update dependencies in README
The Gtk3 dependencie was dropped #376, so we better remove it from the
list
2021-04-08 17:44:13 +02:00
fwsmit
176aad4f3c input: Wake on action close_all
This makes sure the window also gets updated after closing all
notifications. Otherwise this actions seems to do nothing until dunst
wakes up from something else.
2021-04-06 19:02:10 +02:00
fwsmit
af49b76586 wayland: fix NULL pointer dereference 2021-04-06 17:18:48 +02:00
Lukas Radermacher
6c61f3e5e2 Add notification-specific context menu 2021-04-05 16:06:15 +02:00
Lukas Radermacher
bec5e9d25a Add "context_all" mouse action 2021-04-05 16:00:08 +02:00
Lukas Radermacher
790342b913 Add "context" mouse action 2021-04-05 14:14:52 +02:00
fwsmit
4234813417 logging: Set loglevel to error when testing.
It's useful to see what crashed your program when testing. Unfortunately
there's no way to distinguish the DIE way of logging from critical
logging.
This commit also fixes a bug where loglevel error wouldn't function,
because the message level also contains other flags.
2021-04-03 19:52:25 +02:00
fwsmit
d65f69b4db Work around glib bug for better test output 2021-03-16 16:09:09 +01:00
fwsmit
b75d35adb4 queues: correctly sort notifications even when sort is false.
The sort name is a bit misleading. It should actually be called
sort_urgency or something.
Notifications are still not sorted based on time, but ID.
2021-03-16 16:09:00 +01:00
Nikos Tsipinakis
3acffdb194
Merge pull request #814 from fwSmit/wayland-improvements
Wayland improvements
2021-03-08 09:19:26 +02:00
Nikos Tsipinakis
7e22272ebb
Merge pull request #837 from fwSmit/dunst-readme
Dunst readme
2021-03-08 09:16:36 +02:00
fwsmit
7292bfcd89 docs: add section for autostarting dunst 2021-02-27 23:14:34 +01:00
fwsmit
8413ade9d7 readme: add screenshots and improve presentation 2021-02-27 23:14:34 +01:00
Nikos Tsipinakis
54b665898f
Merge pull request #834 from fwSmit/fallback-x11
Wayland: fallback to X11 output when initialization fails
2021-02-27 14:53:55 +02:00
fwsmit
f8a2ff48b3 output: remove unused win_visible function
This was used nowhere, and not even implemented for the wayland output.
2021-02-24 13:32:06 +01:00
fwsmit
a8b2058fcf Wayland: fallback to X11 output when initialization fails 2021-02-24 13:32:06 +01:00
Nikos Tsipinakis
ceca7cfcc7 Add release checklist 2021-02-21 14:37:57 +02:00
Nikos Tsipinakis
598ec5797e Start new release cycle 2021-02-21 14:28:37 +02:00
Nikos Tsipinakis
f615866248 Dunst v1.6.1: Fix incorrect makefile version 2021-02-21 14:22:51 +02:00
Nikos Tsipinakis
5214a61e9f Start new release cycle 2021-02-21 14:13:22 +02:00
Nikos Tsipinakis
0cec75e45e Dunst v1.6.0 2021-02-21 14:02:17 +02:00
Nikos Tsipinakis
ace514ffc7 Redraw buffer after showing window
For some reason if unpausing via dunstctl the window is invisible until
the next window update. Redrawing the buffer after showing fixes it.

Fixes #827
2021-02-21 12:53:32 +02:00
Nikos Tsipinakis
f976c2d44c Fix warning about format string misuse 2021-02-21 12:17:29 +02:00
Nikos Tsipinakis
399489567c
Merge pull request #830 from tsipinakis/drop-commands
Drop support for DUNST_COMMAND_*
2021-02-20 16:11:07 +02:00
Nikos Tsipinakis
a94767e492 Drop support for DUNST_COMMAND_*
These commands may be used for a DoS attack. Specifically, an
application that's running from a sandbox (e.g.  websites with
notification permission) can toggle the permissions.

This is a low severity issue but since dunstctl is available which
performs the exact same task there is no reason to leave this in.

Credit to Vít Šesták for reporting this.
2021-02-19 22:31:23 +02:00
Nikos Tsipinakis
2ba788d9c6
Merge pull request #819 from fwSmit/master
Fix background and highlight rules not working
2021-02-17 13:14:10 +02:00
Nikos Tsipinakis
6e972d30ca
Merge pull request #825 from fwSmit/makefile-wayland
Improve support for compiling without wayland
2021-02-17 13:13:24 +02:00
fwsmit
1f4278a5e2 Improve support for compiling without wayland
Fixes indent in makefile and improves README wording.
2021-02-13 20:23:26 +01:00
fwsmit
ec044eced0 Fix background and highlight rules not working
Classic copy paste error, this is why we need settings V2 (and tests for
the rules code, coming soon™)
2021-02-10 19:14:15 +01:00
fwsmit
0f588998fe Wayland: fix "follow=none" not working. 2021-02-06 00:42:26 +01:00
fwsmit
500b00b344 Wayland: fix lots of messages when idle_threshold=0 2021-02-06 00:42:15 +01:00
fwsmit
36186d37ea Wayland: Implement foreign toplevel manager protocol
Used for detecting if there is a fullscreen application open.
2021-02-06 00:42:04 +01:00
fwsmit
d7f93a3a69 Wayland: added basic touch support
Copy pasted from mako. I could not test.
2021-02-02 23:33:26 +01:00
Nikos Tsipinakis
814e620247
Merge pull request #802 from fwSmit/environment-variables
Set environment variables for scripts
2021-01-30 18:06:36 +02:00
fwsmit
bc3de38aa7 Slightly simplify and document script executing 2021-01-29 10:43:10 +01:00
fwsmit
52134ab8ce Add documentation for environment variables passed to scripts 2021-01-29 10:43:10 +01:00
fwsmit
0d5cbfcfce Set environment variables for scripts 2021-01-29 10:43:10 +01:00
fwsmit
293d71264e Add tests for icon path
I also fixed a test that was broken because of different behaviour.
Now the first icon path is returned instead of the first valid icon
path.
2021-01-29 10:43:10 +01:00
fwsmit
59f994ee94 Extract icon path function from get_pixbuf_from_icon 2021-01-29 10:43:10 +01:00
Nikos Tsipinakis
e90f605a52
Merge pull request #799 from fwSmit/doc-update
Split man pages
2021-01-27 22:44:50 +02:00
fwsmit
63103f991d Update a few man page descriptions 2021-01-27 11:14:30 +01:00
fwsmit
d0dfaf0488 Improve docs 2021-01-27 11:14:30 +01:00
fwSmit
22d108f53e
Merge pull request #810 from GuessWhatBBQ/master
Adding a config variable that adds extra space between the notification icon and text
2021-01-27 11:12:13 +01:00
Nikos Tsipinakis
5d1b58559c
Merge pull request #798 from fwSmit/move-config
Change config location to /etc/dunst/dunstrc
2021-01-27 00:40:58 +02:00
Nikos Tsipinakis
c6638c30d5
Merge pull request #813 from fwSmit/colorize-tests
Colorize test output.
2021-01-27 00:11:23 +02:00
fwsmit
92bc6705ca Colorize test output.
This adds a dependency on awk for testing.

Closes #812
2021-01-26 21:31:52 +01:00
GuessWhatBBQ
be8e32098f Add the text_icon_padding feature as per #543 2021-01-26 12:18:04 +06:00
fwsmit
9615431593 Change config location to /etc/xdg/dunst/dunstrc
Also fixed the test-install script
2021-01-25 18:55:52 +01:00
Nikos Tsipinakis
7e9650ad95
Merge pull request #809 from mcz/screenfix
Fix process of gettign the active monitor
2021-01-24 12:20:14 +02:00
Mikau
ebcd20d8d0 Fix process of gettign the active monitor
- Get the window with keyboard focus using XGetInputFocus() instead of
  _NET_ACTIVE_WINDOW
- Handle edge cases of keyboard/mouse focus being on different X screen
- Set sane default in case the active monitor can't be determined
  (fixing issue #762)
2021-01-23 22:21:48 +01:00
Nikos Tsipinakis
77bfbc4f7f
Merge pull request #781 from fwSmit/wayland-2
Add wayland support
2021-01-18 20:28:16 +02:00
fwsmit
3822f09185 Minor fixes 2021-01-18 17:50:57 +01:00
fwsmit
3ba614b59b Move reason_string debug log to dbus.c 2021-01-09 21:54:56 +01:00
fwsmit
6a6cd6697f Remove unnecesary code. Improve log messages 2021-01-09 21:54:56 +01:00
fwsmit
bb12727bc0 Make compiling for wayland optional
This can be changed in config.mk or by using the command
        make WAYLAND=0

Also removed using_xwayland function definition as it isn't defined
anymore
2021-01-09 21:54:23 +01:00
fwsmit
5a20d463b5 Implement xwayland idle fix differently 2021-01-09 21:49:06 +01:00
fwsmit
af10f6f8cd Fix indent 2021-01-09 21:49:06 +01:00
fwsmit
3441f4c68d Change default layer to overlay, add note about pushback 2021-01-09 21:49:06 +01:00
fwsmit
756f822a6b Fix notifications not disapearing on xwayland
Because idle detection on xwayland is not possible, just assume
that the user is not idle. This also somehow fixes notifications
not disapearing when clicking on them.
2021-01-09 21:49:06 +01:00
fwsmit
ae38efedbb Update dunstrc with Wayland settings 2021-01-09 21:49:06 +01:00
fwsmit
7b26b4bd94 Update input.c 2021-01-09 21:49:06 +01:00
fwsmit
20eebb80e1 Simplify idle handling 2021-01-09 21:49:06 +01:00
fwsmit
c40e797e6b improve error messages 2021-01-09 21:49:06 +01:00
fwsmit
b094074ff2 Free idle handler and remove unnecesary variables 2021-01-09 21:49:06 +01:00
fwsmit
25e1d0c442 Added option for forcing X11 on wayland.
Also added some more documentation.
2021-01-09 21:48:59 +01:00
fwsmit
836d044df8 Implement all options for output follow mode.
As far as I know, it's not possible to differentiate keyboard and
mouse focus, so they behave the same. This commit also fixes
a bug where a NULL pointer was dereferenced.
2020-12-28 15:17:12 +01:00
fwsmit
ac6093a577 Added setting for changing layer. 2020-12-28 15:17:05 +01:00
fwsmit
deabe2bacf Wayland: fix bug where notification doesn't move after changing width. 2020-12-28 15:16:23 +01:00
fwsmit
859c18a2e9 Wayland: Add support for 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.

Also, rename wl_output to something that doesn't exist yet.
2020-12-28 15:15:59 +01:00
fwsmit
74c5d97181 Fix Makefile 2020-12-28 15:15:59 +01:00
fwsmit
e5fec3326e Wayland: optimize by making input code asyncronous 2020-12-28 15:15:59 +01:00
fwsmit
705e575d6d Small fixes 2020-12-28 15:15:59 +01:00
fwsmit
32119a1df7 Wayland: handle mouse input 2020-12-28 15:15:59 +01:00
fwsmit
ed560eab4e Fixed Makefile for building wayland 2020-12-28 15:15:59 +01:00
fwsmit
c1caf4bbc7 Set the right window position 2020-12-28 15:15:59 +01:00
fwsmit
72ceedd4db Fix notification not disappearing 2020-12-28 15:15:59 +01:00
fwsmit
4a9c7693f4 update wayland branch to a new master 2020-12-28 15:15:59 +01:00
Nikos Tsipinakis
167d7dd7c3
Merge pull request #793 from ritze/get-number-of-notifications
Get the number of notifications
2020-12-28 13:49:32 +02:00
Moritz Luedecke
1827f0d974
Suffix "Length" to the dbus variables for the number of notifications 2020-12-25 15:03:50 +01:00
Nikos Tsipinakis
0e6997b6fc Flush stdout when using -print
This ensures writes happen on every new notification when output is
redirected to a file.

Closes #794
2020-12-24 15:39:44 +02:00
Moritz Luedecke
4d74c7b46e
Rename the dunstctl sub-command 'status' to 'count' 2020-12-23 16:30:41 +01:00
Moritz Luedecke
5b81b43aa6
Add documentation for 'dunstctl status' 2020-12-21 12:02:20 +01:00
Moritz Luedecke
137361a95d
Show the number of notifications via dunstctl 2020-12-21 11:46:57 +01:00
Moritz Luedecke
313731f0f2
Add dbus properties for number of displayed, waiting and shown notifications 2020-12-21 11:45:54 +01:00
Nikos Tsipinakis
a89287cb3e Check for marked_for_closure before checking for timeout 2020-12-17 19:49:26 +02:00
Nikos Tsipinakis
8f9e85f122 Fix incorrect handling of 'do_action, close' mouse action (again!) 2020-12-17 19:49:26 +02:00
Nikos Tsipinakis
3d9717a8a3
Merge pull request #791 from fwSmit/master
Contrib: add progress notify script
2020-12-13 21:04:42 +02:00
fwsmit
c73009160e Contrib: add progress notify script
For audio and brightness notifications
2020-12-13 14:44:12 +01:00
Nikos Tsipinakis
732227eff5 Fix incorrect handling of 'do_action, close' mouse action
Fixes #778
2020-12-01 13:41:13 +02:00
Nikos Tsipinakis
f866f8e4e7
Merge pull request #782 from dunst-project/ci
Add new CI images
2020-11-27 11:55:11 +02:00
Benedikt Heine
8df6bc9554 Add new CI images 2020-11-25 17:29:13 +01:00
Nikos Tsipinakis
c37a923def Fix XRM dpi loading
Bug introduced in 0bcc135ffd9a5ad0331f8608f4ed1c4223e0b4e9, XRM database
wasn't properly initialized when the screen dpi is assigned.
2020-11-18 21:57:58 +02:00
Nikos Tsipinakis
0bcc135ffd
Merge pull request #779 from fwSmit/transition-output
Create abstract output and fix wayland bug
2020-11-18 17:44:18 +02:00
fwsmit
58d215ddfe Create abstract output and fix wayland bug
This commit adds an output struct which abstracts the X11 specific
functions and makes it possible to easily create a drop-in wayland
output.

It also fixes a bug in wayland where notifications won't disappear.
This is because wayland doesn't give access to user input when a
client is not in focus. This way it seems like the user is always
idle. The idle functionality is now disabled in Wayland until proper
support is added.
2020-11-18 15:20:06 +01:00
Nikos Tsipinakis
756e6662e1
Merge pull request #780 from fwSmit/test-fix
Fix some tests
2020-11-17 20:30:31 +02:00
fwsmit
b758e4925b Fix some tests 2020-11-17 15:04:41 +01:00
Nikos Tsipinakis
3d3ba4b9c5
Merge pull request #775 from fwSmit/progress-bar
Add progress bar
2020-11-10 11:55:02 +02:00
fwsmit
4f10496123 Add docs, tests and fixes for progress bar 2020-11-10 10:52:26 +01:00
Nikos Tsipinakis
8e3eade7ba
Merge pull request #776 from fwSmit/icon-fix
Turn icons on by default
2020-11-08 17:15:41 +02:00
Nikos Tsipinakis
62ce907ecd
Merge pull request #771 from iFreilicht/patch-1
Remove deprecated shortcuts section
2020-11-07 20:54:43 +02:00
fwsmit
bfcf655b1e Add feature: progress bar 2020-11-06 14:25:04 +01:00
fwsmit
5caac20268 Turn icons on by default 2020-11-06 13:16:57 +01:00
Nikos Tsipinakis
3001f79fc3
Merge pull request #774 from fwSmit/vim-modeline
Add filetype to vim modeline
2020-11-03 17:41:49 +02:00
fwsmit
55d700f746 Add filetype to vim modeline 2020-11-02 20:25:26 +01:00
Felix Uhl
9009033d59
Explain transition to dunstctl in comments 2020-11-01 14:19:30 +01:00
Felix Uhl
8440ac3d23
Remove deprecated shortcuts section 2020-10-29 16:12:21 +01:00
Nikos Tsipinakis
93b52df269
Merge pull request #769 from gregdan3/master
Do not accept empty appname argument
2020-10-27 13:04:43 +02:00
Gregory Danielson
72907cc744
Do not accept empty appname argument
Closes #768
2020-10-26 09:40:38 -05:00
Nikos Tsipinakis
0467e46474 Fix typo 2020-10-26 12:51:47 +02:00
Nikos Tsipinakis
bc6ab69390
Merge pull request #766 from ammgws/signal
Emit signal when `paused` property changes
2020-10-07 09:22:56 +03:00
Jason Nader
8831260be3
Emit signal when paused property changes 2020-10-03 23:42:34 +09:00
Nikos Tsipinakis
f00625692b
Merge pull request #758 from keer4n/master
Fix a typo in configuration option in dunst.pod
2020-09-25 21:45:25 +03:00
Kiran Gurung
6734fa3135 Fix a typo in setting option in dunst.pod 2020-09-04 23:12:20 +00:00
Nikos Tsipinakis
e59d203256 Fix monitor setting overriding follow_mode
Ignore the monitor setting if it's set to a non-zero value and the
follow mode isn't none.

Fixes #755
2020-08-20 13:50:56 +03:00
Nikos Tsipinakis
ca6ce6868e
Merge pull request #750 from MaximeWack/master
Fix possible division by zero in radius calculation.
2020-08-15 18:47:24 +03:00
Maxime Wack
6f5f003e2b Fix possible division by zero in radius calculation.
Check all possible causes of division by zero in the
calculation of the radius.
2020-08-15 14:22:23 +02:00
Nikos Tsipinakis
37feb7d6a3 Fix history_ignore rule being reset
Set the default history_ignore value to -1 for unset, otherwise it'll be
reset to false when applying additional rules.

Fixes #747
2020-08-08 19:11:13 +03:00
Nikos Tsipinakis
28c5074fab Trigger workflows on all branches 2020-07-23 13:54:38 +03:00
Nikos Tsipinakis
ca1bf262ce Fix failing CI due to version mismatch 2020-07-23 13:53:05 +03:00
Nikos Tsipinakis
8c07ffb173 Start new release cycle 2020-07-23 13:41:24 +03:00
Nikos Tsipinakis
52d67616f1 Dunst v1.5.0 2020-07-23 13:27:50 +03:00
Nikos Tsipinakis
3a77b879e0
Merge pull request #742 from phaazon/fix/typo-pod
Fix a typo in the dunst.pod file.
2020-07-23 13:16:36 +03:00
Dimitri Sabadie
bc86ce78a7
Fix a typo in the dunst.pod file. 2020-07-23 12:08:36 +02:00
Nikos Tsipinakis
c01f81b692 Fix possible division by 0 in radius calculation 2020-07-18 22:39:40 +03:00
Nikos Tsipinakis
9db8b76473
Merge pull request #735 from Djeeberjr/master
multiple scripts for notification
2020-07-18 22:12:26 +03:00
Djeeberjr
1b8f56eb45 changed script_run back to bool 2020-07-18 15:51:04 +02:00
Nikos Tsipinakis
eb253a4547
Merge pull request #732 from matclab/731-ignore-dbus-close-setting
Add ignore_dbusclose settings
2020-07-17 21:32:41 +03:00
matclab
842a5242f7 Remove warning message
This message has no real added value and is emitted every time a
notification is closed when `ignore_dbuclose` is set.
2020-07-16 18:28:05 +02:00
matclab
7bd55a53bf ignore_dbusclose: Add debug message when ignoring 2020-07-16 18:28:05 +02:00
matclab
7796f7f848 ignore_dbusclose: Add dunstrc example and man page section 2020-07-16 18:28:05 +02:00
matclab
0a08cefc68 ignore_dbusclose: send NotificationClosed signal back for spec compliance
The notification spec has no concept of disallowing closing notifications,
so to make the ignore_dbusclose 'compliant' we choose to lie to the clients
that the notification is actually closed, but keep it open.
To do this, we send a NotificationClosed signal back (via signal_notification_closed
which also call notification_invalidate_action)
2020-07-16 18:28:05 +02:00
matclab
762d37758d Add ignore_dbusclose settings
closes #731
2020-07-12 22:44:26 +02:00
Nikos Tsipinakis
b040fca067
Merge pull request #736 from matclab/734-BadAtom-composited-test
No compositor if no _NET_WM_CM_Si atom
2020-07-12 21:12:00 +02:00
matclab
3c33f4559f No compositor if no _NET_WM_CM_Si atom
close #734
2020-07-12 14:44:47 +02:00
Djeeberjr
4d66a60a4f multiple scripts for notification 2020-07-12 00:50:52 +02:00
Nikos Tsipinakis
0de8610b67 Fix crash when triggering actions via dunstctl
g_list_nth_data was used to query the notification list, but in the code
it was erroneously assumed that a GList object was returned rather than
a notification.

One of the many pitfalls of generic pointers...

Fixes #727
2020-07-02 19:46:36 +02:00
Nikos Tsipinakis
8afb7fcd1a Clear window before redrawing (again!)
The timeline of this issue is as follows:
In 7a094bc a call to XClearWindow/XFlush was added as part of RGBA
support.
Afterwards, I noticed that there is some noticeable flicker while
redrawing for large notifications, so I foolishly removed both of these
calls without considering the full effects.

As a result when redrawing notifications 'stack up' with each-other.
After a testing it seems that the XFlush call that was causing
the flicker is not necessary and XClearWindow works without it.

Fixes #728
2020-07-02 19:27:44 +02:00
Nikos Tsipinakis
1ca271084a Recognize svgz icon types 2020-06-11 18:21:58 +02:00
Nikos Tsipinakis
00851dd4d3 Mention transparency support in man page 2020-06-09 12:15:06 +02:00
Nikos Tsipinakis
ed3db5048c Revert "Clear window to prevent accumulations"
This reverts commit 7a094bc702149104cd4049281a7dd9ed0ab76c89.

This commit caused a flicker effect every time the window was redrawn,
reverting it for now.
2020-06-09 12:13:29 +02:00
Nikos Tsipinakis
7735c9a4f6 Fix separator not working with frame_width=0
Bug was introduced with 16e38a9a84c46b0cd4fd7b0f2f88e9d8054ccc0b.
2020-05-28 13:00:20 +02:00
Nikos Tsipinakis
d885e93b73
Merge pull request #717 from nick87720z/rgba-support
Rgba support
2020-05-28 12:54:47 +02:00
Nikita Zlobin
12bea04941 optimization 2020-05-26 22:59:01 +05:00
Nikita Zlobin
6413a4d6f8 Revert "Support arbitrary hex color formats (16/32bits per component)"
This reverts commit 1e36a6b4ac569527981e06db429f3e6cd8ac99b8.
2020-05-26 22:28:05 +05:00
Nikita Zlobin
d110ba93e6 Revert "Floating point color config formats"
This reverts commit 6b28d57730a390264b4d0daea2d765f841ad90d8.
2020-05-26 22:27:31 +05:00
Nikita Zlobin
98debc663a simplify UINT_MAX_N
Probably I was too precatious, not relying to common rule about
unsigned subtraction to minus.
2020-05-26 18:35:37 +05:00
Nikita Zlobin
d45888a785 Don't mix separator with underlying content 2020-05-23 14:14:50 +05:00
Nikita Zlobin
6b28d57730 Floating point color config formats 2020-05-23 14:14:50 +05:00
Nikita Zlobin
1e36a6b4ac Support arbitrary hex color formats (16/32bits per component)
It's unlikely that string_to_color() is faster, but at least is more
flexible. Not sure if 8-digit per channel format is used.
2020-05-23 14:14:50 +05:00
Nikita Zlobin
7226ef2c15 Support for different color config precisions (#RGB format)
Support for #RGB is claimed in docs.
2020-05-23 14:14:50 +05:00
Nikita Zlobin
2e2b3e549f RGBA color support 2020-05-23 14:14:50 +05:00
Nikita Zlobin
9833fbba1f Limit frame stroke area 2020-05-23 14:14:50 +05:00
Nikita Zlobin
8d8e6882e8 style fix 2020-05-23 14:14:50 +05:00
Nikos Tsipinakis
813417915c
Merge pull request #713 from nick87720z/fix-ugly-frame
Fix ugly frame
2020-05-22 19:08:04 +02:00
Nikita Zlobin
2de1603cd2 style fix 2020-05-20 21:19:04 +05:00
Nikita Zlobin
10c82ed7fe Rename x_win_round_corners() to x_win_corners_shape()
It's all job is to feel proper X11 shape.
2020-05-20 21:16:31 +05:00
Nikita Zlobin
73b7176e0b Reuse draw_rounded_rect() for xshape drawing 2020-05-20 21:15:01 +05:00
Nikita Zlobin
85ea8b8aa8 Separate some functions 2020-05-20 21:10:45 +05:00
Nikos Tsipinakis
791f514078
Merge pull request #715 from bergercookie/master
Change default for icon_position in dunstrc
2020-05-12 19:12:36 +02:00
Nikos Koukis
2bf4462154 Change default for icon_position in dunstrc 2020-05-10 11:45:18 +01:00
Nikita Zlobin
6b6b63da17 code style fix 2020-05-09 02:10:15 +05:00
Nikita Zlobin
296cea499c Compositor detection 2020-05-08 06:23:10 +05:00
Nikita Zlobin
0e35c6acb0 Use cairo to form xshape mask 2020-05-07 02:47:11 +05:00
Nikita Zlobin
5fd090e618 Frame internal radius 2020-05-06 22:27:39 +05:00
Nikos Tsipinakis
a9d32c4d78 Ignore compiled manpages in gitignore 2020-05-05 11:38:48 +02:00
Nikos Tsipinakis
289b4f50eb Remove const qualifier in queues_get_displayed
The returned node was theoretically read-only however all the other
nodes accessible do not inherit the qualifier. Since none of the glib
functions use const with Glib, even the read-only ones. There's no use
in having it.

This resolves the warning about g_list_nth_data.
2020-05-05 11:38:48 +02:00
Nikita Zlobin
dcf060effa Fix xshape mask rounding 2020-05-05 11:55:50 +05:00
Nikita Zlobin
d964455d36 oops: Keep ready for possible 32bit color lack 2020-05-05 00:56:19 +05:00
Nikita Zlobin
d3f6c05590 True transparency support 2020-05-04 19:45:02 +05:00
Nikita Zlobin
7a094bc702 Clear window to prevent accumulations 2020-05-04 19:06:53 +05:00
Nikita Zlobin
c09f0f029f style fix 2020-05-04 02:42:18 +05:00
Nikita Zlobin
dbbaecfd1d Fix indentation 2020-05-04 02:35:34 +05:00
Nikita Zlobin
16e38a9a84 Don't draw frame with zero line width 2020-05-03 22:03:47 +05:00
Nikita Zlobin
4c4dc9aa95 Fix ugly rounded corners 2020-05-03 22:03:47 +05:00
Nikos Tsipinakis
fb2ffd425e
Merge pull request #711 from mrossinek/dunstctl-toggle-pause
Allow toggling pause state via dunstctl
2020-05-02 10:05:48 +02:00
mrossinek
cfa8625699
Fix boolean negation 2020-05-02 10:01:51 +02:00
mrossinek
f868f51fcd
Fix embedded XML 2020-05-02 09:19:24 +02:00
mrossinek
0dfcc1420d
Change running to paused state on "server" side
This allows the boolean "inversion" to happen in the C code instead of
post-processing in the dunstctl shell script.
2020-05-01 21:54:53 +02:00
mrossinek
2e301b1d39
Fix indentation and use tabs for indents 2020-05-01 21:35:08 +02:00
mrossinek
b8aa8c15e8
Rename set-running to set-paused
Since the pause status is more intuitive for the end-user than the
running state (which is used internally) the interface of the dunstctl
is renamed to set-paused and is-paused (previously set-running and
running).
This requires inversion of the boolean variables to be consistent with
the inverted naming.

Fixes #710
2020-05-01 19:37:43 +02:00
mrossinek
c45a9eac73
Add toggle option to dunstctl set-running command
Fixes #709
2020-05-01 19:37:20 +02:00
Nikos Tsipinakis
337ff1edb5
Merge pull request #651 from bebehei/dunstctl
Implement a command line control (dunstctl)
2020-05-01 15:38:23 +02:00
Nikos Tsipinakis
50a55f2ff1 Mention dunstctl in dunst man page 2020-05-01 15:35:23 +02:00
Nikos Tsipinakis
6ba430a818 Add dunstctl man page 2020-05-01 15:35:23 +02:00
Nikos Tsipinakis
5aa1863762 Rename status commands to running 2020-05-01 15:35:23 +02:00
Nikos Tsipinakis
94aa427a1b Fix dunstctl help alignment 2020-05-01 15:35:23 +02:00
Nikos Tsipinakis
23fa204e69 Use set -e 2020-05-01 15:35:23 +02:00
Nikos Tsipinakis
a91adf9c80 Implement error handling in NotificationAction call 2020-05-01 15:35:23 +02:00
Nikos Tsipinakis
36eed785d2 dunstctl: Fix alignment in help message 2020-05-01 15:35:23 +02:00
Nikos Tsipinakis
ec421f1ea1 Properly handle error conditions from dunstctl 2020-05-01 15:35:23 +02:00
Nikos Tsipinakis
9049bf15b2 Remove live status checking (for now) 2020-05-01 15:35:23 +02:00
Nikos Tsipinakis
ee11dfa8fe Don't wake up after doing an action 2020-05-01 15:35:23 +02:00
Nikos Tsipinakis
936dbb039c dunstctl: Print help message on dbus communication failure 2020-05-01 15:35:23 +02:00
Nikos Tsipinakis
b1f64b266b Wake up after changing status via dunstctl 2020-05-01 15:35:23 +02:00
Nikos Tsipinakis
e14160bda6 dunstctl: Apply shellcheck suggestions 2020-05-01 15:35:23 +02:00
Nikos Tsipinakis
6a8401d85a dunstctl: Use = rather than == for string equality
`==` is not supported in plain sh
2020-05-01 15:35:23 +02:00
Nikos Tsipinakis
d45d26e257 dunstctl: Don't use function keyword
Plain `foo()` is more portable, `function` doesn't appear to be
supported in sh.
2020-05-01 15:35:23 +02:00
Benedikt Heine
7e92619967 Add ability to trigger action of latest notification
Currently triggers only the latest notification. TODO: implement it
either with ActionsAll or more favorable: with an optional paramter,
which should trigger only the action of a single notification.

The problem currently: I have got no ability to check the DBus docs, as
I'm on a bad internet connection.
2020-05-01 15:35:23 +02:00
Nikos Tsipinakis
6b60c52ee1
Merge pull request #708 from cdown/mouse_fallback
follow=keyboard: Fall back to follow=mouse instead of XDefaultScreen()
2020-04-27 17:40:49 +02:00
Chris Down
e8fc45da63 follow=keyboard: Fall back to follow=mouse instead of XDefaultScreen()
In dwm and similar window managers, it's common to often have empty
tags, and navigate to them with the intent to create clients there. If I
navigate to one of those empty tags with a dunst notification visible
and follow=keyboard set, the notification warps over to the default
screen.  If I then open a client, it then warps back, which is pretty
jarring.

This is mostly an artefact of the implementation of follow=keyboard --
if we fail to get a focused window, we use the default screen. However
this case isn't necessarily really a "failure" on window managers like
dwm where it's a common occurrence to end up with no clients on the
screen, whereas that would be significantly rarer on (say) GNOME or KDE.

A guess that's more likely to fit user expectations is falling back to
where the mouse pointer currently is, since this indicates the currently
focused monitor that the window manager would create a client on. This
avoids warping back to that monitor again when a client is created.
2020-04-27 16:34:53 +01:00
Benedikt Heine
a3342d0ced Add debug command 2020-04-10 10:48:47 +02:00
Benedikt Heine
e80e8e9eb5 Install dunstctl via make automatically 2020-04-10 10:48:37 +02:00
Benedikt Heine
72d68fcec4 Overhaul dunstctl 2020-04-10 10:47:12 +02:00
Felix Buehler
305ab6c4e6 Add dunstctl shell script 2020-04-10 10:47:12 +02:00
Benedikt Heine
55f4971a92 Add DBus interface to control dunst 2020-04-10 10:47:12 +02:00
Nikos Tsipinakis
523d5e199e Make all target build dunstify as well 2020-04-06 10:03:12 +02:00
Nikos Tsipinakis
b4633a0061
Merge pull request #705 from mkrasnitski/list-conf-options
Allow for multiple actions per mouse event
2020-04-06 09:56:46 +02:00
Michael Krasnitski
92a21487b2 Make some changes to default dunstrc
These make it more clear to the user that the mouse_click
options can be defined as lists.
2020-04-05 21:46:07 -04:00
Michael Krasnitski
8bcc3070fb Use g_strsplit instead of homebrewing a solution
Lesson: don't reinvent the wheel.
Additionally, changed a `g_malloc` call to `g_malloc_n` for safety.
2020-04-05 19:35:36 -04:00
Michael Krasnitski
a4a1a1ac9e Add tests for parsing lists from conf/cmdline. 2020-04-05 15:29:28 -04:00
Michael Krasnitski
506b4f2cfa Allow a mouse button to perform 1 or more actions in series.
The user provides a comma-separated list of valid mouse actions
that will be performed one after another when a notification is
clicked. If any one of the provided actions is invalid, the value
reverts to its default state.
2020-04-05 15:29:24 -04:00
Michael Krasnitski
94be674cf8 Add parsing of comma-separated list options from conf/cmdline. 2020-04-05 12:15:35 -04:00
Nikos Tsipinakis
0d038021a6 Update README to mention libnotify dependency 2020-04-04 15:40:47 +02:00
Nikos Tsipinakis
8fafc456cb
Merge pull request #702 from braunbearded/fix-dunstify-installation
Fix dunstify installation
2020-04-04 15:38:16 +02:00
braunbearded
c6f1f169a7 Always add libnotify as build dependency 2020-04-04 14:15:37 +02:00
Nikos Tsipinakis
520c36ad7a
Replace coveralls badge with codecov 2020-04-03 17:22:07 +02:00
Nikos Tsipinakis
12873ccd78
Merge pull request #701 from braunbearded/install-dunstify
Install dunstify
2020-04-03 17:03:59 +02:00
stefan
697db7198d Uninstall dunstify 2020-04-03 14:16:53 +02:00
stefan
7e2b148262 Install dunstify 2020-04-03 13:53:15 +02:00
Nikos Tsipinakis
9e5a647092
Merge pull request #700 from bebehei/ci
Update CI to Github actions
2020-04-02 12:47:52 +02:00
Benedikt Heine
c4fe74df37 Use Codecov, coveralls doesn't work at all
The docs of the Github action aren't working at all and are not deemed
to work. The "parallels" feature is completely broken and others have
solved it by switching to Codecov.

See coverallsapp/github-action#13
2020-03-31 23:54:38 +02:00
Benedikt Heine
db76fff1ca Add coveralls settings 2020-03-31 23:51:26 +02:00
Benedikt Heine
29e02c2a29 Introduce Github Actions as CI 2020-03-31 23:51:20 +02:00
Benedikt Heine
ef88e39de6
Merge pull request #699 from tsipinakis/invalidate_actions
Invalidate actions after notification is closed
2020-03-31 16:43:29 +02:00
Benedikt Heine
b3e640a8ef Remove CircleCI and Travis CI
Those will get replaced by Github Actions
2020-03-31 15:27:42 +02:00
Nikos Tsipinakis
69e319c729 Invalidate actions after notification is closed 2020-03-31 15:27:16 +02:00
Nikos Tsipinakis
43c614559c Remove WantedBy in systemd unit file
This avoids dunst starting automatically on session start, since it
usually happens before the graphical session is brought up and ends up
cluttering the journal with errors.

Either way, dunst is autostarted by dbus when a notification is sent.

See #314
2020-03-21 21:24:54 +01:00
Nikos Tsipinakis
fbdef7b58a
Merge pull request #694 from bebehei/issue-693-unset-home
Retrieve user's home from passwd entry as fallback
2020-03-13 16:45:31 +01:00
Benedikt Heine
9591af02a8 Retrieve user's home from passwd entry as fallback
For weird reasons, the HOME-variable might not always be set. So we
should fallback on something more reliable.

Fixes #693
2020-03-10 16:29:20 +01:00
Nikos Tsipinakis
dfd6e76de5
Merge pull request #684 from chronus7/master
Adding vertical content alignment control.
2020-01-30 15:07:52 +02:00
chronus
4c09883ef6 adding changes according to review
- adding default to config.h#defaults
- changing naming from content_* to vertical_*
- replacing switch-case with if-else-if
2020-01-29 23:02:00 +01:00
chronus
e665eea97e adding content_alignment to config and documentation 2020-01-26 11:44:31 +01:00
chronus
f9b6f6a066 fixing comparison issue in bottom alignment 2020-01-25 16:43:42 +01:00
chronus
89d7a81b9c Adding vertical content alignment control.
This adds the option `content_alignment`, which allows the user to set
the vertical alignment of the notification's content (i.e. icon and
text) to either top (`CONTENT_TOP`), center (`CONTENT_CENTER`, default),
or bottom (`CONTENT_BOT`). The default preserves current behaviour,
while the other options fulfill #486.
2020-01-25 16:35:38 +01:00
Nikos Tsipinakis
3f3082efb3
Merge pull request #674 from xkr47/patch-min_icon_size-20191122
Implement #392, take 2
2019-12-18 15:14:38 +02:00
Jonas Berlin
ad5d20bd6a Add tests for icon scaling math & loading 2019-12-17 22:40:36 +02:00
Jonas Berlin
03253e82f7 [ReviewFix] Remove bogous g_error_free() cal 2019-12-05 20:12:07 +02:00
Jonas Berlin
667503ef7d Cleanup, documentation 2019-12-04 21:01:29 +02:00
Jonas Berlin
87491192cb [RegressionFix] Slightly different valgrind leak trace in CI env 2019-12-03 22:34:17 +02:00
Jonas Berlin
9264b0f994 [ReviewFix] dunst.pod: Update icon scaling documentation 2019-12-03 21:29:43 +02:00
Jonas Berlin
445d305bf8 [ReviewFix] dunstrc: Add min_icon_size 2019-12-03 21:29:37 +02:00
Jonas Berlin
e077b949a6 [ReviewFix] gdk_pixbuf_from_file: Update valgrind suppressions to match new code paths 2019-12-03 20:28:08 +02:00
Jonas Berlin
39c97e28f6 [ReviewFix] get_pixbuf_from_file: Free memory on error return 2019-12-03 20:27:31 +02:00
Jonas Berlin
364bce1ed0 Scale icons during loading to get best quality for e.g. vector images 2019-11-29 21:28:22 +02:00
Jonas Berlin
ab3b5c2805 Implement #392 2019-11-29 21:14:57 +02:00
Nikos Tsipinakis
9e29406bb5 Fix dead pango reference link in manpage 2019-08-22 12:58:29 +03:00
Nikos Tsipinakis
68daebb713
Merge pull request #656 from rootkiwi/master
Fix dead link to pango reference in dunstrc
2019-08-22 12:55:57 +03:00
rootkiwi
eddcd2bfdf Fix dead link to pango reference in dunstrc 2019-08-22 11:37:38 +02:00
Benedikt Heine
53e7f3d16a
Merge pull request #650 from tsipinakis/bugfix/parse-empty
option_parser: Fail early when parsing empty string
2019-08-10 20:06:11 +02:00
Nikos Tsipinakis
f37a5d1c64 option_parser: Fail early when parsing empty string
Sometimes these parse functions can have other side-effects such as the
sepcolor one which defaults to a custom color string if the value is
unknown. If the string is empty all of these functions should fail to
allow for the default to be used.

See #649 for details
2019-08-10 19:47:30 +03:00
Nikos Tsipinakis
13307d2b17
Merge pull request #647 from bebehei/issue-646-overflow-tests
Don't overflow when passing INT_MAX on DBus wire
2019-08-09 16:29:12 +03:00
Benedikt Heine
52a8489043 Don't overflow when passing INT_MAX on DBus wire 2019-08-09 14:41:37 +02:00
Nikos Tsipinakis
6c4eeda434 Start new release cycle 2019-07-03 10:02:21 +03:00
98 changed files with 10104 additions and 850 deletions

View File

@ -1,71 +0,0 @@
version: 2.1
jobs:
misc-doxygen:
docker:
- image: dunst/ci:misc-doxygen
environment:
SYSTEMD: 0
SERVICEDIR_DBUS: /tmp/none
SERVICEDIR_SYSTEMD: /tmp/none
PKG_CONFIG: echo
steps:
- checkout
- run: make -j doc-doxygen
compileandtest:
environment:
CFLAGS: -Werror
parameters:
distro:
type: string
docker:
- image: dunst/ci:<<parameters.distro>>
steps:
- checkout
- run: make CC=clang -j all dunstify test/test
- run: make CC=clang -j test-valgrind
- run: ./test/test-install.sh
- run: make clean
- run: make CC=gcc -j all dunstify test/test
- run: make CC=gcc -j test-valgrind
- run: make clean
- run: make -j test-coverage
workflows:
version: 2
build-in-docker:
jobs:
- misc-doxygen
- compileandtest:
name: Alpine
distro: alpine
- compileandtest:
name: Debian Stretch
distro: debian-stretch
requires:
- misc-doxygen
- Alpine
- compileandtest:
name: Arch Linux
distro: archlinux
requires:
- misc-doxygen
- Alpine
- compileandtest:
name: Fedora 30
distro: fedora30
requires:
- misc-doxygen
- Alpine
- compileandtest:
name: Ubuntu 16.04
distro: ubuntu-xenial
requires:
- misc-doxygen
- Alpine
- compileandtest:
name: Ubuntu 18.04
distro: ubuntu-bionic
requires:
- misc-doxygen
- Alpine

82
.github/workflows/main.yml vendored Normal file
View File

@ -0,0 +1,82 @@
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 }}

4
.gitignore vendored
View File

@ -3,11 +3,13 @@
*.gcda
*.gcno
*.gcov
/lcov.info
core
vgcore.*
/docs/dunst.1
/docs/*.1
/docs/*.5
/docs/internal/coverage
/docs/internal/html
/dunst

View File

@ -1,38 +0,0 @@
addons:
apt:
packages:
- doxygen
- graphviz
- libdbus-1-dev
- libx11-dev
- libxrandr-dev
- libxinerama-dev
- libxss-dev
- libglib2.0-dev
- libpango1.0-dev
- libcairo2-dev
- libnotify-dev
- libgtk-3-dev
- valgrind
dist: xenial
sudo: false
language: c
git:
depth: false
before_install:
- pip install --user cpp-coveralls
script:
- CFLAGS="-Werror" make all dunstify test-valgrind doc-doxygen
- ./test/test-install.sh
- CFLAGS="-Werror" make clean
- CFLAGS="-Werror" make test-coverage
matrix:
include:
- compiler: gcc
after_success:
- coveralls
- compiler: clang

View File

@ -20,14 +20,48 @@
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_new_from_file
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)
@ -45,7 +79,8 @@
fun:rsvg_handle_write
obj:*/loaders/libpixbufloader-svg.so
obj:*/libgdk_pixbuf-2.0.so*
fun:gdk_pixbuf_new_from_file
fun:gdk_pixbuf_loader_close
fun:gdk_pixbuf_get_file_info
fun:get_pixbuf_from_file
...
}

View File

@ -1,5 +1,66 @@
# Dunst changelog
## Unreleased
### Added
### Changed
### Fixed
## 1.6.1 - 2021-02-21:
### Fixed
- Incorrect version in Makefile
## 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

View File

@ -15,6 +15,10 @@
- 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

View File

@ -3,7 +3,7 @@
include config.mk
VERSION := "1.4.1 (2019-07-03)"
VERSION := "1.6.1-non-git"
ifneq ($(wildcard ./.git/),)
VERSION := $(shell ${GIT} describe --tags)
endif
@ -33,6 +33,14 @@ $(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})
@ -45,7 +53,14 @@ endif
CFLAGS := ${DEFAULT_CPPFLAGS} ${CPPFLAGS} ${DEFAULT_CFLAGS} ${CFLAGS} ${INCS} -MMD -MP
LDFLAGS := ${DEFAULT_LDFLAGS} ${LDFLAGS} ${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
OBJ := ${SRC:.c=.o}
TEST_SRC := $(sort $(shell ${FIND} test/ -name '*.c'))
TEST_OBJ := $(TEST_SRC:.c=.o)
@ -53,7 +68,7 @@ DEPS := ${SRC:.c=.d} ${TEST_SRC:.c=.d}
.PHONY: all debug
all: doc dunst service
all: doc dunst dunstify service
debug: CFLAGS += ${CPPFLAGS_DEBUG} ${CFLAGS_DEBUG}
debug: LDFLAGS += ${LDFLAGS_DEBUG}
@ -75,7 +90,9 @@ dunstify: dunstify.o
.PHONY: test test-valgrind test-coverage
test: test/test clean-coverage-run
./test/test -v
# 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} \
@ -106,13 +123,21 @@ test/test: ${OBJ} ${TEST_OBJ}
${CC} -o ${@} ${TEST_OBJ} $(filter-out ${TEST_OBJ:test/%=src/%},${OBJ}) ${CFLAGS} ${LDFLAGS}
.PHONY: doc doc-doxygen
doc: docs/dunst.1
docs/dunst.1: docs/dunst.pod
doc: docs/dunst.1 docs/dunst.5 docs/dunstctl.1
# 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} $< > $@
doc-doxygen:
${DOXYGEN} docs/internal/Doxyfile
.PHONY: service service-dbus service-systemd
.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
@ -122,7 +147,23 @@ service-systemd:
@${SED} "s|##PREFIX##|$(PREFIX)|" dunst.systemd.service.in > dunst.systemd.service
endif
.PHONY: clean clean-dunst clean-dunstify clean-doc clean-tests clean-coverage clean-coverage-run
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
clean-dunst:
@ -137,6 +178,8 @@ clean-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
@ -151,18 +194,26 @@ clean-coverage-run:
${FIND} . -type f -name '*.gcov' -delete
${FIND} . -type f -name '*.gcda' -delete
.PHONY: install install-dunst install-doc \
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 uninstall-dunstctl \
uninstall-service uninstall-service-dbus uninstall-service-systemd
install: install-dunst install-doc install-service
install: install-dunst install-dunstctl install-doc install-service install-dunstify
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
install-doc:
install -Dm644 dunstrc ${DESTDIR}${DATADIR}/dunst/dunstrc
install -Dm644 dunstrc ${DESTDIR}${SYSCONFDIR}/dunst/dunstrc
install-service: install-service-dbus
install-service-dbus: service-dbus
@ -173,10 +224,19 @@ install-service-systemd: service-systemd
install -Dm644 dunst.systemd.service ${DESTDIR}${SERVICEDIR_SYSTEMD}/dunst.service
endif
uninstall: uninstall-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
rm -f ${DESTDIR}${MANPREFIX}/man1/dunst.1
rm -rf ${DESTDIR}${DATADIR}/dunst
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:

113
README.md
View File

@ -1,30 +1,94 @@
[![CircleCI](https://circleci.com/gh/dunst-project/dunst/tree/master.svg?style=svg)](https://circleci.com/gh/dunst-project/dunst/tree/master) [![Build Status](https://travis-ci.org/dunst-project/dunst.svg?branch=master)](https://travis-ci.org/dunst-project/dunst) [![Coverage Status](https://coveralls.io/repos/github/dunst-project/dunst/badge.svg?branch=master)](https://coveralls.io/github/dunst-project/dunst?branch=master)
[![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)
## Dunst
# Dunst
* [Wiki][wiki]
* [Description](#description)
* [Compiling](#compiling)
<i>A highly configurable and lightweight notification daemon.</i>
![music](contrib/screenshots/music.png)
## Table of Contents
* [Features](#features)
* [Building](#building)
* [Documentation](#documentation)
* [Copyright](#copyright)
## Description
# Features
Dunst is a highly configurable and lightweight notification daemon.
## ⚙️ Highly customizable
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_
<a href="https://gist.github.com/NNBnh/5f6e601a6a82a6ed43b1959698758141">
<img alt="screenshot1" src="contrib/screenshots/screenshot1_cut.png">
</a>
<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
Dunst has a number of build dependencies that must be present before attempting configuration. The names are different depending on [distribution](https://github.com/dunst-project/dunst/wiki/Dependencies):
- dbus
- dbus (runtime)
- libxinerama
- libxrandr
- libxss
- glib
- pango/cairo
- libgtk-3-dev
- libnotify (optional, for dunstify)
- wayland-client (can build without, see [make parameters](#make-parameters))
- wayland-protocols (optional, for recompiling protocols)
The names will be different depending on your [distribution](https://github.com/dunst-project/dunst/wiki/Dependencies).
### Building
@ -37,21 +101,32 @@ 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)`: Enable/Disable the systemd unit. (Default: detected via `pkg-config`)
- `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. You can also join us on the IRC channel `#dunst` on Freenode.
Please use the [issue tracker][issue-tracker] provided by GitHub to send us bug reports or feature requests.
## Screenshots
<a href="https://gist.github.com/MCotocel/2b34486ae59ccda4319fcb93454d212c">
<img alt="screenshot3" src="contrib/screenshots/screenshot3_cut.png">
</a>
<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
@ -60,13 +135,13 @@ Please use the [issue tracker][issue-tracker] provided by GitHub to send us bug
## 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)
If you feel that copyrights are violated, please send me an email.
Copyright 2013 Sascha Kruse and contributors (see [`LICENSE`](./LICENSE) for licensing information)
[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,3 +1,50 @@
===================================================================================
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
===================================================================================

View File

@ -6,10 +6,13 @@ struct settings defaults = {
.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",
.format = "%s %b", /* default format */
.timeouts = { S2US(10), S2US(10), S2US(0) }, /* low, normal, critical */
@ -33,19 +36,25 @@ struct settings defaults = {
.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] */
.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,
.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 */
.frame_width = 0,
@ -65,6 +74,7 @@ struct settings defaults = {
.browser = "/usr/bin/firefox",
.min_icon_size = 0,
.max_icon_size = 0,
/* paths to default icons */
@ -101,12 +111,23 @@ struct settings defaults = {
.code = 0,.sym = NoSymbol,.is_valid = false
}, /* ignore this */
.mouse_left_click = MOUSE_CLOSE_CURRENT,
.mouse_left_click = (enum mouse_action []){MOUSE_CLOSE_CURRENT, -1},
.mouse_middle_click = MOUSE_DO_ACTION,
.mouse_middle_click = (enum mouse_action []){MOUSE_DO_ACTION, -1},
.mouse_right_click = MOUSE_CLOSE_ALL,
.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[] = {
@ -138,4 +159,4 @@ struct rule default_rules[] = {
}
};
/* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */
/* vim: set ft=c tabstop=8 shiftwidth=8 expandtab textwidth=0: */

View File

@ -1,10 +1,12 @@
# 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
@ -20,14 +22,23 @@ VALGRIND ?= valgrind
# 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
# 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}
DEFAULT_LDFLAGS = -lm
DEFAULT_CFLAGS = -g --std=gnu99 -pedantic -Wall -Wno-overlength-strings -Os ${STATIC} ${ENABLE_WAYLAND} ${EXTRA_CFLAGS}
DEFAULT_LDFLAGS = -lm -lrt
CPPFLAGS_DEBUG := -DDEBUG_BUILD
CFLAGS_DEBUG := -O0
@ -41,13 +52,16 @@ pkg_config_packs := gio-2.0 \
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)))
$(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

75
contrib/progress-notify.sh Executable file
View File

@ -0,0 +1,75 @@
#!/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.

After

Width:  |  Height:  |  Size: 388 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 406 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 672 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 214 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 393 KiB

172
docs/dunst.1.pod Normal file
View File

@ -0,0 +1,172 @@
=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,48 +1,20 @@
=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] ...
dunst - configuration file
=head1 DESCRIPTION
Dunst is a highly configurable and lightweight notification daemon.
=head1 COMMAND LINE OPTIONS
=over 4
=item B<-h/--help>
List all command line flags
=item B<-conf/-config file>
Use alternative config file.
=item B<-v/--version>
Print version information.
=item B<-print>
Print notifications to stdout. This might be useful for logging, setting up
rules or using the output in other scripts.
=back
=head1 CONFIGURATION
An example configuration file is included (usually /usr/share/dunst/dunstrc).
An example configuration file is included (usually /etc/dunst/dunstrc). Note:
this was previously /usr/share/dunst/dunstrc.
To change the configuration, copy this file to ~/.config/dunst/dunstrc and edit
it accordingly.
The configuration is divided into sections in an ini-like format. The 'global'
section contains most general settings while the 'shortcuts' sections contains
all keyboard configuration and the 'experimental' section all the features that
have not yet been tested thoroughly.
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.
Any section that is not one of the above is assumed to be a rule, see RULES for
more details.
@ -50,24 +22,6 @@ 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
@ -82,6 +36,10 @@ 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>
@ -142,6 +100,32 @@ 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
@ -188,6 +172,23 @@ 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
@ -238,6 +239,25 @@ 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.
=item B<font> (default: "Monospace 8")
Defines the font or font set used. Optionally set the size as a decimal number
@ -272,7 +292,7 @@ Allow a small subset of html markup in notifications
<u>underline</u>
For a complete reference see
<http://developer.gnome.org/pango/stable/PangoMarkupFormat.html>
<https://developer.gnome.org/pango/stable/pango-Markup.html>
=item B<strip>
@ -337,6 +357,11 @@ 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.
@ -370,7 +395,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_duplicates_count> (values: [true/false], default: false)
=item B<hide_duplicate_count> (values: [true/false], default: false)
Hide the count of stacked duplicate notifications.
@ -384,14 +409,28 @@ 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 the specified value it won't be affected.
If the icon is smaller than or equal to the specified value it won't be affected.
If it's larger then it will be scaled down so that the larger axis is equivalent
to the specified size.
Set to 0 to disable icon scaling. (default)
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.
If B<icon_position> is set to off, this setting is ignored.
@ -450,7 +489,7 @@ 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)
=item B<force_xinerama> (values: [true/false], default: false) (X11 only)
Use the Xinerama extension instead of RandR for multi-monitor support. This
setting is provided for compatibility with older nVidia drivers that do not
@ -472,9 +511,10 @@ 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])
=item B<mouse_left/middle/right_click> (values: [none/do_action/close_current/close_all/context/context_all])
Defines action of mouse click.
Defines action of mouse click. A touch input in Wayland acts as a mouse left
click.
=over 4
@ -484,7 +524,13 @@ Don't do anything.
=item B<do_action> (default for mouse_middle_click)
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.
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)
@ -494,11 +540,26 @@ Close current notification.
Close all notifications.
=back
=item B<context>
Open context menu for the notification.
=item B<context_all>
Open context menu for all notifications.
=back
=head2 Shortcut section
=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)
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.
@ -535,8 +596,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, timeout, frame_color and
icon attributes can be modified.
critical). Currently only the background, foreground, hightlight, 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.
@ -569,6 +630,12 @@ 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.
@ -583,18 +650,53 @@ 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 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.
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.
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
@ -692,7 +794,12 @@ The background color of the notification. See COLORS for possible values.
=item C<foreground>
The background color of the notification. See COLORS for possible values.
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>
@ -706,7 +813,7 @@ The frame color color of the notification. See COLORS for possible values.
One of show, delay, or pushback.
This attribute speicifies how notifications are handled if a fullscreen window
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
@ -717,6 +824,16 @@ 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.
@ -725,7 +842,7 @@ Updates the icon of the notification, it should be a path to a valid image.
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 only want one of
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',
@ -757,6 +874,13 @@ later. Use C<msg_urgency> to match it.
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
@ -776,11 +900,22 @@ Within rules you can specify a script to be run every time the rule is matched
by assigning the 'script' option to the name of the script to be run.
When the script is called details of the notification that triggered it will be
passed via command line parameters in the following order: appname, summary,
body, icon, urgency.
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>.
Where icon is the absolute path to the icon file if there is one and urgency is
one of "LOW", "NORMAL" or "CRITICAL".
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.
If the notification is suppressed, the script will not be run unless
B<always_run_scripts> is set to true.
@ -795,6 +930,8 @@ 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"
@ -804,6 +941,46 @@ 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
@ -846,9 +1023,8 @@ Example time: "1000ms" "10m"
=head1 MISCELLANEOUS
Dunst can be paused by sending a notification with a summary of
"DUNST_COMMAND_PAUSE", resumed with a summary of "DUNST_COMMAND_RESUME" and
toggled with a summary of "DUNST_COMMAND_TOGGLE".
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:
@ -867,11 +1043,27 @@ 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-
$HOME/.config/dunst/dunstrc
$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
@ -889,4 +1081,4 @@ If you feel that copyrights are violated, please send me an email.
=head1 SEE ALSO
dwm(1), dmenu(1), twmn(1), notify-send(1)
dunst(1), dunstctl(1), dmenu(1), notify-send(1)

64
docs/dunstctl.pod Normal file
View File

@ -0,0 +1,64 @@
=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

@ -0,0 +1,33 @@
# 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,7 +7,3 @@ PartOf=graphical-session.target
Type=dbus
BusName=org.freedesktop.Notifications
ExecStart=##PREFIX##/bin/dunst
[Install]
WantedBy=default.target

122
dunstctl Executable file
View File

@ -0,0 +1,122 @@
#!/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

@ -127,6 +127,11 @@ 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) {
g_printerr("I need at least a summary\n");
@ -234,7 +239,7 @@ void add_action(NotifyNotification *n, char *str)
char *label = strchr(str, ',');
if (!label || *(label+1) == '\0') {
g_printerr("Malformed action. Excpected \"action,label\", got \"%s\"", str);
g_printerr("Malformed action. Expected \"action,label\", got \"%s\"", str);
return;
}
@ -359,4 +364,4 @@ int main(int argc, char *argv[])
die(0);
}
/* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */
/* vim: set ft=c tabstop=8 shiftwidth=8 expandtab textwidth=0: */

91
dunstrc
View File

@ -31,6 +31,23 @@
# 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
@ -59,6 +76,9 @@
# 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,7 +120,7 @@
# <u>underline</u>
#
# For a complete reference see
# <http://developer.gnome.org/pango/stable/PangoMarkupFormat.html>.
# <https://developer.gnome.org/pango/stable/pango-Markup.html>.
#
# strip: This setting is provided for compatibility with some broken
# clients that send markup even though it's not enabled on the
@ -132,6 +152,10 @@
# 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.
@ -160,7 +184,12 @@
### Icons ###
# Align icons left/right/off
icon_position = 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
# Scale larger icons down to this size, set to 0 to disable
max_icon_size = 32
@ -215,6 +244,22 @@
# 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.
@ -229,15 +274,21 @@
### mouse
# Defines action of mouse event
# Defines list of actions for each mouse event
# Possible values are:
# * none: Don't do anything.
# * do_action: 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.
# * 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
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
@ -250,6 +301,10 @@
# 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
@ -257,20 +312,21 @@
# "mod3" and "mod4" (windows-key).
# Xev might be helpful to find names for keys.
# Close notification.
close = ctrl+space
# Close notification. Equivalent dunstctl command:
# dunstctl close
# close = ctrl+space
# Close all notifications.
close_all = ctrl+shift+space
# Close all notifications. Equivalent dunstctl command:
# dunstctl close-all
# close_all = ctrl+shift+space
# Redisplay last message(s).
# On the US keyboard layout "grave" is normally above TAB and left
# of "1". Make sure this key actually exists on your keyboard layout,
# e.g. check output of 'xmodmap -pke'
history = ctrl+grave
# Redisplay last message(s). Equivalent dunstctl command:
# dunstctl history-pop
# history = ctrl+grave
# Context menu.
context = ctrl+shift+period
# Context menu. Equivalent dunstctl command:
# dunstctl context
# context = ctrl+shift+period
[urgency_low]
# IMPORTANT: colors have to be defined in quotation marks.
@ -321,6 +377,7 @@
# set_transient
# timeout
# urgency
# action_name
#
# Shell-like globbing will get expanded.
#

2
main.c
View File

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

View File

@ -8,6 +8,7 @@
#include "dunst.h"
#include "log.h"
#include "menu.h"
#include "notification.h"
#include "queues.h"
#include "settings.h"
@ -17,6 +18,12 @@
#define FDN_IFAC "org.freedesktop.Notifications"
#define FDN_NAME "org.freedesktop.Notifications"
#define DUNST_PATH "/org/freedesktop/Notifications"
#define DUNST_IFAC "org.dunstproject.cmd0"
#define DUNST_NAME "org.freedesktop.Notifications"
#define PROPERTIES_IFAC "org.freedesktop.DBus.Properties"
GDBusConnection *dbus_conn;
static GDBusNodeInfo *introspection_data = NULL;
@ -62,7 +69,27 @@ static const char *introspection_xml =
" <arg name=\"id\" type=\"u\"/>"
" <arg name=\"action_key\" type=\"s\"/>"
" </signal>"
" </interface>"
" </interface>"
" <interface name=\""DUNST_IFAC"\">"
" <method name=\"ContextMenuCall\" />"
" <method name=\"NotificationAction\">"
" <arg name=\"number\" type=\"i\"/>"
" </method>"
" <method name=\"NotificationCloseLast\" />"
" <method name=\"NotificationCloseAll\" />"
" <method name=\"NotificationShow\" />"
" <method name=\"Ping\" />"
" <property name=\"paused\" type=\"b\" access=\"readwrite\">"
" <annotation name=\"org.freedesktop.DBus.Property.EmitsChangedSignal\" value=\"true\"/>"
" </property>"
" <property name=\"displayedLength\" type=\"u\" access=\"read\" />"
" <property name=\"historyLength\" type=\"u\" access=\"read\" />"
" <property name=\"waitingLength\" type=\"u\" access=\"read\" />"
" </interface>"
"</node>";
static const char *stack_tag_hints[] = {
@ -98,7 +125,6 @@ DBUS_METHOD(Notify);
DBUS_METHOD(CloseNotification);
DBUS_METHOD(GetCapabilities);
DBUS_METHOD(GetServerInformation);
static struct dbus_method methods_fdn[] = {
{"CloseNotification", dbus_cb_CloseNotification},
{"GetCapabilities", dbus_cb_GetCapabilities},
@ -106,7 +132,7 @@ static struct dbus_method methods_fdn[] = {
{"Notify", dbus_cb_Notify},
};
void handle_method_call(GDBusConnection *connection,
void dbus_cb_fdn_methods(GDBusConnection *connection,
const gchar *sender,
const gchar *object_path,
const gchar *interface_name,
@ -115,12 +141,12 @@ void handle_method_call(GDBusConnection *connection,
GDBusMethodInvocation *invocation,
gpointer user_data)
{
struct dbus_method *m = bsearch(
method_name,
&methods_fdn,
G_N_ELEMENTS(methods_fdn),
sizeof(struct dbus_method),
cmp_methods);
struct dbus_method *m = bsearch(method_name,
methods_fdn,
G_N_ELEMENTS(methods_fdn),
sizeof(struct dbus_method),
cmp_methods);
if (m) {
m->method(connection, sender, parameters, invocation);
@ -131,6 +157,143 @@ void handle_method_call(GDBusConnection *connection,
}
}
DBUS_METHOD(dunst_ContextMenuCall);
DBUS_METHOD(dunst_NotificationAction);
DBUS_METHOD(dunst_NotificationCloseAll);
DBUS_METHOD(dunst_NotificationCloseLast);
DBUS_METHOD(dunst_NotificationShow);
DBUS_METHOD(dunst_Ping);
static struct dbus_method methods_dunst[] = {
{"ContextMenuCall", dbus_cb_dunst_ContextMenuCall},
{"NotificationAction", dbus_cb_dunst_NotificationAction},
{"NotificationCloseAll", dbus_cb_dunst_NotificationCloseAll},
{"NotificationCloseLast", dbus_cb_dunst_NotificationCloseLast},
{"NotificationShow", dbus_cb_dunst_NotificationShow},
{"Ping", dbus_cb_dunst_Ping},
};
void dbus_cb_dunst_methods(GDBusConnection *connection,
const gchar *sender,
const gchar *object_path,
const gchar *interface_name,
const gchar *method_name,
GVariant *parameters,
GDBusMethodInvocation *invocation,
gpointer user_data)
{
struct dbus_method *m = bsearch(method_name,
methods_dunst,
G_N_ELEMENTS(methods_dunst),
sizeof(struct dbus_method),
cmp_methods);
if (m) {
m->method(connection, sender, parameters, invocation);
} else {
LOG_M("Unknown method name: '%s' (sender: '%s').",
method_name,
sender);
}
}
static void dbus_cb_dunst_ContextMenuCall(GDBusConnection *connection,
const gchar *sender,
GVariant *parameters,
GDBusMethodInvocation *invocation)
{
LOG_D("CMD: Calling context menu");
context_menu();
g_dbus_method_invocation_return_value(invocation, NULL);
g_dbus_connection_flush(connection, NULL, NULL, NULL);
}
static void dbus_cb_dunst_NotificationAction(GDBusConnection *connection,
const gchar *sender,
GVariant *parameters,
GDBusMethodInvocation *invocation)
{
int notification_nr = 0;
g_variant_get(parameters, "(i)", &notification_nr);
LOG_D("CMD: Calling action for notification %d", notification_nr);
if (notification_nr < 0 || queues_length_waiting() < notification_nr) {
g_dbus_method_invocation_return_error(invocation,
G_DBUS_ERROR,
G_DBUS_ERROR_INVALID_ARGS,
"Couldn't activate action for notification in position %d, %d notifications currently open",
notification_nr, queues_length_waiting());
return;
}
struct notification *n = g_list_nth_data(queues_get_displayed(), notification_nr);
if (n) {
LOG_D("CMD: Calling action for notification %s", n->summary);
notification_do_action(n);
}
g_dbus_method_invocation_return_value(invocation, NULL);
g_dbus_connection_flush(connection, NULL, NULL, NULL);
}
static void dbus_cb_dunst_NotificationCloseAll(GDBusConnection *connection,
const gchar *sender,
GVariant *parameters,
GDBusMethodInvocation *invocation)
{
LOG_D("CMD: Pushing all to history");
queues_history_push_all();
wake_up();
g_dbus_method_invocation_return_value(invocation, NULL);
g_dbus_connection_flush(connection, NULL, NULL, NULL);
}
static void dbus_cb_dunst_NotificationCloseLast(GDBusConnection *connection,
const gchar *sender,
GVariant *parameters,
GDBusMethodInvocation *invocation)
{
LOG_D("CMD: Closing last notification");
const GList *list = queues_get_displayed();
if (list && list->data) {
struct notification *n = list->data;
queues_notification_close_id(n->id, REASON_USER);
wake_up();
}
g_dbus_method_invocation_return_value(invocation, NULL);
g_dbus_connection_flush(connection, NULL, NULL, NULL);
}
static void dbus_cb_dunst_NotificationShow(GDBusConnection *connection,
const gchar *sender,
GVariant *parameters,
GDBusMethodInvocation *invocation)
{
LOG_D("CMD: Showing last notification from history");
queues_history_pop();
wake_up();
g_dbus_method_invocation_return_value(invocation, NULL);
g_dbus_connection_flush(connection, NULL, NULL, NULL);
}
/* Just a simple Ping command to give the ability to dunstctl to test for the existence of this interface
* Any other way requires parsing the XML of the Introspection or other foo. Just calling the Ping on an old dunst version will fail. */
static void dbus_cb_dunst_Ping(GDBusConnection *connection,
const gchar *sender,
GVariant *parameters,
GDBusMethodInvocation *invocation)
{
g_dbus_method_invocation_return_value(invocation, NULL);
g_dbus_connection_flush(connection, NULL, NULL, NULL);
}
static void dbus_cb_GetCapabilities(
GDBusConnection *connection,
const gchar *sender,
@ -144,6 +307,7 @@ static void dbus_cb_GetCapabilities(
g_variant_builder_add(builder, "s", "actions");
g_variant_builder_add(builder, "s", "body");
g_variant_builder_add(builder, "s", "body-hyperlinks");
g_variant_builder_add(builder, "s", "icon-static");
for (int i = 0; i < sizeof(stack_tag_hints)/sizeof(*stack_tag_hints); ++i)
g_variant_builder_add(builder, "s", stack_tag_hints[i]);
@ -222,6 +386,11 @@ static struct notification *dbus_message_to_notification(const gchar *sender, GV
g_variant_unref(dict_value);
}
if ((dict_value = g_variant_lookup_value(hints, "hlcolor", G_VARIANT_TYPE_STRING))) {
n->colors.highlight = g_variant_dup_string(dict_value, NULL);
g_variant_unref(dict_value);
}
if ((dict_value = g_variant_lookup_value(hints, "category", G_VARIANT_TYPE_STRING))) {
n->category = g_variant_dup_string(dict_value, NULL);
g_variant_unref(dict_value);
@ -286,7 +455,7 @@ static struct notification *dbus_message_to_notification(const gchar *sender, GV
}
if (timeout >= 0)
n->timeout = timeout * 1000;
n->timeout = ((gint64)timeout) * 1000;
g_variant_unref(hints);
g_variant_type_free(required_type);
@ -335,7 +504,17 @@ static void dbus_cb_CloseNotification(
{
guint32 id;
g_variant_get(parameters, "(u)", &id);
queues_notification_close_id(id, REASON_SIG);
if (settings.ignore_dbusclose) {
LOG_D("Ignoring CloseNotification message");
// Stay commpliant by lying to the sender, telling him we closed the notification
if (id > 0) {
struct notification *n = queues_get_by_id(id);
if (n)
signal_notification_closed(n, REASON_SIG);
}
} else {
queues_notification_close_id(id, REASON_SIG);
}
wake_up();
g_dbus_method_invocation_return_value(invocation, NULL);
g_dbus_connection_flush(connection, NULL, NULL, NULL);
@ -347,19 +526,15 @@ static void dbus_cb_GetServerInformation(
GVariant *parameters,
GDBusMethodInvocation *invocation)
{
GVariant *value;
value = g_variant_new("(ssss)", "dunst", "knopwob", VERSION, "1.2");
g_dbus_method_invocation_return_value(invocation, value);
GVariant *answer = g_variant_new("(ssss)", "dunst", "knopwob", VERSION, "1.2");
g_dbus_method_invocation_return_value(invocation, answer);
g_dbus_connection_flush(connection, NULL, NULL, NULL);
}
void signal_notification_closed(struct notification *n, enum reason reason)
{
if (!n->dbus_valid) {
LOG_W("Closing notification '%s' not supported. "
"Notification already closed.", n->summary);
return;
}
@ -384,11 +559,34 @@ void signal_notification_closed(struct notification *n, enum reason reason)
body,
&err);
notification_invalidate_actions(n);
n->dbus_valid = false;
if (err) {
LOG_W("Unable to close notification: %s", err->message);
g_error_free(err);
} else {
char* reason_string;
switch (reason) {
case REASON_TIME:
reason_string="time";
break;
case REASON_USER:
reason_string="user";
break;
case REASON_SIG:
reason_string="signal";
break;
case REASON_UNDEF:
reason_string="undfined";
break;
default:
reason_string="unknown";
}
LOG_D("Queues: Closing notification for reason: %s", reason_string);
}
}
@ -418,28 +616,106 @@ void signal_action_invoked(const struct notification *n, const char *identifier)
}
}
static const GDBusInterfaceVTable interface_vtable = {
handle_method_call
GVariant *dbus_cb_dunst_Properties_Get(GDBusConnection *connection,
const gchar *sender,
const gchar *object_path,
const gchar *interface_name,
const gchar *property_name,
GError **error,
gpointer user_data)
{
struct dunst_status status = dunst_status_get();
if (STR_EQ(property_name, "paused")) {
return g_variant_new_boolean(!status.running);
} else if (STR_EQ(property_name, "displayedLength")) {
unsigned int displayed = queues_length_displayed();
return g_variant_new_uint32(displayed);
} else if (STR_EQ(property_name, "historyLength")) {
unsigned int history = queues_length_history();
return g_variant_new_uint32(history);
} else if (STR_EQ(property_name, "waitingLength")) {
unsigned int waiting = queues_length_waiting();
return g_variant_new_uint32(waiting);
} else {
LOG_W("Unknown property!\n");
*error = g_error_new(G_DBUS_ERROR, G_DBUS_ERROR_UNKNOWN_PROPERTY, "Unknown property");
return NULL;
}
}
gboolean dbus_cb_dunst_Properties_Set(GDBusConnection *connection,
const gchar *sender,
const gchar *object_path,
const gchar *interface_name,
const gchar *property_name,
GVariant *value,
GError **error,
gpointer user_data)
{
if (STR_EQ(property_name, "paused")) {
dunst_status(S_RUNNING, !g_variant_get_boolean(value));
wake_up();
GVariantBuilder *builder = g_variant_builder_new(G_VARIANT_TYPE_ARRAY);
GVariantBuilder *invalidated_builder = g_variant_builder_new(G_VARIANT_TYPE("as"));
g_variant_builder_add(builder,
"{sv}",
"paused", g_variant_new_boolean(g_variant_get_boolean(value)));
g_dbus_connection_emit_signal(connection,
NULL,
object_path,
"org.freedesktop.DBus.Properties",
"PropertiesChanged",
g_variant_new("(sa{sv}as)",
interface_name,
builder,
invalidated_builder),
NULL);
return true;
}
*error = g_error_new(G_DBUS_ERROR, G_DBUS_ERROR_UNKNOWN_PROPERTY, "Unknown property");
return false;
}
static const GDBusInterfaceVTable interface_vtable_fdn = {
dbus_cb_fdn_methods
};
static const GDBusInterfaceVTable interface_vtable_dunst = {
dbus_cb_dunst_methods,
dbus_cb_dunst_Properties_Get,
dbus_cb_dunst_Properties_Set,
};
static void dbus_cb_bus_acquired(GDBusConnection *connection,
const gchar *name,
gpointer user_data)
{
guint registration_id;
GError *err = NULL;
if(!g_dbus_connection_register_object(
connection,
FDN_PATH,
introspection_data->interfaces[0],
&interface_vtable_fdn,
NULL,
NULL,
&err)) {
DIE("Unable to register dbus connection interface '%s': %s", introspection_data->interfaces[0]->name, err->message);
}
registration_id = g_dbus_connection_register_object(connection,
FDN_PATH,
introspection_data->interfaces[0],
&interface_vtable,
NULL,
NULL,
&err);
if (registration_id == 0) {
DIE("Unable to register dbus connection: %s", err->message);
if(!g_dbus_connection_register_object(
connection,
FDN_PATH,
introspection_data->interfaces[1],
&interface_vtable_dunst,
NULL,
NULL,
&err)) {
DIE("Unable to register dbus connection interface '%s': %s", introspection_data->interfaces[1]->name, err->message);
}
}
@ -447,7 +723,9 @@ static void dbus_cb_name_acquired(GDBusConnection *connection,
const gchar *name,
gpointer user_data)
{
dbus_conn = connection;
// If we're not able to get org.fd.N bus, we've still got a problem
if (STR_EQ(name, FDN_NAME))
dbus_conn = connection;
}
/**
@ -609,4 +887,4 @@ void dbus_teardown(int owner_id)
g_bus_unown_name(owner_id);
}
/* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */
/* vim: set ft=c tabstop=8 shiftwidth=8 expandtab textwidth=0: */

View File

@ -3,6 +3,7 @@
#ifndef DUNST_DBUS_H
#define DUNST_DBUS_H
#include "dunst.h"
#include "notification.h"
/// The reasons according to the notification spec
@ -21,4 +22,4 @@ void signal_notification_closed(struct notification *n, enum reason reason);
void signal_action_invoked(const struct notification *n, const char *identifier);
#endif
/* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */
/* vim: set ft=c tabstop=8 shiftwidth=8 expandtab textwidth=0: */

View File

@ -1,7 +1,6 @@
#include "draw.h"
#include <assert.h>
#include <cairo.h>
#include <math.h>
#include <pango/pango-attributes.h>
#include <pango/pangocairo.h>
@ -9,6 +8,8 @@
#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"
@ -16,12 +17,21 @@
#include "markup.h"
#include "notification.h"
#include "queues.h"
#include "x11/x.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;
@ -29,24 +39,33 @@ struct colored_layout {
const struct notification *n;
};
struct window_x11 *win;
const struct output *output;
window win;
PangoFontDescription *pango_fdesc;
#define UINT_MAX_N(bits) ((1 << bits) - 1)
void draw_setup(void)
{
x_setup();
const struct output *out = output_create(settings.force_xwayland);
output = out;
win = out->win_create();
win = x_win_create();
pango_fdesc = pango_font_description_from_string(settings.font);
}
static struct color hex_to_color(int hexValue)
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 >> 16) & 0xFF) / 255.0;
ret.g = ((hexValue >> 8) & 0xFF) / 255.0;
ret.b = ((hexValue) & 0xFF) / 255.0;
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;
}
@ -54,15 +73,24 @@ static struct color hex_to_color(int hexValue)
static struct color string_to_color(const char *str)
{
char *end;
long int val = strtol(str+1, &end, 16);
if (*end != '\0' && *(end+1) != '\0') {
uint_fast32_t val = strtoul(str+1, &end, 16);
if (end[0] != '\0' && end[1] != '\0') {
LOG_W("Invalid color string: '%s'", str);
}
return hex_to_color(val);
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 double color_apply_delta(double base, double delta)
static inline double color_apply_delta(double base, double delta)
{
base += delta;
if (base > 1)
@ -113,10 +141,11 @@ static struct color layout_get_sepcolor(struct colored_layout *cl,
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 * PANGO_SCALE);
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 * PANGO_SCALE);
pango_layout_set_spacing(layout, settings.line_height * scale * PANGO_SCALE);
PangoAlignment align;
switch (settings.align) {
@ -150,11 +179,36 @@ 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();
struct screen_info *scr = get_active_screen();
const struct screen_info *scr = output->get_active_screen();
if (have_dynamic_width()) {
/* dynamic width */
dim.w = 0;
@ -179,10 +233,10 @@ static struct dimensions calculate_dimensions(GSList *layouts)
for (GSList *iter = layouts; iter; iter = iter->next) {
struct colored_layout *cl = iter->data;
int w=0,h=0;
pango_layout_get_pixel_size(cl->l, &w, &h);
get_text_size(cl->l, &w, &h, scale);
if (cl->icon) {
h = MAX(cairo_image_surface_get_height(cl->icon), h);
w += cairo_image_surface_get_width(cl->icon) + settings.h_padding;
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;
@ -207,20 +261,27 @@ static struct dimensions calculate_dimensions(GSList *layouts)
w = dim.w;
w -= 2 * settings.h_padding;
w -= 2 * settings.frame_width;
if (cl->icon) w -= cairo_image_surface_get_width(cl->icon) + settings.h_padding;
if (cl->icon) {
w -= get_icon_width(cl->icon, scale) + get_text_icon_padding();
}
layout_setup_pango(cl->l, w);
/* re-read information */
pango_layout_get_pixel_size(cl->l, &w, &h);
get_text_size(cl->l, &w, &h, scale);
if (cl->icon) {
h = MAX(cairo_image_surface_get_height(cl->icon), h);
w += cairo_image_surface_get_width(cl->icon) + settings.h_padding;
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);
}
@ -234,10 +295,10 @@ static struct dimensions calculate_dimensions(GSList *layouts)
static PangoLayout *layout_create(cairo_t *c)
{
struct screen_info *screen = get_active_screen();
const struct screen_info *screen = output->get_active_screen();
PangoContext *context = pango_cairo_create_context(c);
pango_cairo_context_set_resolution(context, screen_dpi_get(screen));
pango_cairo_context_set_resolution(context, screen->dpi);
PangoLayout *layout = pango_layout_new(context);
@ -250,6 +311,7 @@ static struct colored_layout *layout_init_shared(cairo_t *c, const struct notifi
{
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;
@ -283,6 +345,7 @@ static struct colored_layout *layout_init_shared(cairo_t *c, const struct notifi
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;
@ -295,7 +358,9 @@ static struct colored_layout *layout_init_shared(cairo_t *c, const struct notifi
} else {
width -= 2 * settings.h_padding;
width -= 2 * settings.frame_width;
if (cl->icon) width -= cairo_image_surface_get_width(cl->icon) + settings.h_padding;
if (cl->icon) {
width -= get_icon_width(cl->icon, scale) + get_text_icon_padding();
}
layout_setup_pango(cl->l, width);
}
@ -315,6 +380,7 @@ static struct colored_layout *layout_from_notification(cairo_t *c, struct notifi
{
struct colored_layout *cl = layout_init_shared(c, n);
int scale = output->get_scale();
/* markup */
GError *err = NULL;
@ -336,9 +402,15 @@ static struct colored_layout *layout_from_notification(cairo_t *c, struct notifi
}
pango_layout_get_pixel_size(cl->l, NULL, &(n->displayed_height));
if (cl->icon) n->displayed_height = MAX(cairo_image_surface_get_height(cl->icon), n->displayed_height);
n->displayed_height = MAX(settings.notification_height, n->displayed_height + settings.padding * 2);
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;
@ -377,15 +449,46 @@ static GSList *create_layouts(cairo_t *c)
}
static int layout_get_height(struct colored_layout *cl)
static int layout_get_height(struct colored_layout *cl, int scale)
{
int h;
int h_icon = 0;
pango_layout_get_pixel_size(cl->l, NULL, &h);
int h_progress_bar = 0;
get_text_size(cl->l, NULL, &h, scale);
if (cl->icon)
h_icon = cairo_image_surface_get_height(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;
}
return MAX(h, h_icon);
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;
}
/**
@ -393,8 +496,14 @@ static int layout_get_height(struct colored_layout *cl)
* The top corners will get rounded by `corner_radius`, if `first` is set.
* Respectably the same for `last` with the bottom corners.
*/
static void draw_rounded_rect(cairo_t *c, int x, int y, int width, int height, int corner_radius, bool first, bool last)
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);
@ -442,6 +551,13 @@ static void draw_rounded_rect(cairo_t *c, int x, int y, int width, int height, i
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,
@ -451,12 +567,20 @@ static cairo_surface_t *render_background(cairo_surface_t *srf,
int corner_radius,
bool first,
bool last,
int *ret_width)
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)
@ -464,9 +588,7 @@ static cairo_surface_t *render_background(cairo_surface_t *srf,
else
height += settings.separator_height;
cairo_set_source_rgb(c, cl->frame.r, cl->frame.g, cl->frame.b);
draw_rounded_rect(c, x, y, width, height, corner_radius, first, last);
cairo_fill(c);
draw_rounded_rect(c, x, y, width, height, corner_radius, scale, first, last);
/* adding frame */
x += settings.frame_width;
@ -482,17 +604,25 @@ static cairo_surface_t *render_background(cairo_surface_t *srf,
else
height -= settings.separator_height;
cairo_set_source_rgb(c, cl->bg.r, cl->bg.g, cl->bg.b);
draw_rounded_rect(c, x, y, width, height, corner_radius, first, last);
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_rgb(c, sep_color.r, sep_color.g, sep_color.b);
cairo_set_source_rgba(c, sep_color.r, sep_color.g, sep_color.b, sep_color.a);
cairo_rectangle(c, settings.frame_width, y + height, width, settings.separator_height);
draw_rect(c, settings.frame_width, y + height, width, settings.separator_height, scale);
cairo_fill(c);
}
@ -502,48 +632,104 @@ static cairo_surface_t *render_background(cairo_surface_t *srf,
if (ret_width)
*ret_width = width;
return cairo_surface_create_for_rectangle(srf, x, y, width, height);
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)
static void render_content(cairo_t *c, struct colored_layout *cl, int width, int scale)
{
const int h = layout_get_height(cl);
int h_text;
pango_layout_get_pixel_size(cl->l, NULL, &h_text);
if (cl->icon && settings.icon_position == ICON_LEFT) {
cairo_move_to(c, cairo_image_surface_get_width(cl->icon) + 2 * settings.h_padding,
settings.padding + h/2 - h_text/2);
} else if (cl->icon && settings.icon_position == ICON_RIGHT) {
cairo_move_to(c, settings.h_padding, settings.padding + h/2 - h_text/2);
} else {
cairo_move_to(c, settings.h_padding, settings.padding);
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);
cairo_set_source_rgb(c, cl->fg.r, cl->fg.g, cl->fg.b);
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 = cairo_image_surface_get_width(cl->icon),
image_height = cairo_image_surface_get_height(cl->icon),
image_x,
image_y = settings.padding + h/2 - image_height/2;
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 if (settings.icon_position == ICON_RIGHT){
image_x = width - settings.h_padding - image_width;
} else {
LOG_E("Tried to draw icon but icon position is not valid. %s:%d", __FILE__, __LINE__);
}
} // else ICON_RIGHT
cairo_set_source_surface(c, cl->icon, image_x, image_y);
cairo_rectangle(c, image_x, image_y, image_width, image_height);
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,
@ -553,18 +739,19 @@ static struct dimensions layout_render(cairo_surface_t *srf,
bool first,
bool last)
{
const int cl_h = layout_get_height(cl);
int scale = output->get_scale();
const int cl_h = layout_get_height(cl, scale);
int h_text = 0;
pango_layout_get_pixel_size(cl->l, NULL, &h_text);
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);
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);
render_content(c, cl, bg_width, scale);
/* adding frame */
if (first)
@ -590,7 +777,7 @@ static struct dimensions layout_render(cairo_surface_t *srf,
*/
static void calc_window_pos(int width, int height, int *ret_x, int *ret_y)
{
struct screen_info *scr = get_active_screen();
const struct screen_info *scr = output->get_active_screen();
if (ret_x) {
if (settings.geometry.negative_x) {
@ -613,11 +800,12 @@ void draw(void)
{
assert(queues_length_displayed() > 0);
GSList *layouts = create_layouts(x_win_get_context(win));
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, dim.h);
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) {
@ -631,7 +819,7 @@ void draw(void)
}
calc_window_pos(dim.w, dim.h, &dim.x, &dim.y);
x_display_surface(image_surface, win, &dim);
output->display_surface(image_surface, win, &dim);
cairo_surface_destroy(image_surface);
g_slist_free_full(layouts, free_colored_layout);
@ -639,7 +827,17 @@ void draw(void)
void draw_deinit(void)
{
x_win_destroy(win);
x_free();
output->win_destroy(win);
output->deinit();
}
/* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */
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,14 +1,24 @@
#ifndef DUNST_DRAW_H
#define DUNST_DRAW_H
#include "x11/x.h"
extern struct window_x11 *win; // Temporary
#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 tabstop=8 shiftwidth=8 expandtab textwidth=0: */
/* vim: set ft=c tabstop=8 shiftwidth=8 expandtab textwidth=0: */

View File

@ -9,7 +9,6 @@
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <X11/Xlib.h>
#include "dbus.h"
#include "draw.h"
@ -20,12 +19,12 @@
#include "queues.h"
#include "settings.h"
#include "utils.h"
#include "x11/screen.h"
#include "x11/x.h"
#include "output.h"
GMainLoop *mainloop = NULL;
static struct dunst_status status;
static bool setup_done = false;
/* see dunst.h */
void dunst_status(const enum dunst_status_field field,
@ -58,6 +57,15 @@ static gboolean run(void *data);
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);
}
@ -67,8 +75,8 @@ static gboolean run(void *data)
LOG_D("RUN");
dunst_status(S_FULLSCREEN, have_fullscreen_window());
dunst_status(S_IDLE, x_is_idle());
dunst_status(S_FULLSCREEN, output->have_fullscreen_window());
dunst_status(S_IDLE, output->is_idle());
queues_update(status);
@ -77,9 +85,9 @@ static gboolean run(void *data)
if (active) {
// Call draw before showing the window to avoid flickering
draw();
x_win_show(win);
output->win_show(win);
} else {
x_win_hide(win);
output->win_hide(win);
}
if (active) {
@ -87,6 +95,8 @@ static gboolean run(void *data)
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);
@ -196,6 +206,7 @@ int dunst_main(int argc, char *argv[])
// 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);
@ -229,4 +240,4 @@ void print_version(void)
exit(EXIT_SUCCESS);
}
/* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */
/* vim: set ft=c tabstop=8 shiftwidth=8 expandtab textwidth=0: */

View File

@ -41,4 +41,4 @@ void usage(int exit_status);
void print_version(void);
#endif
/* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */
/* vim: set ft=c tabstop=8 shiftwidth=8 expandtab textwidth=0: */

View File

@ -86,6 +86,14 @@ static void pixbuf_data_to_cairo_data(
}
}
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);
@ -110,25 +118,60 @@ cairo_surface_t *gdk_pixbuf_to_cairo_surface(GdkPixbuf *pixbuf)
return icon_surface;
}
GdkPixbuf *icon_pixbuf_scale(GdkPixbuf *pixbuf)
/**
* 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);
int larger = w > h ? w : h;
if (settings.max_icon_size && larger > settings.max_icon_size) {
int scaled_w = settings.max_icon_size;
int scaled_h = settings.max_icon_size;
if (w >= h)
scaled_h = (settings.max_icon_size * h) / w;
else
scaled_w = (settings.max_icon_size * w) / h;
// TODO immediately rescale icon upon scale changes
if (icon_size_clamp(&w, &h)) {
GdkPixbuf *scaled = gdk_pixbuf_scale_simple(
pixbuf,
scaled_w,
scaled_h,
w * dpi_scale,
h * dpi_scale,
GDK_INTERP_BILINEAR);
g_object_unref(pixbuf);
pixbuf = scaled;
@ -137,12 +180,24 @@ GdkPixbuf *icon_pixbuf_scale(GdkPixbuf *pixbuf)
return pixbuf;
}
GdkPixbuf *get_pixbuf_from_file(const char *filename)
GdkPixbuf *get_pixbuf_from_file(const char *filename, int scale)
{
char *path = string_to_path(g_strdup(filename));
GError *error = NULL;
gint w, h;
GdkPixbuf *pixbuf = gdk_pixbuf_new_from_file(path, &error);
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);
@ -153,14 +208,14 @@ GdkPixbuf *get_pixbuf_from_file(const char *filename)
return pixbuf;
}
GdkPixbuf *get_pixbuf_from_icon(const char *iconname)
char *get_path_from_icon_name(const char *iconname)
{
if (STR_EMPTY(iconname))
return NULL;
const char *suffixes[] = { ".svg", ".png", ".xpm", NULL };
GdkPixbuf *pixbuf = 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);
@ -170,7 +225,7 @@ GdkPixbuf *get_pixbuf_from_icon(const char *iconname)
/* absolute path? */
if (iconname[0] == '/' || iconname[0] == '~') {
pixbuf = get_pixbuf_from_file(iconname);
new_name = g_strdup(iconname);
} else {
/* search in icon_path */
char *start = settings.icon_path,
@ -182,41 +237,62 @@ GdkPixbuf *get_pixbuf_from_icon(const char *iconname)
current_folder = g_strndup(start, end - start);
for (const char **suf = suffixes; *suf; suf++) {
maybe_icon_path = g_strconcat(current_folder, "/", iconname, *suf, NULL);
if (is_readable_file(maybe_icon_path))
pixbuf = get_pixbuf_from_file(maybe_icon_path);
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 (pixbuf)
if (new_name)
break;
}
g_free(current_folder);
if (pixbuf)
if (new_name)
break;
start = end + 1;
} while (STR_FULL(end));
if (!pixbuf)
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)
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);
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)
GdkPixbuf *icon_get_for_data(GVariant *data, char **id, int dpi_scale)
{
ASSERT_OR_RET(data, NULL);
ASSERT_OR_RET(id, NULL);
@ -284,7 +360,13 @@ GdkPixbuf *icon_get_for_data(GVariant *data, char **id)
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,
@ -320,7 +402,9 @@ GdkPixbuf *icon_get_for_data(GVariant *data, char **id)
g_free(data_chk);
g_variant_unref(data_variant);
pixbuf = icon_pixbuf_scale(pixbuf, dpi_scale);
return pixbuf;
}
/* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */
/* vim: set ft=c tabstop=8 shiftwidth=8 expandtab textwidth=0: */

View File

@ -8,38 +8,54 @@
cairo_surface_t *gdk_pixbuf_to_cairo_surface(GdkPixbuf *pixbuf);
/**
* 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.
* @return the scaled version of the pixbuf. If scaling wasn't
* necessary, it returns the same pixbuf. Transfers full
* ownership of the reference.
*/
GdkPixbuf *icon_pixbuf_scale(GdkPixbuf *pixbuf);
/** Retrieve an icon by its full filepath.
/** 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);
GdkPixbuf *get_pixbuf_from_file(const char *filename, int scale);
/** Retrieve an icon by its name sent via the notification bus
/**
* 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);
GdkPixbuf *get_pixbuf_from_icon(const char *iconname, int scale);
/** Read an icon from disk and convert it to a GdkPixbuf.
/** 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.
@ -49,12 +65,13 @@ GdkPixbuf *get_pixbuf_from_icon(const char *iconname);
* 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);
GdkPixbuf *icon_get_for_name(const char *name, char **id, int dpi_scale);
/** Convert a GVariant like described in GdkPixbuf
/** 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.
@ -63,10 +80,11 @@ GdkPixbuf *icon_get_for_name(const char *name, char **id);
* 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);
GdkPixbuf *icon_get_for_data(GVariant *data, char **id, int scale);
#endif
/* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */
/* vim: set ft=c tabstop=8 shiftwidth=8 expandtab textwidth=0: */

81
src/input.c Normal file
View File

@ -0,0 +1,81 @@
#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: */

18
src/input.h Normal file
View File

@ -0,0 +1,18 @@
#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: */

View File

@ -74,19 +74,21 @@ static void dunst_log_handler(
gpointer testing)
{
if (testing)
return;
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)
if (log_level < message_level_masked)
return;
#endif
const char *log_level_str =
log_level_to_string(message_level & G_LOG_LEVEL_MASK);
log_level_to_string(message_level_masked);
/* Use stderr for warnings and higher */
if (message_level <= G_LOG_LEVEL_WARNING)
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);
@ -98,4 +100,4 @@ void dunst_log_init(bool testing)
g_log_set_default_handler(dunst_log_handler, (void*)testing);
}
/* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */
/* vim: set ft=c tabstop=8 shiftwidth=8 expandtab textwidth=0: */

View File

@ -45,4 +45,4 @@ void log_set_level_from_string(const char* level);
void dunst_log_init(bool testing);
#endif
/* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */
/* vim: set ft=c tabstop=8 shiftwidth=8 expandtab textwidth=0: */

View File

@ -338,4 +338,4 @@ char *markup_transform(char *str, enum markup_mode markup_mode)
return str;
}
/* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */
/* vim: set ft=c tabstop=8 shiftwidth=8 expandtab textwidth=0: */

View File

@ -48,4 +48,4 @@ void markup_strip_img(char **str, char **urls);
char *markup_transform(char *str, enum markup_mode markup_mode);
#endif
/* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */
/* vim: set ft=c tabstop=8 shiftwidth=8 expandtab textwidth=0: */

View File

@ -23,12 +23,12 @@
static bool is_initialized = false;
static regex_t url_regex;
struct notification_lock {
struct notification *n;
gint64 timeout;
};
static gpointer context_menu_thread(gpointer data);
struct {
GList *locked_notifications;
} menu_ctx;
/**
* Initializes regexes needed for matching.
*
@ -290,9 +290,43 @@ char *invoke_dmenu(const char *dmenu_input)
return ret;
}
/**
* Lock and get all notifications with an action or URL.
**/
static GList *get_actionable_notifications(void)
{
GList *locked_notifications = NULL;
for (const GList *iter = queues_get_displayed(); iter;
iter = iter->next) {
struct notification *n = iter->data;
if (n->urls || g_hash_table_size(n->actions)) {
notification_lock(n);
locked_notifications = g_list_prepend(locked_notifications, n);
}
}
return locked_notifications;
}
/* see menu.h */
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");
return;
}
menu_ctx.locked_notifications = notifications;
GError *err = NULL;
g_thread_unref(g_thread_try_new("dmenu",
context_menu_thread,
@ -305,32 +339,41 @@ void context_menu(void)
}
}
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;
GList *locked_notifications = NULL;
for (const GList *iter = queues_get_displayed(); iter;
iter = iter->next) {
for (GList *iter = menu_ctx.locked_notifications; iter; iter = iter->next) {
struct notification *n = iter->data;
// Reference and lock the notification if we need it
if (n->urls || g_hash_table_size(n->actions)) {
notification_ref(n);
struct notification_lock *nl =
g_malloc(sizeof(struct notification_lock));
nl->n = n;
nl->timeout = n->timeout;
n->timeout = 0;
locked_notifications = g_list_prepend(locked_notifications, nl);
}
char *dmenu_str = notification_dmenu_string(n);
dmenu_input = string_append(dmenu_input, dmenu_str, "\n");
g_free(dmenu_str);
@ -340,26 +383,10 @@ static gpointer context_menu_thread(gpointer data)
}
dmenu_output = invoke_dmenu(dmenu_input);
dispatch_menu_result(dmenu_output);
g_timeout_add(50, context_menu_result_dispatch, dmenu_output);
g_free(dmenu_input);
g_free(dmenu_output);
// unref all notifications
for (GList *iter = locked_notifications;
iter;
iter = iter->next) {
struct notification_lock *nl = iter->data;
struct notification *n = nl->n;
n->timeout = nl->timeout;
g_free(nl);
notification_unref(n);
}
g_list_free(locked_notifications);
return NULL;
}
/* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */
/* vim: set ft=c tabstop=8 shiftwidth=8 expandtab textwidth=0: */

View File

@ -2,6 +2,8 @@
#ifndef DUNST_MENU_H
#define DUNST_MENU_H
#include <glib.h>
/**
* Extract all urls from the given string.
*
@ -16,9 +18,15 @@ void invoke_action(const char *action);
void regex_teardown(void);
/**
* Open the context menu that lets the user select urls/actions/etc.
* 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 tabstop=8 shiftwidth=8 expandtab textwidth=0: */
/* vim: set ft=c tabstop=8 shiftwidth=8 expandtab textwidth=0: */

View File

@ -23,6 +23,7 @@
#include "rules.h"
#include "settings.h"
#include "utils.h"
#include "draw.h"
static void notification_extract_urls(struct notification *n);
static void notification_format_message(struct notification *n);
@ -64,6 +65,7 @@ void notification_print(const struct notification *n)
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);
@ -88,16 +90,21 @@ void notification_print(const struct notification *n)
printf("\t\t\"%s\": \"%s\"\n", (char*)p_key, (char*)p_value);
printf("\t}\n");
}
printf("\tscript: %s\n", n->script);
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]);
}
printf("\n");
}
printf("}\n");
fflush(stdout);
}
/* see notification.h */
void notification_run_script(struct notification *n)
{
if (STR_EMPTY(n->script))
return;
if (n->script_run && !settings.always_run_script)
return;
@ -110,26 +117,54 @@ void notification_run_script(struct notification *n)
const char *urgency = notification_urgency_to_string(n->urgency);
int pid1 = fork();
for(int i = 0; i < n->script_count; i++) {
if (pid1) {
int status;
waitpid(pid1, &status, 0);
} else {
int pid2 = fork();
if (pid2) {
exit(0);
const char *script = n->scripts[i];
if (STR_EMPTY(script))
continue;
int pid1 = fork();
if (pid1) {
int status;
waitpid(pid1, &status, 0);
} else {
int ret = execlp(n->script,
n->script,
appname,
summary,
body,
icon,
urgency,
(char *)NULL);
if (ret != 0) {
LOG_W("Unable to run script: %s", strerror(errno));
// 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));
exit(EXIT_FAILURE);
}
}
@ -158,7 +193,7 @@ const char *notification_urgency_to_string(const enum urgency urgency)
/* see notification.h */
int notification_cmp(const struct notification *a, const struct notification *b)
{
if (a->urgency != b->urgency) {
if (settings.sort && a->urgency != b->urgency) {
return b->urgency - a->urgency;
} else {
return a->id - b->id;
@ -171,8 +206,6 @@ int notification_cmp_data(const void *va, const void *vb, void *data)
struct notification *a = (struct notification *) va;
struct notification *b = (struct notification *) vb;
ASSERT_OR_RET(settings.sort, 1);
return notification_cmp(a, b);
}
@ -185,6 +218,30 @@ bool notification_is_duplicate(const struct notification *a, const struct notifi
&& 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)
{
g_free(p);
@ -224,11 +281,13 @@ void notification_unref(struct notification *n)
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);
@ -236,6 +295,10 @@ void notification_unref(struct notification *n)
notification_private_free(n->priv);
if (n->script_count > 0){
g_free(n->scripts);
}
g_free(n);
}
@ -251,8 +314,7 @@ void notification_icon_replace_path(struct notification *n, const char *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);
n->icon = icon_pixbuf_scale(n->icon);
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)
@ -263,8 +325,7 @@ void notification_icon_replace_data(struct notification *n, GVariant *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);
n->icon = icon_pixbuf_scale(n->icon);
n->icon = icon_get_for_data(new_icon, &n->icon_id, draw_get_scale());
}
/* see notification.h */
@ -325,7 +386,9 @@ struct notification *notification_create(void)
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;
}
@ -378,6 +441,8 @@ void notification_init(struct notification *n)
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);
@ -388,6 +453,12 @@ void notification_init(struct notification *n)
/* 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");
}
/* UPDATE derived fields */
notification_extract_urls(n);
notification_format_message(n);
@ -577,27 +648,47 @@ void notification_update_text_to_render(struct notification *n)
}
/* see notification.h */
void notification_do_action(const struct notification *n)
void notification_do_action(struct notification *n)
{
assert(n->default_action_name);
if (g_hash_table_size(n->actions)) {
if (g_hash_table_contains(n->actions, "default")) {
signal_action_invoked(n, "default");
if (g_hash_table_contains(n->actions, n->default_action_name)) {
signal_action_invoked(n, n->default_action_name);
return;
}
if (g_hash_table_size(n->actions) == 1) {
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;
}
context_menu();
notification_open_context_menu(n);
} else if (n->urls) {
if (strstr(n->urls, "\n"))
context_menu();
else
open_browser(n->urls);
}
}
/* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */
/* 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: */

View File

@ -33,6 +33,7 @@ struct notification_colors {
char *frame;
char *bg;
char *fg;
char *highlight;
};
struct notification {
@ -54,15 +55,18 @@ struct notification {
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 */
gint64 timestamp; /**< arrival time */
gint64 timeout; /**< time to display */
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;
const char *format;
const char *script;
const char **scripts;
int script_count;
struct notification_colors colors;
char *stack_tag; /**< stack notifications by tag */
@ -80,6 +84,7 @@ struct notification {
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;
/* derived fields */
char *msg; /**< formatted message */
@ -137,6 +142,12 @@ 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
@ -185,11 +196,33 @@ void notification_replace_single_field(char **haystack,
void notification_update_text_to_render(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.
* 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(const struct notification *n);
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);
@ -202,4 +235,4 @@ const char *notification_urgency_to_string(const enum urgency urgency);
const char *enum_to_string_fullscreen(enum behavior_fullscreen in);
#endif
/* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */
/* vim: set ft=c tabstop=8 shiftwidth=8 expandtab textwidth=0: */

View File

@ -44,7 +44,7 @@ static int cmdline_find_option(const char *key);
bool string_parse_alignment(const char *s, enum alignment *ret)
{
ASSERT_OR_RET(s, false);
ASSERT_OR_RET(STR_FULL(s), false);
ASSERT_OR_RET(ret, false);
STRING_PARSE_RET("left", ALIGN_LEFT);
@ -56,7 +56,7 @@ bool string_parse_alignment(const char *s, enum alignment *ret)
bool string_parse_ellipsize(const char *s, enum ellipsize *ret)
{
ASSERT_OR_RET(s, false);
ASSERT_OR_RET(STR_FULL(s), false);
ASSERT_OR_RET(ret, false);
STRING_PARSE_RET("start", ELLIPSE_START);
@ -68,7 +68,7 @@ bool string_parse_ellipsize(const char *s, enum ellipsize *ret)
bool string_parse_follow_mode(const char *s, enum follow_mode *ret)
{
ASSERT_OR_RET(s, false);
ASSERT_OR_RET(STR_FULL(s), false);
ASSERT_OR_RET(ret, false);
STRING_PARSE_RET("mouse", FOLLOW_MOUSE);
@ -81,7 +81,7 @@ bool string_parse_follow_mode(const char *s, enum follow_mode *ret)
bool string_parse_fullscreen(const char *s, enum behavior_fullscreen *ret)
{
ASSERT_OR_RET(s, false);
ASSERT_OR_RET(STR_FULL(s), false);
ASSERT_OR_RET(ret, false);
STRING_PARSE_RET("show", FS_SHOW);
@ -93,7 +93,7 @@ bool string_parse_fullscreen(const char *s, enum behavior_fullscreen *ret)
bool string_parse_icon_position(const char *s, enum icon_position *ret)
{
ASSERT_OR_RET(s, false);
ASSERT_OR_RET(STR_FULL(s), false);
ASSERT_OR_RET(ret, false);
STRING_PARSE_RET("left", ICON_LEFT);
@ -103,9 +103,21 @@ bool string_parse_icon_position(const char *s, enum icon_position *ret)
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(s, false);
ASSERT_OR_RET(STR_FULL(s), false);
ASSERT_OR_RET(ret, false);
STRING_PARSE_RET("strip", MARKUP_STRIP);
@ -118,22 +130,46 @@ bool string_parse_markup_mode(const char *s, enum markup_mode *ret)
bool string_parse_mouse_action(const char *s, enum mouse_action *ret)
{
ASSERT_OR_RET(s, false);
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_sepcolor(const char *s, struct separator_color_data *ret)
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});
@ -146,7 +182,7 @@ bool string_parse_sepcolor(const char *s, struct separator_color_data *ret)
bool string_parse_urgency(const char *s, enum urgency *ret)
{
ASSERT_OR_RET(s, false);
ASSERT_OR_RET(STR_FULL(s), false);
ASSERT_OR_RET(ret, false);
STRING_PARSE_RET("low", URG_LOW);
@ -156,6 +192,18 @@ bool string_parse_urgency(const char *s, enum urgency *ret)
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)
{
for (int i = 0; i < section_count; i++) {
@ -248,6 +296,15 @@ 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);
@ -463,6 +520,17 @@ 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);
@ -562,6 +630,23 @@ 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,
@ -646,4 +731,4 @@ const char *cmdline_create_usage(void)
return usage_str;
}
/* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */
/* vim: set ft=c tabstop=8 shiftwidth=8 expandtab textwidth=0: */

View File

@ -14,15 +14,19 @@ 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);
@ -33,6 +37,7 @@ 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);
@ -54,6 +59,11 @@ 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,
@ -77,4 +87,4 @@ int option_get_bool(const char *ini_section,
const char *next_section(const char *section);
#endif
/* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */
/* vim: set ft=c tabstop=8 shiftwidth=8 expandtab textwidth=0: */

96
src/output.c Normal file
View File

@ -0,0 +1,96 @@
#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: */

61
src/output.h Normal file
View File

@ -0,0 +1,61 @@
#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

@ -25,6 +25,7 @@
#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 */
@ -45,7 +46,7 @@ void queues_init(void)
}
/* see queues.h */
const GList *queues_get_displayed(void)
GList *queues_get_displayed(void)
{
return g_queue_peek_head_link(displayed);
}
@ -167,19 +168,6 @@ int queues_notification_insert(struct notification *n)
LOG_M("Skipping notification: '%s' '%s'", n->body, n->summary);
return 0;
}
/* Do not insert the message if it's a command */
if (STR_EQ("DUNST_COMMAND_PAUSE", n->summary)) {
dunst_status(S_RUNNING, false);
return 0;
}
if (STR_EQ("DUNST_COMMAND_RESUME", n->summary)) {
dunst_status(S_RUNNING, true);
return 0;
}
if (STR_EQ("DUNST_COMMAND_TOGGLE", n->summary)) {
dunst_status(S_RUNNING, !dunst_status_get().running);
return 0;
}
bool inserted = false;
if (n->id != 0) {
@ -389,6 +377,19 @@ void queues_update(struct dunst_status status)
struct notification *n = iter->data;
nextiter = iter->next;
if (notification_is_locked(n)) {
iter = nextiter;
continue;
}
if (n->marked_for_closure) {
queues_notification_close(n, n->marked_for_closure);
n->marked_for_closure = 0;
iter = nextiter;
continue;
}
if (queues_notification_is_finished(n, status)){
queues_notification_close(n, REASON_TIME);
iter = nextiter;
@ -506,6 +507,27 @@ gint64 queues_get_next_datachange(gint64 time)
return sleep != G_MAXINT64 ? sleep : -1;
}
/* see queues.h */
struct notification* queues_get_by_id(int id)
{
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;
}
/**
* Helper function for queues_teardown() to free a single notification
*
@ -527,4 +549,6 @@ void queues_teardown(void)
g_queue_free_full(waiting, teardown_notification);
waiting = NULL;
}
/* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */
/* vim: set ft=c tabstop=8 shiftwidth=8 expandtab textwidth=0: */

View File

@ -24,7 +24,7 @@ void queues_init(void);
*
* @return read only list of notifications
*/
const GList *queues_get_displayed(void);
GList *queues_get_displayed(void);
/**
* Get the highest notification in line
@ -145,6 +145,17 @@ void queues_update(struct dunst_status status);
*/
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
*
* @param id the id searched for.
*
* @return the `id` notification or NULL
*/
struct notification* queues_get_by_id(int id);
/**
* Remove all notifications from all list and free the notifications
*
@ -153,4 +164,4 @@ gint64 queues_get_next_datachange(gint64 time);
void queues_teardown(void);
#endif
/* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */
/* vim: set ft=c tabstop=8 shiftwidth=8 expandtab textwidth=0: */

View File

@ -26,6 +26,10 @@ void rule_apply(struct rule *r, struct notification *n)
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)
@ -38,14 +42,22 @@ void rule_apply(struct rule *r, struct notification *n)
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->format)
n->format = r->format;
if (r->script)
n->script = r->script;
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);
@ -74,7 +86,7 @@ struct rule *rule_new(void)
r->urgency = URG_NONE;
r->fullscreen = FS_NULL;
r->markup = MARKUP_NULL;
r->history_ignore = false;
r->history_ignore = -1;
r->match_transient = -1;
r->set_transient = -1;
r->skip_display = -1;
@ -102,4 +114,4 @@ bool rule_matches_notification(struct rule *r, struct notification *n)
&& rule_field_matches_string(n->category, r->category)
&& rule_field_matches_string(n->stack_tag, r->stack_tag);
}
/* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */
/* vim: set ft=c tabstop=8 shiftwidth=8 expandtab textwidth=0: */

View File

@ -23,6 +23,7 @@ struct rule {
/* actions */
gint64 timeout;
enum urgency urgency;
char *action_name;
enum markup_mode markup;
int history_ignore;
int match_transient;
@ -31,6 +32,7 @@ struct rule {
char *new_icon;
char *fg;
char *bg;
char *highlight;
char *fc;
const char *format;
const char *script;
@ -52,4 +54,4 @@ void rule_apply_all(struct notification *n);
bool rule_matches_notification(struct rule *r, struct notification *n);
#endif
/* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */
/* vim: set ft=c tabstop=8 shiftwidth=8 expandtab textwidth=0: */

View File

@ -13,6 +13,7 @@
#include "rules.h"
#include "utils.h"
#include "x11/x.h"
#include "output.h"
#include "../config.h"
@ -53,6 +54,10 @@ static FILE *xdg_config(const char *filename)
g_free(path);
}
if (!f) {
f = fopen("/etc/dunst/dunstrc", "r");
}
return f;
}
@ -118,6 +123,12 @@ 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,
@ -176,6 +187,11 @@ 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(
@ -204,6 +220,22 @@ 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,
@ -338,6 +370,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,
@ -350,6 +388,49 @@ void load_settings(char *cmdline_config_path)
"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",
@ -411,7 +492,7 @@ void load_settings(char *cmdline_config_path)
{
char *c = option_get_string(
"global",
"icon_position", "-icon_position", "off",
"icon_position", "-icon_position", "left",
"Align icons left/right/off"
);
@ -423,6 +504,43 @@ void load_settings(char *cmdline_config_path)
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,
@ -508,48 +626,42 @@ void load_settings(char *cmdline_config_path)
}
{
char *c = option_get_string(
char **c = option_get_list(
"global",
"mouse_left_click", "-left_click", NULL,
"mouse_left_click", "-mouse_left_click", NULL,
"Action of Left click event"
);
if (!string_parse_mouse_action(c, &settings.mouse_left_click)) {
if (c)
LOG_W("Unknown mouse action value: '%s'", c);
if (!string_parse_mouse_action_list(c, &settings.mouse_left_click)) {
settings.mouse_left_click = defaults.mouse_left_click;
}
g_free(c);
free_string_array(c);
}
{
char *c = option_get_string(
char **c = option_get_list(
"global",
"mouse_middle_click", "-mouse_middle_click", NULL,
"Action of middle click event"
);
if (!string_parse_mouse_action(c, &settings.mouse_middle_click)) {
if (c)
LOG_W("Unknown mouse action value: '%s'", c);
if (!string_parse_mouse_action_list(c, &settings.mouse_middle_click)) {
settings.mouse_middle_click = defaults.mouse_middle_click;
}
g_free(c);
free_string_array(c);
}
{
char *c = option_get_string(
char **c = option_get_list(
"global",
"mouse_right_click", "-mouse_right_click", NULL,
"Action of right click event"
);
if (!string_parse_mouse_action(c, &settings.mouse_right_click)) {
if (c)
LOG_W("Unknown mouse action value: '%s'", c);
if (!string_parse_mouse_action_list(c, &settings.mouse_right_click)) {
settings.mouse_right_click = defaults.mouse_right_click;
}
g_free(c);
free_string_array(c);
}
settings.colors_low.bg = option_get_string(
@ -564,6 +676,12 @@ void load_settings(char *cmdline_config_path)
"Foreground color for notifications with low urgency"
);
settings.colors_low.highlight = 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,
@ -594,6 +712,12 @@ void load_settings(char *cmdline_config_path)
"Foreground color for notifications with normal urgency"
);
settings.colors_norm.highlight = 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,
@ -624,6 +748,12 @@ void load_settings(char *cmdline_config_path)
"Foreground color for notifications with ciritical urgency"
);
settings.colors_crit.highlight = 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,
@ -732,10 +862,12 @@ void load_settings(char *cmdline_config_path)
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);
@ -767,4 +899,4 @@ void load_settings(char *cmdline_config_path)
}
#endif
}
/* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */
/* vim: set ft=c tabstop=8 shiftwidth=8 expandtab textwidth=0: */

View File

@ -4,6 +4,10 @@
#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"
@ -11,9 +15,20 @@
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 follow_mode { FOLLOW_NONE, FOLLOW_MOUSE, FOLLOW_KEYBOARD };
enum mouse_action { MOUSE_NONE, MOUSE_DO_ACTION, MOUSE_CLOSE_CURRENT, MOUSE_CLOSE_ALL };
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 */
struct separator_color_data {
enum separator_color type;
@ -58,6 +73,7 @@ struct settings {
int history_length;
int show_indicators;
int word_wrap;
int ignore_dbusclose;
enum ellipsize ellipsize;
int ignore_newline;
int line_height;
@ -65,6 +81,7 @@ struct settings {
int separator_height;
int padding;
int h_padding;
int text_icon_padding;
struct separator_color_data sep_color;
int frame_width;
char *frame_color;
@ -75,6 +92,8 @@ struct settings {
char *browser;
char **browser_cmd;
enum icon_position icon_position;
enum vertical_alignment vertical_alignment;
int min_icon_size;
int max_icon_size;
char *icon_path;
enum follow_mode f_mode;
@ -84,10 +103,17 @@ struct settings {
struct keyboard_shortcut history_ks;
struct 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;
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;
};
extern struct settings settings;
@ -95,4 +121,4 @@ extern struct settings settings;
void load_settings(char *cmdline_config_path);
#endif
/* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */
/* vim: set ft=c tabstop=8 shiftwidth=8 expandtab textwidth=0: */

View File

@ -5,12 +5,25 @@
#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 "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)
{
@ -133,12 +146,25 @@ 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(getenv("HOME"), "/", NULL);
char *home = g_strconcat(user_get_home(), "/", NULL);
string = string_replace_at(string, 0, 2, home);
@ -204,4 +230,33 @@ gint64 time_monotonic_now(void)
#endif
return S2US(tv_now.tv_sec) + tv_now.tv_nsec / 1000;
}
/* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */
/* 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: */

View File

@ -4,6 +4,7 @@
#include <glib.h>
#include <string.h>
#include <stdbool.h>
//! Test if a string is NULL or empty
#define STR_EMPTY(s) (!s || (*s == '\0'))
@ -22,6 +23,15 @@
//! 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.
*
@ -82,6 +92,17 @@ char *string_strip_quotes(const char *value);
*/
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);
/**
* Replace tilde and path-specific values with its equivalents
*
@ -110,5 +131,23 @@ gint64 string_to_time(const char *string);
*/
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 tabstop=8 shiftwidth=8 expandtab textwidth=0: */
/* vim: set ft=c tabstop=8 shiftwidth=8 expandtab textwidth=0: */

View File

@ -0,0 +1,192 @@
/*
* 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

@ -0,0 +1,42 @@
/*
* 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__ */

150
src/wayland/pool-buffer.c Normal file
View File

@ -0,0 +1,150 @@
#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: */

26
src/wayland/pool-buffer.h Normal file
View File

@ -0,0 +1,26 @@
#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

@ -0,0 +1,233 @@
/* 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

@ -0,0 +1,68 @@
/* 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

@ -0,0 +1,49 @@
<?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

@ -0,0 +1,611 @@
/* 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

@ -0,0 +1,106 @@
/* 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

@ -0,0 +1,270 @@
<?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

@ -0,0 +1,559 @@
/* 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

@ -0,0 +1,91 @@
/* 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

@ -0,0 +1,285 @@
<?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

@ -0,0 +1,409 @@
/* 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

@ -0,0 +1,78 @@
/* 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

@ -0,0 +1,181 @@
/* 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,
};

917
src/wayland/wl.c Normal file
View File

@ -0,0 +1,917 @@
#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: */

32
src/wayland/wl.h Normal file
View File

@ -0,0 +1,32 @@
#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

@ -69,12 +69,12 @@ static double screen_dpi_get_from_xft(void)
return screen_dpi_xft_cache;
}
static double screen_dpi_get_from_monitor(struct screen_info *scr)
static double screen_dpi_get_from_monitor(const struct screen_info *scr)
{
return (double)scr->h * 25.4 / (double)scr->mmh;
}
double screen_dpi_get(struct screen_info *scr)
double screen_dpi_get(const struct screen_info *scr)
{
if ( ! settings.force_xinerama
&& settings.per_monitor_dpi)
@ -156,6 +156,7 @@ void randr_update(void)
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]);
}
XRRFreeMonitors(m);
@ -300,28 +301,52 @@ bool window_is_fullscreen(Window window)
* Select the screen on which the Window
* should be displayed.
*/
struct screen_info *get_active_screen(void)
const struct screen_info *get_active_screen(void)
{
int ret = 0;
if (settings.monitor > 0 && settings.monitor < screens_len) {
ret = settings.monitor;
goto sc_cleanup;
}
x_follow_setup_error_handler();
bool force_follow_mouse = false;
if (settings.f_mode == FOLLOW_NONE) {
ret = XDefaultScreen(xctx.dpy);
if (settings.monitor >= 0 && settings.monitor < screens_len) {
ret = settings.monitor;
}
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_MOUSE) {
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) {
int dummy;
unsigned int dummy_ui;
Window dummy_win;
@ -337,21 +362,6 @@ 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. Fall back 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)) {
@ -363,7 +373,7 @@ struct screen_info *get_active_screen(void)
goto sc_cleanup;
/* something seems to be wrong. Fall back to default */
ret = XDefaultScreen(xctx.dpy);
ret = 0;
goto sc_cleanup;
}
sc_cleanup:
@ -379,32 +389,13 @@ sc_cleanup:
*/
static Window get_focused_window(void)
{
Window focused = 0;
Atom type;
int format;
unsigned long nitems, bytes_after;
unsigned char *prop_return = NULL;
Window root = RootWindow(xctx.dpy, DefaultScreen(xctx.dpy));
Atom netactivewindow =
XInternAtom(xctx.dpy, "_NET_ACTIVE_WINDOW", false);
Window focused;
int 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);
}
XGetInputFocus(xctx.dpy, &focused, &ignored);
if (focused == None || focused == PointerRoot)
focused = 0;
return focused;
}
@ -433,4 +424,4 @@ static int FollowXErrorHandler(Display *display, XErrorEvent *e)
return 0;
}
/* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */
/* vim: set ft=c tabstop=8 shiftwidth=8 expandtab textwidth=0: */

View File

@ -7,21 +7,12 @@
#define INRECT(x,y,rx,ry,rw,rh) ((x) >= (rx) && (x) < (rx)+(rw) && (y) >= (ry) && (y) < (ry)+(rh))
struct screen_info {
int id;
int x;
int y;
unsigned int h;
unsigned int mmh;
unsigned int w;
};
void init_screens(void);
void screen_dpi_xft_cache_purge(void);
bool screen_check_event(XEvent *ev);
struct screen_info *get_active_screen(void);
double screen_dpi_get(struct screen_info *scr);
const struct screen_info *get_active_screen(void);
double screen_dpi_get(const struct screen_info *scr);
/**
* Find the currently focused window and check if it's in
@ -45,4 +36,4 @@ bool have_fullscreen_window(void);
bool window_is_fullscreen(Window window);
#endif
/* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */
/* vim: set ft=c tabstop=8 shiftwidth=8 expandtab textwidth=0: */

View File

@ -19,6 +19,7 @@
#include <X11/Xlib.h>
#include <X11/Xresource.h>
#include <X11/Xutil.h>
#include <linux/input-event-codes.h>
#include "../dbus.h"
#include "../draw.h"
@ -30,6 +31,7 @@
#include "../queues.h"
#include "../settings.h"
#include "../utils.h"
#include "../input.h"
#include "screen.h"
@ -67,8 +69,10 @@ static int x_shortcut_tear_down_error_handler(void);
static void setopacity(Window win, unsigned long opacity);
static void x_handle_click(XEvent ev);
static void x_win_move(struct window_x11 *win, int x, int y, int width, int height)
static void x_win_move(window winptr, int x, int y, int width, int height)
{
struct window_x11 *win = (struct window_x11*)winptr;
/* move and resize */
if (x != win->dim.x || y != win->dim.y) {
XMoveWindow(xctx.dpy, win->xwin, x, y);
@ -85,97 +89,99 @@ static void x_win_move(struct window_x11 *win, int x, int y, int width, int heig
}
}
static void x_win_round_corners(struct window_x11 *win, const int rad)
static void x_win_corners_shape(struct window_x11 *win, const int rad)
{
const int width = win->dim.w;
const int height = win->dim.h;
const int dia = 2 * rad;
const int degrees = 64; // the factor to convert degrees to XFillArc's angle param
Pixmap mask = XCreatePixmap(xctx.dpy, win->xwin, width, height, 1);
XGCValues xgcv;
Pixmap mask;
cairo_surface_t * cxbm;
cairo_t * cr;
Screen * scr;
GC shape_gc = XCreateGC(xctx.dpy, mask, 0, &xgcv);
mask = XCreatePixmap(xctx.dpy, win->xwin, width, height, 1);
scr = ScreenOfDisplay(xctx.dpy, win->cur_screen);
cxbm = cairo_xlib_surface_create_for_bitmap(xctx.dpy, mask, scr, width, height);
cr = cairo_create(cxbm);
XSetForeground(xctx.dpy, shape_gc, 0);
XFillRectangle(xctx.dpy,
mask,
shape_gc,
0,
0,
width,
height);
cairo_set_antialias(cr, CAIRO_ANTIALIAS_NONE);
cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE);
XSetForeground(xctx.dpy, shape_gc, 1);
cairo_set_source_rgba(cr, 0, 0, 0, 0);
cairo_paint(cr);
cairo_set_source_rgba(cr, 1, 1, 1, 1);
/* To mark all pixels, which should get exposed, we
* use a circle for every corner and two overlapping rectangles */
unsigned const int centercoords[] = {
0, 0,
width - dia - 1, 0,
0, height - dia - 1,
width - dia - 1, height - dia - 1,
};
draw_rounded_rect(cr, 0, 0,
width, height,
rad, 1,
true, true);
cairo_fill(cr);
for (int i = 0; i < sizeof(centercoords)/sizeof(unsigned int); i = i+2) {
XFillArc(xctx.dpy,
mask,
shape_gc,
centercoords[i],
centercoords[i+1],
dia,
dia,
degrees * 0,
degrees * 360);
}
XFillRectangle(xctx.dpy,
mask,
shape_gc,
rad,
0,
width-dia,
height);
XFillRectangle(xctx.dpy,
mask,
shape_gc,
0,
rad,
width,
height-dia);
cairo_show_page(cr);
cairo_destroy(cr);
cairo_surface_flush(cxbm);
cairo_surface_destroy(cxbm);
XShapeCombineMask(xctx.dpy, win->xwin, ShapeBounding, 0, 0, mask, ShapeSet);
XFreeGC(xctx.dpy, shape_gc);
XFreePixmap(xctx.dpy, mask);
XShapeSelectInput(xctx.dpy,
win->xwin, ShapeNotifyMask);
}
void x_display_surface(cairo_surface_t *srf, struct window_x11 *win, const struct dimensions *dim)
static void x_win_corners_unshape(window winptr)
{
struct window_x11 *win = (struct window_x11*)winptr;
XRectangle rect = {
.x = 0,
.y = 0,
.width = win->dim.w,
.height = win->dim.h };
XShapeCombineRectangles(xctx.dpy, win->xwin, ShapeBounding, 0, 0, &rect, 1, ShapeSet, 1);
XShapeSelectInput(xctx.dpy,
win->xwin, ShapeNotifyMask);
}
static bool x_win_composited(struct window_x11 *win)
{
char astr[sizeof("_NET_WM_CM_S") / sizeof(char) + 8];
Atom cm_sel;
sprintf(astr, "_NET_WM_CM_S%i", win->cur_screen);
cm_sel = XInternAtom(xctx.dpy, astr, true);
if (cm_sel == None) {
return false;
} else {
return XGetSelectionOwner(xctx.dpy, cm_sel) != None;
}
}
void x_display_surface(cairo_surface_t *srf, window winptr, const struct dimensions *dim)
{
struct window_x11 *win = (struct window_x11*)winptr;
x_win_move(win, dim->x, dim->y, dim->w, dim->h);
cairo_xlib_surface_set_size(win->root_surface, dim->w, dim->h);
XClearWindow(xctx.dpy, win->xwin);
cairo_set_source_surface(win->c_ctx, srf, 0, 0);
cairo_paint(win->c_ctx);
cairo_show_page(win->c_ctx);
if (settings.corner_radius != 0)
x_win_round_corners(win, dim->corner_radius);
if (settings.corner_radius != 0 && ! x_win_composited(win))
x_win_corners_shape(win, dim->corner_radius);
else
x_win_corners_unshape(win);
XFlush(xctx.dpy);
}
bool x_win_visible(struct window_x11 *win)
cairo_t* x_win_get_context(window winptr)
{
return win->visible;
}
cairo_t* x_win_get_context(struct window_x11 *win)
{
return win->c_ctx;
return ((struct window_x11*)win)->c_ctx;
}
static void setopacity(Window win, unsigned long opacity)
@ -273,7 +279,7 @@ gboolean x_mainloop_fd_dispatch(GSource *source, GSourceFunc callback, gpointer
struct window_x11 *win = ((struct x11_source*) source)->win;
bool fullscreen_now;
struct screen_info *scr;
const struct screen_info *scr;
XEvent ev;
unsigned int state;
while (XPending(xctx.dpy) > 0) {
@ -394,57 +400,47 @@ bool x_is_idle(void)
return xctx.screensaver_info->idle > settings.idle_threshold / 1000;
}
/*
* Convert x button code to linux event code
* Returns 0 if button is not recognized.
*/
static unsigned int x_mouse_button_to_linux_event_code(unsigned int x_button)
{
switch (x_button) {
case Button1:
return BTN_LEFT;
case Button2:
return BTN_MIDDLE;
case Button3:
return BTN_RIGHT;
default:
LOG_W("Unsupported mouse button: '%d'", x_button);
return 0;
}
}
/* TODO move to x_mainloop_* */
/*
* Handle incoming mouse click events
*/
static void x_handle_click(XEvent ev)
{
enum mouse_action act;
switch (ev.xbutton.button) {
case Button1:
act = settings.mouse_left_click;
break;
case Button2:
act = settings.mouse_middle_click;
break;
case Button3:
act = settings.mouse_right_click;
break;
default:
LOG_W("Unsupported mouse button: '%d'", ev.xbutton.button);
return;
}
if (act == MOUSE_CLOSE_ALL) {
queues_history_push_all();
unsigned int linux_code = x_mouse_button_to_linux_event_code(ev.xbutton.button);
if (linux_code == 0) {
return;
}
if (act == MOUSE_DO_ACTION || act == MOUSE_CLOSE_CURRENT) {
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 (ev.xbutton.y > y && ev.xbutton.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)
queues_notification_close(n, REASON_USER);
else
notification_do_action(n);
}
bool button_state;
if(ev.type == ButtonRelease) {
button_state = false; // button is up
} else {
// this shouldn't happen, because this function
// is only called when it'a a ButtonRelease event
button_state = true; // button is down
}
input_handle_click(linux_code, button_state, ev.xbutton.x, ev.xbutton.y);
}
void x_free(void)
@ -500,14 +496,16 @@ static void XRM_update_db(void)
/*
* Setup X11 stuff
*/
void x_setup(void)
bool x_setup(void)
{
/* initialize xctx.dc, font, keyboard, colors */
if (!setlocale(LC_CTYPE, "") || !XSupportsLocale())
LOG_W("No locale support");
if (!(xctx.dpy = XOpenDisplay(NULL))) {
DIE("Cannot open X11 display.");
LOG_W("Cannot open X11 display.");
return false;
}
x_shortcut_init(&settings.close_ks);
@ -526,10 +524,12 @@ void x_setup(void)
xctx.screensaver_info = XScreenSaverAllocInfo();
XrmInitialize();
XRM_update_db();
init_screens();
x_shortcut_grab(&settings.history_ks);
XrmInitialize();
return true;
}
struct geometry x_parse_geometry(const char *geom_str)
@ -634,22 +634,37 @@ GSource* x_win_reg_source(struct window_x11 *win)
/*
* Setup the window
*/
struct window_x11 *x_win_create(void)
window x_win_create(void)
{
struct window_x11 *win = g_malloc0(sizeof(struct window_x11));
Window root;
int scr_n;
int depth;
Visual * vis;
XVisualInfo vi;
XSetWindowAttributes wa;
root = RootWindow(xctx.dpy, DefaultScreen(xctx.dpy));
scr_n = DefaultScreen(xctx.dpy);
root = RootWindow(xctx.dpy, scr_n);
if (XMatchVisualInfo(xctx.dpy, scr_n, 32, TrueColor, &vi)) {
vis = vi.visual;
depth = vi.depth;
} else {
vis = DefaultVisual(xctx.dpy, scr_n);
depth = DefaultDepth(xctx.dpy, scr_n);
}
wa.override_redirect = true;
wa.background_pixmap = ParentRelative;
wa.background_pixmap = None;
wa.background_pixel = 0;
wa.border_pixel = 0;
wa.colormap = XCreateColormap(xctx.dpy, root, vis, AllocNone);
wa.event_mask =
ExposureMask | KeyPressMask | VisibilityChangeMask |
ButtonReleaseMask | FocusChangeMask| StructureNotifyMask;
struct screen_info *scr = get_active_screen();
const struct screen_info *scr = get_active_screen();
win->xwin = XCreateWindow(xctx.dpy,
root,
scr->x,
@ -657,10 +672,10 @@ struct window_x11 *x_win_create(void)
scr->w,
1,
0,
DefaultDepth(xctx.dpy, DefaultScreen(xctx.dpy)),
depth,
CopyFromParent,
DefaultVisual(xctx.dpy, DefaultScreen(xctx.dpy)),
CWOverrideRedirect | CWBackPixmap | CWEventMask,
vis,
CWOverrideRedirect | CWBackPixmap | CWBackPixel | CWBorderPixel | CWColormap | CWEventMask,
&wa);
x_set_wm(win->xwin);
@ -671,7 +686,7 @@ struct window_x11 *x_win_create(void)
(0xffffffff / 100)));
win->root_surface = cairo_xlib_surface_create(xctx.dpy, win->xwin,
DefaultVisual(xctx.dpy, 0),
vis,
WIDTH, HEIGHT);
win->c_ctx = cairo_create(win->root_surface);
@ -691,11 +706,13 @@ struct window_x11 *x_win_create(void)
}
XSelectInput(xctx.dpy, root, root_event_mask);
return win;
return (window)win;
}
void x_win_destroy(struct window_x11 *win)
void x_win_destroy(window winptr)
{
struct window_x11 *win = (struct window_x11*)winptr;
g_source_destroy(win->esrc);
g_source_unref(win->esrc);
@ -709,12 +726,15 @@ void x_win_destroy(struct window_x11 *win)
/*
* Show the window and grab shortcuts.
*/
void x_win_show(struct window_x11 *win)
void x_win_show(window winptr)
{
struct window_x11 *win = (struct window_x11*)winptr;
/* window is already mapped or there's nothing to show */
if (win->visible)
return;
x_shortcut_grab(&settings.close_ks);
x_shortcut_grab(&settings.close_all_ks);
x_shortcut_grab(&settings.context_ks);
@ -736,13 +756,17 @@ void x_win_show(struct window_x11 *win)
XMapRaised(xctx.dpy, win->xwin);
win->visible = true;
x_display_surface(win->root_surface, win, &win->dim);
}
/*
* Hide the window and ungrab unused keyboard_shortcuts
*/
void x_win_hide(struct window_x11 *win)
void x_win_hide(window winptr)
{
LOG_I("X11: Hiding window");
struct window_x11 *win = (struct window_x11*)winptr;
ASSERT_OR_RET(win->visible,);
x_shortcut_ungrab(&settings.close_ks);
@ -918,4 +942,8 @@ static void x_shortcut_init(struct keyboard_shortcut *ks)
g_free(str_begin);
}
/* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */
int x_get_scale(void) {
return 1;
}
/* vim: set ft=c tabstop=8 shiftwidth=8 expandtab textwidth=0: */

View File

@ -11,6 +11,8 @@
#include <X11/X.h>
#include <X11/Xlib.h>
#include "../output.h"
#include "screen.h"
struct keyboard_shortcut {
@ -24,48 +26,31 @@ struct keyboard_shortcut {
// Cyclical dependency
#include "../settings.h"
struct window_x11;
struct dimensions {
int x;
int y;
int w;
int h;
int corner_radius;
};
struct x_context {
Display *dpy;
XScreenSaverInfo *screensaver_info;
};
struct color {
double r;
double g;
double b;
};
extern struct x_context xctx;
/* window */
struct window_x11 *x_win_create(void);
void x_win_destroy(struct window_x11 *win);
window x_win_create(void);
void x_win_destroy(window);
void x_win_show(struct window_x11 *win);
void x_win_hide(struct window_x11 *win);
void x_win_show(window);
void x_win_hide(window);
void x_display_surface(cairo_surface_t *srf, struct window_x11 *win, const struct dimensions *dim);
void x_display_surface(cairo_surface_t *srf, window, const struct dimensions *dim);
bool x_win_visible(struct window_x11 *win);
cairo_t* x_win_get_context(struct window_x11 *win);
cairo_t* x_win_get_context(window);
/* X misc */
bool x_is_idle(void);
void x_setup(void);
bool x_setup(void);
void x_free(void);
struct geometry x_parse_geometry(const char *geom_str);
int x_get_scale(void);
#endif
/* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */
/* vim: set ft=c tabstop=8 shiftwidth=8 expandtab textwidth=0: */

View File

@ -27,6 +27,16 @@
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

@ -6,6 +6,7 @@
#include <gdk-pixbuf/gdk-pixbuf.h>
#include <gio/gio.h>
#include "helpers.h"
#include "queues.h"
extern const char *base;
@ -252,33 +253,13 @@ bool dbus_notification_fire(struct dbus_notification *n, uint *id)
void dbus_notification_set_raw_image(struct dbus_notification *n_dbus, const char *path)
{
GdkPixbuf *pb = gdk_pixbuf_new_from_file(path, NULL);
if (!pb)
GVariant *hint = notification_setup_raw_image(path);
if (!hint)
return;
GVariant *hint_data = g_variant_new_from_data(
G_VARIANT_TYPE("ay"),
gdk_pixbuf_read_pixels(pb),
gdk_pixbuf_get_byte_length(pb),
TRUE,
(GDestroyNotify) g_object_unref,
g_object_ref(pb));
GVariant *hint = g_variant_new(
"(iiibii@ay)",
gdk_pixbuf_get_width(pb),
gdk_pixbuf_get_height(pb),
gdk_pixbuf_get_rowstride(pb),
gdk_pixbuf_get_has_alpha(pb),
gdk_pixbuf_get_bits_per_sample(pb),
gdk_pixbuf_get_n_channels(pb),
hint_data);
g_hash_table_insert(n_dbus->hints,
g_strdup("image-data"),
g_variant_ref_sink(hint));
g_object_unref(pb);
}
/////// TESTS
@ -660,6 +641,34 @@ TEST test_hint_raw_image(void)
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;
@ -677,6 +686,7 @@ TEST test_server_caps(enum markup_mode markup)
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)
@ -790,6 +800,12 @@ TEST assert_methodlists_sorted(void)
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();
}
@ -821,6 +837,7 @@ gpointer run_threaded_tests(gpointer data)
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);
@ -840,6 +857,10 @@ SUITE(suite_dbus)
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);

View File

@ -1,6 +1,15 @@
#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};

View File

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

View File

@ -0,0 +1,10 @@
[global]
font = Monospace 8
allow_markup = yes
format = "<b>%s</b>\n%b"
geometry = "0x5-30+20"
icon_position = left
progress_bar_min_width = 100
progress_bar_max_width = 200
progress_bar_frame_width = 5
progress_bar_height = 30

View File

@ -80,15 +80,17 @@ function markup {
killall dunst
../../dunst -config dunstrc.markup "200x0+10+10" &
../../dunstify -a "dunst tester" "Markup Tests" -u "c"
../../dunstify -a "dunst tester" "<b>bold</b> <i>italic</i>"
../../dunstify -a "dunst tester" "<b>broken markup</i>"
../../dunstify -a "dunst tester" "There should be no markup in the title" -u "c"
../../dunstify -a "dunst tester" "Title" "<b>bold</b> <i>italic</i>"
../../dunstify -a "dunst tester" "Title" "<a href="github.com"> Github link </a>"
../../dunstify -a "dunst tester" "Title" "<b>broken markup</i>"
keypress
killall dunst
../../dunst -config dunstrc.nomarkup "200x0+10+10" &
../../dunstify -a "dunst tester" -u c "NO Markup Tests"
../../dunstify -a "dunst tester" "<b>bold</b><i>italic</i>"
../../dunstify -a "dunst tester" "<b>broken markup</i>"
../../dunstify -a "dunst tester" -u c "No markup Tests"
../../dunstify -a "dunst tester" "<b>Title</b>" "<b>bold</b><i>italic</i>"
../../dunstify -a "dunst tester" "<b>Title</b>" "<b>broken markup</i>"
keypress
}
@ -160,13 +162,13 @@ function geometry {
killall dunst
../../dunst -config dunstrc.default -geom "-300x1" &
../../dunstify -a "dunst tester" -u c "-300x1"
../../dunstify -a "dunst tester" -u c -- "-300x1"
basic_notifications
keypress
killall dunst
../../dunst -config dunstrc.default -geom "-300x1-20-20" &
../../dunstify -a "dunst tester" -u c "-300x1-20-20"
../../dunstify -a "dunst tester" -u c -- "-300x1-20-20"
basic_notifications
keypress
@ -177,12 +179,35 @@ function geometry {
keypress
}
function progress_bar {
killall dunst
../../dunst -config dunstrc.default &
../../dunstify -h int:value:0 -a "dunst tester" -u c "Progress bar 0%: "
../../dunstify -h int:value:33 -a "dunst tester" -u c "Progress bar 33%: "
../../dunstify -h int:value:66 -a "dunst tester" -u c "Progress bar 66%: "
../../dunstify -h int:value:100 -a "dunst tester" -u c "Progress bar 100%: "
keypress
killall dunst
../../dunst -config dunstrc.default &
../../dunstify -h int:value:33 -a "dunst tester" -u l "Low priority: "
../../dunstify -h int:value:33 -a "dunst tester" -u n "Normal priority: "
../../dunstify -h int:value:33 -a "dunst tester" -u c "Critical priority: "
keypress
killall dunst
../../dunst -config dunstrc.progress_bar &
../../dunstify -h int:value:33 -a "dunst tester" -u n "The progress bar should not be the entire width"
../../dunstify -h int:value:33 -a "dunst tester" -u n "You might also notice height and frame size are changed"
../../dunstify -h int:value:33 -a "dunst tester" -u c "Short"
keypress
}
if [ -n "$1" ]; then
while [ -n "$1" ]; do
$1
shift
done
else
progress_bar
geometry
corners
show_age

33
test/greenest.awk Executable file
View File

@ -0,0 +1,33 @@
#!/usr/bin/awk -f
# Copyright (c) 2016 Scott Vokes <vokes.s@gmail.com>
#
# Permission to use, copy, modify, and/or distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
# copyright notice and this permission notice appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
# ANY SPECIAL, DIRECT, 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.
BEGIN {
GREEN = "\033[32m"
RED = "\033[31m"
YELLOW = "\033[33m"
RESET = "\033[m"
}
/^PASS/ { sub("PASS", GREEN "PASS" RESET) }
/^SKIP/ { sub("SKIP", YELLOW "SKIP" RESET) }
/^FAIL/ { sub("FAIL", RED "FAIL" RESET) }
# highlight hexdump difference markers
/^[0-9a-f]/ {
sub("X", GREEN "X" RESET, $2)
gsub("<", GREEN "<" RESET, $0)
}
{ print($0) }

35
test/helpers.c Normal file
View File

@ -0,0 +1,35 @@
#include <gdk-pixbuf/gdk-pixbuf.h>
#include "helpers.h"
GVariant *notification_setup_raw_image(const char *path)
{
GdkPixbuf *pb = gdk_pixbuf_new_from_file(path, NULL);
if (!pb)
return NULL;
GVariant *hint_data = g_variant_new_from_data(
G_VARIANT_TYPE("ay"),
gdk_pixbuf_read_pixels(pb),
gdk_pixbuf_get_byte_length(pb),
TRUE,
(GDestroyNotify) g_object_unref,
g_object_ref(pb));
GVariant *hint = g_variant_new(
"(iiibii@ay)",
gdk_pixbuf_get_width(pb),
gdk_pixbuf_get_height(pb),
gdk_pixbuf_get_rowstride(pb),
gdk_pixbuf_get_has_alpha(pb),
gdk_pixbuf_get_bits_per_sample(pb),
gdk_pixbuf_get_n_channels(pb),
hint_data);
g_object_unref(pb);
return hint;
}
/* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */

9
test/helpers.h Normal file
View File

@ -0,0 +1,9 @@
#ifndef DUNST_TEST_HELPERS_H
#define DUNST_TEST_HELPERS_H
#include <glib.h>
GVariant *notification_setup_raw_image(const char *path);
#endif
/* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */

View File

@ -12,6 +12,69 @@
extern const char *base;
int scale = 1;
TEST test_get_path_from_icon_null(void){
char *result = get_path_from_icon_name(NULL);
ASSERT_EQ(result, NULL);
PASS();
}
TEST test_get_path_from_icon_sorting(void)
{
const char *iconpath = ICONPREFIX;
const char* icon_names[] = { "onlypng", "onlysvg", "icon1" };
const char* icon_paths[] = { "valid/onlypng.png", "valid/onlysvg.svg", "invalid/icon1.svg" };
ASSERT_EQm("Test is incorrect", G_N_ELEMENTS(icon_names), G_N_ELEMENTS(icon_paths));
for (int i = 0; i < G_N_ELEMENTS(icon_names); i++){
gchar *path = g_build_filename(base, iconpath, icon_paths[i], NULL);
char *result = get_path_from_icon_name(icon_names[i]);
ASSERT(result);
ASSERT_EQ(strcmp(result, path), 0);
g_free(path);
g_free(result);
}
PASS();
}
TEST test_get_path_from_icon_name(void)
{
const char *iconpath = ICONPREFIX;
const char* icon_name = "onlypng";
const char* expected_suffix = ".png";
char* full_name = g_strconcat(icon_name, expected_suffix, NULL);
gchar *path = g_build_filename(base, iconpath, "valid", full_name, NULL);
char *result = get_path_from_icon_name(icon_name);
ASSERT(result);
ASSERT_EQ(strcmp(result, path), 0);
g_free(full_name);
g_free(path);
g_free(result);
PASS();
}
TEST test_get_path_from_icon_name_full(void)
{
const char *iconpath = ICONPREFIX;
gchar *path = g_build_filename(base, iconpath, "valid", "icon1.svg", NULL);
char *result = get_path_from_icon_name(path);
ASSERT(result);
ASSERT_EQ(strcmp(result, path), 0);
g_free(path);
g_free(result);
PASS();
}
TEST test_get_pixbuf_from_file_tilde(void)
{
const char *home = g_get_home_dir();
@ -25,7 +88,7 @@ TEST test_get_pixbuf_from_file_tilde(void)
gchar *path = g_build_filename(base, iconpath, "valid", "icon1.svg", NULL);
path = string_replace_at(path, 0, strlen(home), "~");
GdkPixbuf *pixbuf = get_pixbuf_from_file(path);
GdkPixbuf *pixbuf = get_pixbuf_from_file(path, scale);
g_clear_pointer(&path, g_free);
ASSERT(pixbuf);
@ -40,7 +103,7 @@ TEST test_get_pixbuf_from_file_absolute(void)
gchar *path = g_build_filename(base, iconpath, "valid", "icon1.svg", NULL);
GdkPixbuf *pixbuf = get_pixbuf_from_file(path);
GdkPixbuf *pixbuf = get_pixbuf_from_file(path, scale);
g_clear_pointer(&path, g_free);
ASSERT(pixbuf);
@ -52,7 +115,7 @@ TEST test_get_pixbuf_from_file_absolute(void)
TEST test_get_pixbuf_from_icon_invalid(void)
{
GdkPixbuf *pixbuf = get_pixbuf_from_icon("invalid");
GdkPixbuf *pixbuf = get_pixbuf_from_icon("invalid", scale);
ASSERT(pixbuf == NULL);
g_clear_pointer(&pixbuf, g_object_unref);
@ -61,9 +124,9 @@ TEST test_get_pixbuf_from_icon_invalid(void)
TEST test_get_pixbuf_from_icon_both(void)
{
GdkPixbuf *pixbuf = get_pixbuf_from_icon("icon1");
ASSERT(pixbuf);
ASSERTm("SVG pixbuf hasn't precedence", IS_ICON_SVG(pixbuf));
GdkPixbuf *pixbuf = get_pixbuf_from_icon("icon1", scale);
// the first icon found is invalid, so the pixbuf is empty
ASSERT(!pixbuf);
g_clear_pointer(&pixbuf, g_object_unref);
PASS();
@ -71,7 +134,7 @@ TEST test_get_pixbuf_from_icon_both(void)
TEST test_get_pixbuf_from_icon_onlysvg(void)
{
GdkPixbuf *pixbuf = get_pixbuf_from_icon("onlysvg");
GdkPixbuf *pixbuf = get_pixbuf_from_icon("onlysvg", scale);
ASSERT(pixbuf);
ASSERTm("SVG pixbuf isn't loaded", IS_ICON_SVG(pixbuf));
g_clear_pointer(&pixbuf, g_object_unref);
@ -81,7 +144,7 @@ TEST test_get_pixbuf_from_icon_onlysvg(void)
TEST test_get_pixbuf_from_icon_onlypng(void)
{
GdkPixbuf *pixbuf = get_pixbuf_from_icon("onlypng");
GdkPixbuf *pixbuf = get_pixbuf_from_icon("onlypng", scale);
ASSERT(pixbuf);
ASSERTm("PNG pixbuf isn't loaded", IS_ICON_PNG(pixbuf));
g_clear_pointer(&pixbuf, g_object_unref);
@ -92,7 +155,7 @@ TEST test_get_pixbuf_from_icon_onlypng(void)
TEST test_get_pixbuf_from_icon_filename(void)
{
char *icon = g_strconcat(base, "/data/icons/valid.png", NULL);
GdkPixbuf *pixbuf = get_pixbuf_from_icon(icon);
GdkPixbuf *pixbuf = get_pixbuf_from_icon(icon, scale);
ASSERT(pixbuf);
ASSERTm("PNG pixbuf isn't loaded", IS_ICON_PNG(pixbuf));
g_clear_pointer(&pixbuf, g_object_unref);
@ -104,7 +167,7 @@ TEST test_get_pixbuf_from_icon_filename(void)
TEST test_get_pixbuf_from_icon_fileuri(void)
{
char *icon = g_strconcat("file://", base, "/data/icons/valid.svg", NULL);
GdkPixbuf *pixbuf = get_pixbuf_from_icon(icon);
GdkPixbuf *pixbuf = get_pixbuf_from_icon(icon, scale);
ASSERT(pixbuf);
ASSERTm("SVG pixbuf isn't loaded", IS_ICON_SVG(pixbuf));
g_clear_pointer(&pixbuf, g_object_unref);
@ -113,14 +176,80 @@ TEST test_get_pixbuf_from_icon_fileuri(void)
PASS();
}
TEST test_icon_size_clamp_too_small(void)
{
int w = 12, h = 24;
bool resized = icon_size_clamp(&w, &h);
ASSERT(resized);
ASSERT_EQ(w, 16);
ASSERT_EQ(h, 32);
PASS();
}
TEST test_icon_size_clamp_not_necessary(void)
{
int w = 20, h = 30;
bool resized = icon_size_clamp(&w, &h);
ASSERT(!resized);
ASSERT_EQ(w, 20);
ASSERT_EQ(h, 30);
PASS();
}
TEST test_icon_size_clamp_too_big(void)
{
int w = 75, h = 150;
bool resized = icon_size_clamp(&w, &h);
ASSERT(resized);
ASSERT_EQ(w, 50);
ASSERT_EQ(h, 100);
PASS();
}
TEST test_icon_size_clamp_too_small_then_too_big(void)
{
int w = 8, h = 80;
bool resized = icon_size_clamp(&w, &h);
ASSERT(resized);
ASSERT_EQ(w, 10);
ASSERT_EQ(h, 100);
PASS();
}
TEST test_get_pixbuf_from_icon_both_is_scaled(void)
{
GdkPixbuf *pixbuf = get_pixbuf_from_icon("onlypng", scale);
ASSERT(pixbuf);
ASSERT_EQ(gdk_pixbuf_get_width(pixbuf), 16);
ASSERT_EQ(gdk_pixbuf_get_height(pixbuf), 16);
g_clear_pointer(&pixbuf, g_object_unref);
PASS();
}
SUITE(suite_icon)
{
// set only valid icons in the path
settings.icon_path = g_strconcat(
base, ICONPREFIX "/valid"
":", base, ICONPREFIX "/both",
NULL);
RUN_TEST(test_get_path_from_icon_name);
g_clear_pointer(&settings.icon_path, g_free);
settings.icon_path = g_strconcat(
base, ICONPREFIX "/invalid"
":", base, ICONPREFIX "/valid"
":", base, ICONPREFIX "/both",
NULL);
RUN_TEST(test_get_path_from_icon_null);
RUN_TEST(test_get_path_from_icon_sorting);
RUN_TEST(test_get_path_from_icon_name_full);
RUN_TEST(test_get_pixbuf_from_file_tilde);
RUN_TEST(test_get_pixbuf_from_file_absolute);
RUN_TEST(test_get_pixbuf_from_icon_invalid);
@ -129,6 +258,31 @@ SUITE(suite_icon)
RUN_TEST(test_get_pixbuf_from_icon_onlypng);
RUN_TEST(test_get_pixbuf_from_icon_filename);
RUN_TEST(test_get_pixbuf_from_icon_fileuri);
RUN_TEST(test_icon_size_clamp_not_necessary);
settings.min_icon_size = 16;
settings.max_icon_size = 100;
RUN_TEST(test_get_pixbuf_from_icon_both_is_scaled);
RUN_TEST(test_icon_size_clamp_too_small);
RUN_TEST(test_icon_size_clamp_not_necessary);
RUN_TEST(test_icon_size_clamp_too_big);
RUN_TEST(test_icon_size_clamp_too_small_then_too_big);
settings.min_icon_size = 16;
settings.max_icon_size = 0;
RUN_TEST(test_icon_size_clamp_too_small);
RUN_TEST(test_icon_size_clamp_not_necessary);
settings.min_icon_size = 0;
settings.max_icon_size = 100;
RUN_TEST(test_icon_size_clamp_not_necessary);
RUN_TEST(test_icon_size_clamp_too_big);
settings.min_icon_size = 0;
settings.max_icon_size = 0;
g_clear_pointer(&settings.icon_path, g_free);
}

View File

@ -1,5 +1,6 @@
#include "../src/notification.c"
#include "greatest.h"
#include "helpers.h"
#include "../src/option_parser.h"
#include "../src/settings.h"
@ -124,6 +125,76 @@ TEST test_notification_referencing(void)
PASS();
}
static struct notification *notification_load_icon_with_scaling(int min_icon_size, int max_icon_size)
{
struct notification *n = notification_create();
char *path = g_strconcat(base, "/data/icons/valid.svg", NULL); // 16x16
GVariant *rawIcon = notification_setup_raw_image(path);
settings.min_icon_size = min_icon_size;
settings.max_icon_size = max_icon_size;
notification_icon_replace_data(n, rawIcon);
settings.min_icon_size = 0;
settings.max_icon_size = 0;
g_variant_unref(rawIcon);
g_free(path);
return n;
}
TEST test_notification_icon_scaling_toosmall(void)
{
struct notification *n = notification_load_icon_with_scaling(20, 100);
ASSERT_EQ(gdk_pixbuf_get_width(n->icon), 20);
ASSERT_EQ(gdk_pixbuf_get_height(n->icon), 20);
notification_unref(n);
PASS();
}
TEST test_notification_icon_scaling_toolarge(void)
{
struct notification *n = notification_load_icon_with_scaling(5, 10);
ASSERT_EQ(gdk_pixbuf_get_width(n->icon), 10);
ASSERT_EQ(gdk_pixbuf_get_height(n->icon), 10);
notification_unref(n);
PASS();
}
TEST test_notification_icon_scaling_notconfigured(void)
{
struct notification *n = notification_load_icon_with_scaling(0, 0);
ASSERT_EQ(gdk_pixbuf_get_width(n->icon), 16);
ASSERT_EQ(gdk_pixbuf_get_height(n->icon), 16);
notification_unref(n);
PASS();
}
TEST test_notification_icon_scaling_notneeded(void)
{
struct notification *n = notification_load_icon_with_scaling(10, 20);
ASSERT_EQ(gdk_pixbuf_get_width(n->icon), 16);
ASSERT_EQ(gdk_pixbuf_get_height(n->icon), 16);
notification_unref(n);
PASS();
}
TEST test_notification_format_message(struct notification *n, const char *format, const char *exp)
{
n->format = format;
@ -167,6 +238,10 @@ SUITE(suite_notification)
RUN_TEST(test_notification_is_duplicate);
RUN_TEST(test_notification_replace_single_field);
RUN_TEST(test_notification_referencing);
RUN_TEST(test_notification_icon_scaling_toosmall);
RUN_TEST(test_notification_icon_scaling_toolarge);
RUN_TEST(test_notification_icon_scaling_notconfigured);
RUN_TEST(test_notification_icon_scaling_notneeded);
// TEST notification_format_message
struct notification *a = notification_create();

View File

@ -8,6 +8,7 @@ TEST test_next_section(void)
const char *section = NULL;
ASSERT_STR_EQ("bool", (section = next_section(section)));
ASSERT_STR_EQ("string", (section = next_section(section)));
ASSERT_STR_EQ("list", (section = next_section(section)));
ASSERT_STR_EQ("path", (section = next_section(section)));
ASSERT_STR_EQ("int", (section = next_section(section)));
ASSERT_STR_EQ("double", (section = next_section(section)));
@ -68,6 +69,55 @@ TEST test_ini_get_string(void)
PASS();
}
enum greatest_test_res ARRAY_EQ(char **a, char **b){
ASSERT(a);
ASSERT(b);
int i = 0;
while (a[i] && b[i]){
ASSERT_STR_EQ(a[i], b[i]);
i++;
}
ASSERT_FALSE(a[i]);
ASSERT_FALSE(b[i]);
PASS();
}
TEST test_ini_get_list(void)
{
char *list_section = "list";
char *cmp1[] = {"A", "simple", "list", NULL};
char *cmp2[] = {"A", "list", "with", "spaces", NULL};
char *cmp3[] = {"A list", "with", "multiword entries", NULL};
char *cmp4[] = {"A", "quoted", "list", NULL};
char *cmp5[] = {"A", "list", "\"with quotes\"", NULL};
char *cmp6[] = {"List", "with", "a", NULL};
char **ptr;
CHECK_CALL(ARRAY_EQ(cmp1, (ptr = ini_get_list(list_section, "simple", ""))));
free_string_array(ptr);
CHECK_CALL(ARRAY_EQ(cmp2, (ptr = ini_get_list(list_section, "spaces", ""))));
free_string_array(ptr);
CHECK_CALL(ARRAY_EQ(cmp3, (ptr = ini_get_list(list_section, "multiword", ""))));
free_string_array(ptr);
CHECK_CALL(ARRAY_EQ(cmp4, (ptr = ini_get_list(list_section, "quoted", ""))));
free_string_array(ptr);
CHECK_CALL(ARRAY_EQ(cmp5, (ptr = ini_get_list(list_section, "quoted_with_quotes", ""))));
free_string_array(ptr);
CHECK_CALL(ARRAY_EQ(cmp5, (ptr = ini_get_list(list_section, "unquoted_with_quotes", ""))));
free_string_array(ptr);
CHECK_CALL(ARRAY_EQ(cmp6, (ptr = ini_get_list(list_section, "quoted_comment", ""))));
free_string_array(ptr);
CHECK_CALL(ARRAY_EQ(cmp6, (ptr = ini_get_list(list_section, "unquoted_comment", ""))));
free_string_array(ptr);
PASS();
}
TEST test_ini_get_path(void)
{
char *section = "path";
@ -151,6 +201,22 @@ TEST test_cmdline_get_string(void)
PASS();
}
TEST test_cmdline_get_list(void)
{
char **ptr;
char *cmp1[] = {"A", "simple", "list", "from", "the", "cmdline", NULL};
char *cmp2[] = {"A", "list", "with", "spaces", NULL};
char *cmp3[] = {"A", "default", "list", NULL};
CHECK_CALL(ARRAY_EQ(cmp1, (ptr = cmdline_get_list("-list", "", ""))));
free_string_array(ptr);
CHECK_CALL(ARRAY_EQ(cmp2, (ptr = cmdline_get_list("-list2", "", ""))));
free_string_array(ptr);
CHECK_CALL(ARRAY_EQ(cmp3, (ptr = cmdline_get_list("-nonexistent", "A, default, list", ""))));
free_string_array(ptr);
PASS();
}
TEST test_cmdline_get_int(void)
{
ASSERT_EQ(3, cmdline_get_int("-int", 0, ""));
@ -221,6 +287,29 @@ TEST test_option_get_string(void)
PASS();
}
TEST test_option_get_list(void)
{
char *list_section = "list";
char **ptr;
char *cmp1[] = {"A", "simple", "list", NULL};
char *cmp2[] = {"A", "list", "with", "spaces", NULL};
char *cmp3[] = {"A", "simple", "list", "from", "the", "cmdline", NULL};
char *cmp4[] = {"A", "default", "list", NULL};
CHECK_CALL(ARRAY_EQ(cmp1, (ptr = option_get_list(list_section, "simple", "-nonexistent", "", ""))));
free_string_array(ptr);
CHECK_CALL(ARRAY_EQ(cmp2, (ptr = option_get_list(list_section, "quoted", "-list2", "", ""))));
free_string_array(ptr);
CHECK_CALL(ARRAY_EQ(cmp3, (ptr = option_get_list(list_section, "simple", "-list", "", ""))));
free_string_array(ptr);
CHECK_CALL(ARRAY_EQ(cmp3, (ptr = option_get_list(list_section, "simple", "-list/-l", "", ""))));
free_string_array(ptr);
CHECK_CALL(ARRAY_EQ(cmp4, (ptr = option_get_list(list_section, "nonexistent", "-nonexistent", "A, default, list", ""))));
free_string_array(ptr);
PASS();
}
TEST test_option_get_path(void)
{
char *section = "path";
@ -308,11 +397,13 @@ SUITE(suite_option_parser)
RUN_TEST(test_next_section);
RUN_TEST(test_ini_get_bool);
RUN_TEST(test_ini_get_string);
RUN_TEST(test_ini_get_list);
RUN_TEST(test_ini_get_path);
RUN_TEST(test_ini_get_int);
RUN_TEST(test_ini_get_double);
char cmdline[] = "dunst -bool -b "
"-string \"A simple string from the cmdline\" -s Single_word_string "
"-list A,simple,list,from,the,cmdline -list2 \"A, list, with, spaces\" "
"-int 3 -i 2 -negative -7 -zeroes 04 -intdecim 2.5 "
"-path ~/path/from/cmdline "
"-simple_double 2 -double 5.2"
@ -322,6 +413,7 @@ SUITE(suite_option_parser)
g_shell_parse_argv(&cmdline[0], &argc, &argv, NULL);
cmdline_load(argc, argv);
RUN_TEST(test_cmdline_get_string);
RUN_TEST(test_cmdline_get_list);
RUN_TEST(test_cmdline_get_path);
RUN_TEST(test_cmdline_get_int);
RUN_TEST(test_cmdline_get_double);
@ -329,6 +421,7 @@ SUITE(suite_option_parser)
RUN_TEST(test_cmdline_create_usage);
RUN_TEST(test_option_get_string);
RUN_TEST(test_option_get_list);
RUN_TEST(test_option_get_path);
RUN_TEST(test_option_get_int);
RUN_TEST(test_option_get_double);

View File

@ -740,6 +740,91 @@ TEST test_queues_timeout_before_paused(void)
PASS();
}
TEST test_queue_find_by_id(void)
{
struct notification *n;
int id;
queues_init();
n = test_notification("n", 0);
queues_notification_insert(n);
n = test_notification("n1", 0);
queues_notification_insert(n);
id = n->id;
n = test_notification("n2", 0);
queues_notification_insert(n);
n = queues_get_by_id(id);
ASSERT(n->id == id);
ASSERT(!strncmp(n->summary, "n1", 2));
queues_teardown();
PASS();
}
void print_queues() {
printf("\nQueues:\n");
for (GList *iter = g_queue_peek_head_link(QUEUE_WAIT); iter;
iter = iter->next) {
struct notification *notif = iter->data;
printf("waiting %s\n", notif->summary);
}
}
// Test if notifications are correctly sorted, even if dunst is paused in
// between. See #838 for the issue.
TEST test_queue_no_sort_and_pause(void)
{
// Setting sort to false, this means that notifications will only be
// sorted based on time
settings.sort = false;
settings.geometry.h = 0;
struct notification *n;
queues_init();
n = test_notification("n0", 0);
queues_notification_insert(n);
queues_update(STATUS_NORMAL);
n = test_notification("n1", 0);
queues_notification_insert(n);
queues_update(STATUS_NORMAL);
n = test_notification("n2", 0);
queues_notification_insert(n);
queues_update(STATUS_PAUSE);
n = test_notification("n3", 0);
queues_notification_insert(n);
queues_update(STATUS_PAUSE);
/* queues_update(STATUS_NORMAL); */
n = test_notification("n4", 0);
queues_notification_insert(n);
queues_update(STATUS_NORMAL);
QUEUE_LEN_ALL(0, 5, 0);
const char* order[] = {
"n0",
"n1",
"n2",
"n3",
"n4",
};
for (int i = 0; i < g_queue_get_length(QUEUE_DISP); i++) {
struct notification *notif = g_queue_peek_nth(QUEUE_DISP, i);
ASSERTm("Notifications are not in the right order",
STR_EQ(notif->summary, order[i]));
}
queues_teardown();
PASS();
}
SUITE(suite_queues)
{
settings.icon_path = "";
@ -770,6 +855,8 @@ SUITE(suite_queues)
RUN_TEST(test_queues_update_seeping);
RUN_TEST(test_queues_update_xmore);
RUN_TEST(test_queues_timeout_before_paused);
RUN_TEST(test_queue_find_by_id);
RUN_TEST(test_queue_no_sort_and_pause);
settings.icon_path = NULL;
}

View File

@ -1,24 +1,32 @@
#!/usr/bin/env bash
# Throw error any time a command fails
set -euo pipefail
BASE="$(dirname "$(dirname "$(readlink -f "$0")")")"
PREFIX="${BASE}/install"
DESTDIR="${BASE}/install"
PREFIX="/testprefix"
SYSCONFDIR="/sysconfdir"
make -C "${BASE}" SYSTEMD=1 SERVICEDIR_SYSTEMD="${PREFIX}/systemd" SERVICEDIR_DBUS="${PREFIX}/dbus" PREFIX="${PREFIX}" install
make -C "${BASE}" SYSTEMD=1 DESTDIR="${DESTDIR}" PREFIX="${PREFIX}" SYSCONFDIR="${SYSCONFDIR}" SERVICEDIR_SYSTEMD="/systemd" SERVICEDIR_DBUS="/dbus" install
diff -u <(find "${PREFIX}" -type f -printf "%P\n" | sort) - <<EOF
bin/dunst
diff -u <(find "${DESTDIR}" -type f -printf "%P\n" | sort) - <<EOF
dbus/org.knopwob.dunst.service
share/dunst/dunstrc
share/man/man1/dunst.1
sysconfdir/dunst/dunstrc
systemd/dunst.service
testprefix/bin/dunst
testprefix/bin/dunstctl
testprefix/bin/dunstify
testprefix/share/man/man1/dunst.1
testprefix/share/man/man1/dunstctl.1
testprefix/share/man/man5/dunst.5
EOF
# make sure to manually sort the above values
make -C "${BASE}" SYSTEMD=1 SERVICEDIR_SYSTEMD="${PREFIX}/systemd" SERVICEDIR_DBUS="${PREFIX}/dbus" PREFIX="${PREFIX}" uninstall
make -C "${BASE}" SYSTEMD=1 DESTDIR="${DESTDIR}" PREFIX="${PREFIX}" SYSCONFDIR="${SYSCONFDIR}" SERVICEDIR_SYSTEMD="/systemd" SERVICEDIR_DBUS="/dbus" uninstall
if ! [ -z "$(find "${PREFIX}" -type f)" ]; then
if ! [ -z "$(find "${DESTDIR}" -type f)" ]; then
echo "Uninstall failed, following files weren't removed"
find "${PREFIX}" -type f
find "${DESTDIR}" -type f
exit 1
fi