Switched to dunst

This commit is contained in:
Lian Drake 2024-08-03 03:06:52 -04:00
parent 659971045c
commit 03f276da64
17 changed files with 360 additions and 932 deletions

View file

@ -40,7 +40,6 @@
### Other suckless utilities included ### Other suckless utilities included
* *dmenu:* the best run launcher. This build includes some very useful scripts for things like wifi, bluetooth and wallpaper configuration, drive mounting, etc. * *dmenu:* the best run launcher. This build includes some very useful scripts for things like wifi, bluetooth and wallpaper configuration, drive mounting, etc.
* *herbe & tiramisu:* a minimal and fast notification daemon (tiramisu is the daemon, herbe is the notification window, I bundled them together).
* *st:* the fastest terminal emulator ever, [siduck's build](https://github.com/siduck/st). * *st:* the fastest terminal emulator ever, [siduck's build](https://github.com/siduck/st).
* *slock:* simple and efficient lock screen with fingerprint reader support. * *slock:* simple and efficient lock screen with fingerprint reader support.
* *dwbmlocks:* what enables you to customize dwm's status area in the bar. This build includes some cool & customizable status scripts. * *dwbmlocks:* what enables you to customize dwm's status area in the bar. This build includes some cool & customizable status scripts.
@ -57,6 +56,7 @@ Other configuration files included in this project are available at the config f
* `.config/mpv`: Mpv config files, mainly just for vim-like keybindings. * `.config/mpv`: Mpv config files, mainly just for vim-like keybindings.
* `.config/newsboat`: [Newsboat](https://github.com/newsboat/newsboat) is an awesome RSS/Atom feeds reader for the terminal. Also accesible from dwm via a scratchpad. The config file is for vim-like keybindings and also my collection of RSS & YouTube subscriptions feeds (you can open any video in mpv hitting first comma and then v). * `.config/newsboat`: [Newsboat](https://github.com/newsboat/newsboat) is an awesome RSS/Atom feeds reader for the terminal. Also accesible from dwm via a scratchpad. The config file is for vim-like keybindings and also my collection of RSS & YouTube subscriptions feeds (you can open any video in mpv hitting first comma and then v).
* `.config/picom`: The only X compositor that actually works, responsible of transparency and some animations. * `.config/picom`: The only X compositor that actually works, responsible of transparency and some animations.
* `.config/dunst`: A cool, minimal and fast notification daemon. Best one out there.
* `.config/qutebrowser`: Sometimes I like using a minimal browser, and qutebrowser is the best one. * `.config/qutebrowser`: Sometimes I like using a minimal browser, and qutebrowser is the best one.
* `.config/X11`: This is where I put the xinitrc file, responsible of starting up dwm properly. * `.config/X11`: This is where I put the xinitrc file, responsible of starting up dwm properly.
* `.config/vifm`: [vifm](https://vifm.info/) is the best terminal file manager with everything you will and may need, with vim-like keybindings and image previews (with ueberzug). * `.config/vifm`: [vifm](https://vifm.info/) is the best terminal file manager with everything you will and may need, with vim-like keybindings and image previews (with ueberzug).
@ -234,8 +234,6 @@ These are the patches I applied to this dwm build (some of them I modified):
## Credits ## Credits
- dwm and the suckless tools available here are made by the suckless guys at [https://suckless.org](https://suckless.org). - dwm and the suckless tools available here are made by the suckless guys at [https://suckless.org](https://suckless.org).
- herbe is made by dudik and available [here](https://github.com/dudik/herbe).
- tiramisu is made by Sweets and available [here](https://github.com/Sweets/tiramisu).
- dwmblocks is made by torrinfail and available [here](https://github.com/torrinfail/dwmblocks) - dwmblocks is made by torrinfail and available [here](https://github.com/torrinfail/dwmblocks)
## License ## License

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

View file

@ -0,0 +1,358 @@
[global]
### Display ###
# Which monitor should the notifications be displayed on.
monitor = 0
# Display notification on focused monitor. Possible modes are:
# mouse: follow mouse pointer
# keyboard: follow window with keyboard focus
# none: don't follow anything
#
# "keyboard" needs a window manager that exports the
# _NET_ACTIVE_WINDOW property.
# This should be the case for almost all modern window managers.
#
# If this option is set to mouse or keyboard, the monitor option
# will be ignored.
follow = mouse
# Show how many messages are currently hidden (because of geometry).
indicate_hidden = yes
# Shrink window if it's smaller than the width. Will be ignored if
# width is 0.
shrink = no
# The transparency of the window. Range: [0; 100].
# This option will only work if a compositing window manager is
# present (e.g. xcompmgr, compiz, etc.).
transparency = 30
# Draw a line of "separator_height" pixel height between two
# notifications.
# Set to 0 to disable.
separator_height = 2
# Padding between text and separator.
padding = 8
# Horizontal padding.
horizontal_padding = 8
# Defines width in pixels of frame around the notification window.
# Set to 0 to disable.
frame_width = 1
# Defines color of the frame around the notification window.
frame_color = "#fb4934"
# Define a color for the separator.
# possible values are:
# * auto: dunst tries to find a color fitting to the background;
# * foreground: use the same color as the foreground;
# * frame: use the same color as the frame;
# * anything else will be interpreted as a X color.
separator_color = auto
# Sort messages by urgency.
sort = yes
# Don't remove messages, if the user is idle (no mouse or keyboard input)
# for longer than idle_threshold seconds.
# Set to 0 to disable.
# A client can set the 'transient' hint to bypass this. See the rules
# section for how to disable this if necessary
idle_threshold = 120
### Text ###
font = mononoki Nerd Font 10
# The spacing between lines. If the height is smaller than the
# font height, it will get raised to the font height.
line_height = 0
# Possible values are:
# full: Allow a small subset of html markup in notifications:
# <b>bold</b>
# <i>italic</i>
# <s>strikethrough</s>
# <u>underline</u>
#
# For a complete reference see
# <http://developer.gnome.org/pango/stable/PangoMarkupFormat.html>.
#
# strip: This setting is provided for compatibility with some broken
# clients that send markup even though it's not enabled on the
# server. Dunst will try to strip the markup but the parsing is
# simplistic so using this option outside of matching rules for
# specific applications *IS GREATLY DISCOURAGED*.
#
# no: Disable markup parsing, incoming notifications will be treated as
# plain text. Dunst will not advertise that it has the body-markup
# capability if this is set as a global setting.
#
# It's important to note that markup inside the format option will be parsed
# regardless of what this is set to.
markup = full
# The format of the message. Possible variables are:
# %a appname
# %s summary
# %b body
# %i iconname (including its path)
# %I iconname (without its path)
# %p progress value if set ([ 0%] to [100%]) or nothing
# %n progress value if set without any extra characters
# %% Literal %
# Markup is allowed
format = "<b>%s</b>\n%b"
# Alignment of message text.
# Possible values are "left", "center" and "right".
alignment = center
# Show age of message if message is older than show_age_threshold
# seconds.
# Set to -1 to disable.
show_age_threshold = 60
# Split notifications into multiple lines if they don't fit into
# geometry.
word_wrap = yes
# When word_wrap is set to no, specify where to make an ellipsis in long lines.
# Possible values are "start", "middle" and "end".
ellipsize = middle
# Ignore newlines '\n' in notifications.
ignore_newline = no
# Stack together notifications with the same content
stack_duplicates = true
# Hide the count of stacked notifications with the same content
hide_duplicate_count = false
# Display indicators for URLs (U) and actions (A).
show_indicators = yes
### Icons ###
# Align icons left/right/off
icon_position = left
# Scale larger icons down to this size, set to 0 to disable
max_icon_size = 32
# Paths to default icons.
icon_path = /usr/share/icons/gnome/16x16/status/:/usr/share/icons/gnome/16x16/devices/
### History ###
# Should a notification popped up from history be sticky or timeout
# as if it would normally do.
sticky_history = yes
# Maximum amount of notifications kept in history
history_length = 20
### Misc/Advanced ###
# dmenu path.
dmenu = /usr/bin/dmenu -p dunst:
# Browser for opening urls in context menu.
browser = /usr/bin/qutebrowser
# Always run rule-defined scripts, even if the notification is suppressed
always_run_script = true
# Define the title of the windows spawned by dunst
title = Dunst
# Define the class of the windows spawned by dunst
class = Dunst
# Define the corner radius of the notification window
# in pixel size. If the radius is 0, you have no rounded
# corners.
# The radius will be automatically lowered if it exceeds half of the
# notification height to avoid clipping text and/or icons.
corner_radius = 0
### Legacy
# Use the Xinerama extension instead of RandR for multi-monitor support.
# This setting is provided for compatibility with older nVidia drivers that
# do not support RandR and using it on systems that support RandR is highly
# discouraged.
#
# By enabling this setting dunst will not be able to detect when a monitor
# is connected or disconnected which might break follow mode if the screen
# layout changes.
force_xinerama = false
### mouse
# Defines action of 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.
# * close_current: Close current notification.
# * close_all: Close all notifications.
mouse_left_click = do_action
mouse_middle_click = close_all
mouse_right_click = close_current
# Experimental features that may or may not work correctly. Do not expect them
# to have a consistent behaviour across releases.
[experimental]
# Calculate the dpi to use on a per-monitor basis.
# If this setting is enabled the Xft.dpi value will be ignored and instead
# dunst will attempt to calculate an appropriate dpi value for each monitor
# using the resolution and physical size. This might be useful in setups
# where there are multiple screens with very different dpi values.
per_monitor_dpi = false
[urgency_low]
# IMPORTANT: colors have to be defined in quotation marks.
# Otherwise the "#" and following would be interpreted as a comment.
background = "#282828"
foreground = "#ebdbd2"
timeout = 5
# Icon for notifications with low urgency, uncomment to enable
icon = /home/drk/.config/dunst/normal.png
[urgency_normal]
background = "#282828"
foreground = "#ebdbd2"
timeout = 5
# Icon for notifications with normal urgency, uncomment to enable
icon = /home/drk/.config/dunst/normal.png
[urgency_critical]
background = "#900000"
foreground = "#ebdbd2"
frame_color = "#ff0000"
timeout = 5
# Icon for notifications with critical urgency, uncomment to enable
icon = /home/drk/.config/dunst/critical.png
# Every section that isn't one of the above is interpreted as a rules to
# override settings for certain messages.
#
# Messages can be matched by
# appname (discouraged, see desktop_entry)
# body
# category
# desktop_entry
# icon
# match_transient
# msg_urgency
# stack_tag
# summary
#
# and you can override the
# background
# foreground
# format
# frame_color
# fullscreen
# new_icon
# set_stack_tag
# set_transient
# timeout
# urgency
#
# Shell-like globbing will get expanded.
#
# Instead of the appname filter, it's recommended to use the desktop_entry filter.
# GLib based applications export their desktop-entry name. In comparison to the appname,
# the desktop-entry won't get localized.
#
# SCRIPTING
# You can specify a script that gets run when the rule matches by
# setting the "script" option.
# The script will be called as follows:
# script appname summary body icon urgency
# where urgency can be "LOW", "NORMAL" or "CRITICAL".
#
# NOTE: if you don't want a notification to be displayed, set the format
# to "".
# NOTE: It might be helpful to run dunst -print in a terminal in order
# to find fitting options for rules.
# Disable the transient hint so that idle_threshold cannot be bypassed from the
# client
#[transient_disable]
# match_transient = yes
# set_transient = no
#
# Make the handling of transient notifications more strict by making them not
# be placed in history.
#[transient_history_ignore]
# match_transient = yes
# history_ignore = yes
# fullscreen values
# show: show the notifications, regardless if there is a fullscreen window opened
# delay: displays the new notification, if there is no fullscreen window active
# If the notification is already drawn, it won't get undrawn.
# pushback: same as delay, but when switching into fullscreen, the notification will get
# withdrawn from screen again and will get delayed like a new notification
#[fullscreen_delay_everything]
# fullscreen = delay
#[fullscreen_show_critical]
# msg_urgency = critical
# fullscreen = show
#[espeak]
# summary = "*"
# script = dunst_espeak.sh
#[script-test]
# summary = "*script*"
# script = dunst_test.sh
#[ignore]
# # This notification will not be displayed
# summary = "foobar"
# format = ""
#[history-ignore]
# # This notification will not be saved in history
# summary = "foobar"
# history_ignore = yes
#[skip-display]
# # This notification will not be displayed, but will be included in the history
# summary = "foobar"
# skip_display = yes
#[signed_on]
# appname = Pidgin
# summary = "*signed on*"
# urgency = low
#
#[signed_off]
# appname = Pidgin
# summary = *signed off*
# urgency = low
#
#[says]
# appname = Pidgin
# summary = *says*
# urgency = critical
#
#[twitter]
# appname = Pidgin
# summary = *twitter.com*
# urgency = normal
#
#[stack-volumes]
# appname = "some_volume_notifiers"
# set_stack_tag = "volume"
#
# vim: ft=cfg

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

View file

@ -10,7 +10,7 @@ pidof pipewire || pipewire &
pidof polkit-gnome-authentication-agent-1 || /usr/libexec/polkit-gnome-authentication-agent-1 & pidof polkit-gnome-authentication-agent-1 || /usr/libexec/polkit-gnome-authentication-agent-1 &
pidof dwmblocks || dwmblocks & pidof dwmblocks || dwmblocks &
pidof unclutter || unclutter --hide-on-touch & pidof unclutter || unclutter --hide-on-touch &
pidof tiramisu || herbed & pidof dunst || dunst --config $HOME/.config/dunst/dunstrc &
pidof picom || picom & pidof picom || picom &
gsettings set org.gnome.desktop.interface cursor-theme 'Simp1e-Gruvbox-Dark' gsettings set org.gnome.desktop.interface cursor-theme 'Simp1e-Gruvbox-Dark'

View file

@ -1,22 +0,0 @@
MIT License
Copyright (c) 2020 Samuel Dudík
Copyright (c) 2020 Sweets
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.

View file

@ -1,38 +0,0 @@
TARGETC = herbe
TARGETV = tiramisu
CFLAGS += -Wall -Wno-unused-value -Wextra -pedantic -lX11 -lXft -I/usr/include/freetype2 -pthread
IFLAGS = --pkg gio-2.0
SRCC := herbe.c config.h
SRCV := src/notification.vala src/dbus.vala src/tiramisu.vala
PREFIX ?= /usr/local
CC ?= cc
VALAC ?= valac
PKG_CONFIG ?= pkg-config
all: $(TARGETC)
$(TARGETC): $(SRCC)
$(CC) herbe.c $(CFLAGS) -o $(TARGETC)
all: $(TARGETV)
$(TARGETV): $(SRCV)
$(VALAC) $(IFLAGS) $(SRCV) -o $(TARGETV)
install: $(TARGETC) $(TARGETV)
mkdir -p ${DESTDIR}${PREFIX}/bin
cp -f $(TARGETC) ${DESTDIR}${PREFIX}/bin
cp -f $(TARGETV) ${DESTDIR}${PREFIX}/bin
cp -f herbed ${DESTDIR}${PREFIX}/bin
uninstall:
rm -f ${DESTDIR}${PREFIX}/bin/$(TARGETC)
rm -f ${DESTDIR}${PREFIX}/bin/$(TARGETV)
rm -f ${DESTDIR}${PREFIX}/bin/herbed
clean:
rm -f $(TARGETC)
rm -f $(TARGETV)
.PHONY: all install uninstall clean

View file

@ -1,68 +0,0 @@
<p align="center">
<b>tiramisu</b><br/>
desktop notifications, the UNIX way
</p>
---
tiramisu is a notification daemon for \*nix desktops that implement notifications using dbus.
Unlike other daemons, tiramisu does not have any sort of window or pop-up, but rather sends all notifications to STDOUT. Doing so enables endless customization from the end-user.
---
<p align="center">
<b>Crafted with ♡</b>
</p>
- [anufrievroman/polytiramisu](https://github.com/anufrievroman/polytiramisu)
---
<p align="center">
<b>Installation</b>
</p>
Tiramisu depends upon Vala, gio, and glib.
|Distribution|Repository|Package name|
|-|-|-|
|Arch Linux|AUR|`tiramisu-git`|
|Alpine Linux|v3.15+|`tiramisu`|
|NixOS|stable|`nixos.tiramisu`|
Don't see your distribution? Check to make sure it wasn't forgotten at [repology](https://repology.org/projects/?search=tiramisu).
Alternatively, build from source.
```sh
$ git clone https://github.com/Sweets/tiramisu
$ cd ./tiramisu
$ make && make install
```
---
<p align="center">
<b>Usage</b>
</p>
By default, tiramisu outputs all information from a notification to standard output. You can change this with `-o`, or if you wish to use JSON format, `-j`. If you need the output format to be sanitized (quotes to be escaped), you can do so with `-s`.
Using `-o` will interpolate your desired format.
Appropriate keys are `#source`, `#icon`, `#id`, `#summary`, `#body`, `#actions`, `#hints`, and `#timeout`.
Using `-j` implies `-s`.
Below is an example of the default output of tiramisu with no flags.
```
evolution-mail-notification
evolution
0
New email in Evolution
You have received 4 new messages.
desktop-entry=org.gnome.Evolution|urgency=1
Show INBOX=default
-1
```

View file

@ -1,139 +0,0 @@
# 🌱 herbe
> Daemon-less notifications without D-Bus. Minimal and lightweight.
<p align="center">
<img src="https://user-images.githubusercontent.com/24730635/90975811-cd62fd00-e537-11ea-9169-92e68a71d0a0.gif" />
</p>
## Features
* Under 200 lines of code
* Doesn't run in the background, just displays the notification and exits
* No external dependencies except Xlib and Xft
* Configurable through `config.h` or Xresources ([using this patch](https://github.com/dudik/herbe/pull/11))
* [Actions support](#actions)
* Extensible through [patches](https://github.com/dudik/herbe/pulls?q=is%3Aopen+is%3Apr+label%3Apatch)
## Table of contents
* [Usage](#usage)
* [Patches](#patches)
* [Dismiss a notification](#dismiss-a-notification)
* [Actions](#actions)
* [Newlines](#newlines)
* [Multiple notifications](#multiple-notifications)
* [Notifications don't show up](#notifications-dont-show-up)
* [Installation](#installation)
* [Packages](#packages)
* [Dependencies](#dependencies)
* [Build](#build)
* [Configuration](#configuration)
* [Contribute](#contribute)
## Usage
### Patches
[List of available patches](https://github.com/dudik/herbe/pulls?q=is%3Aopen+is%3Apr+label%3Apatch)
To create a new patch you'll have to open a pull request with your changes. Append `.diff` to the pull request URL to get a downloadable diff file. Don't forget to prefix the title with `patch:` and to apply the `patch` label to it. For inspiration, look at [my Xresources patch](https://github.com/dudik/herbe/pull/11). Thank you.
_Note: This patching method was heavily inspired by [dylan's sowm](https://github.com/dylanaraps/sowm)._
### Dismiss a notification
A notification can be dismissed either by clicking on it with `DISMISS_BUTTON` (set in config.h, defaults to left mouse button) or sending a `SIGUSR1` signal to it:
```shell
$ pkill -SIGUSR1 herbe
```
Dismissed notifications return exit code 2.
### Actions
Action is a piece of shell code that runs when a notification gets accepted. Accepting a notification is the same as dismissing it, but you have to use either `ACTION_BUTTON` (defaults to right mouse button) or the `SIGUSR2` signal.
An accepted notification always returns exit code 0. To specify an action:
```shell
$ herbe "Notification body" && echo "This is an action"
```
Where everything after `&&` is the action and will get executed after the notification gets accepted.
### Newlines
Every command line argument gets printed on a separate line by default e.g.:
```shell
$ herbe "First line" "Second line" "Third line" ...
```
You can also use `\n` e.g. in `bash`:
```shell
$ herbe $'First line\nSecond line\nThird line'
```
But by default `herbe` prints `\n` literally:
```shell
$ herbe "First line\nStill the first line"
```
Output of other programs will get printed correctly, just make sure to escape it (so you don't end up with every word on a separate line):
```shell
$ herbe "$(ps axch -o cmd:15,%cpu --sort=-%cpu | head)"
```
### Multiple notifications
Notifications are put in a queue and shown one after another in order of creation (first in, first out). They don't overlap and each one is shown for its entire duration.
### Notifications don't show up
Most likely a running notification got terminated forcefully (SIGKILL or any uncaught signal) which caused the semaphore not getting unlocked. First, kill any `herbe` instance that is stuck:
```shell
$ pkill -SIGKILL herbe
```
Then just call `herbe` without any arguments:
```shell
$ herbe
```
Notifications should now show up as expected.
Don't ever send any signals to `herbe` except these:
```shell
# same as pkill -SIGTERM herbe, terminates every running herbe process
$ pkill herbe
$ pkill -SIGUSR1 herbe
$ pkill -SIGUSR2 herbe
```
And you should be fine. That's all you really need to interact with `herbe`.
## Installation
### Packages
[![Packaging status](https://repology.org/badge/vertical-allrepos/herbe.svg)](https://repology.org/project/herbe/versions)
[OpenBSD patch](https://github.com/dudik/herbe/pull/4)
[FreeBSD patch](https://github.com/dudik/herbe/pull/16)
[Wayland port](https://github.com/muevoid/Wayherb) by [muevoid](https://github.com/muevoid)
**Only the [herbe-git AUR package](https://aur.archlinux.org/packages/herbe-git/) is maintained by me.**
### Dependencies
* X11 (Xlib)
* Xft
The names of packages are different depending on which distribution you use.
For example, if you use [Void Linux](https://voidlinux.org/) you will have to install these dependencies:
```shell
sudo xbps-install base-devel libX11-devel libXft-devel
```
### Build
```shell
git clone https://github.com/dudik/herbe
cd herbe
sudo make install
```
`make install` requires root privileges because it copies the resulting binary to `/usr/local/bin`. This makes `herbe` accessible globally.
You can also use `make clean` to remove the binary from the build folder, `sudo make uninstall` to remove the binary from `/usr/local/bin` or just `make` to build the binary locally.
## Configuration
herbe is configured at compile-time by editing `config.h`. Every option should be self-explanatory. There is no `height` option because height is determined by font size and text padding.
[Xresources patch](https://github.com/dudik/herbe/pull/11)
## Contribute
If you want to report a bug or you have a feature request, feel free to [open an issue](https://github.com/dudik/herbe/issues).
## Projects with herbe integration
- [qutebrowser](https://qutebrowser.org/) supports showing web notifications via herbe, via the `content.notifications.presenter` setting.

View file

@ -1,19 +0,0 @@
static const char *background_color = "#1d2021";
static const char *border_color = "#cc241d";
static const char *font_color = "#fbf1c7";
static const char *font_pattern = "mononoki Nerd Font Mono:size=12";
static const unsigned line_spacing = 5;
static const unsigned int padding = 10;
static const unsigned int width = 400;
static const unsigned int border_size = 1;
static const unsigned int pos_x = 30;
static const unsigned int pos_y = 60;
enum corners { TOP_LEFT, TOP_RIGHT, BOTTOM_LEFT, BOTTOM_RIGHT };
enum corners corner = TOP_RIGHT;
static unsigned int duration = 5; /* in seconds */
#define DISMISS_BUTTON Button1
#define ACTION_BUTTON Button3

View file

@ -1,258 +0,0 @@
#include <X11/Xlib.h>
#include <X11/Xft/Xft.h>
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
#include <string.h>
#include <stdarg.h>
#include <fcntl.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include "config.h"
#define EXIT_ACTION 0
#define EXIT_FAIL 1
#define EXIT_DISMISS 2
Display *display;
Window window;
int exit_code = EXIT_DISMISS;
static void die(const char *format, ...)
{
va_list ap;
va_start(ap, format);
vfprintf(stderr, format, ap);
fprintf(stderr, "\n");
va_end(ap);
exit(EXIT_FAIL);
}
int get_max_len(char *string, XftFont *font, int max_text_width)
{
int eol = strlen(string);
XGlyphInfo info;
XftTextExtentsUtf8(display, font, (FcChar8 *)string, eol, &info);
if (info.width > max_text_width)
{
eol = max_text_width / font->max_advance_width;
info.width = 0;
while (info.width < max_text_width)
{
eol++;
XftTextExtentsUtf8(display, font, (FcChar8 *)string, eol, &info);
}
eol--;
}
for (int i = 0; i < eol; i++)
if (string[i] == '\n')
{
string[i] = ' ';
return ++i;
}
if (info.width <= max_text_width)
return eol;
int temp = eol;
while (string[eol] != ' ' && eol)
--eol;
if (eol == 0)
return temp;
else
return ++eol;
}
void expire(int sig)
{
XEvent event;
event.type = ButtonPress;
event.xbutton.button = (sig == SIGUSR2) ? (ACTION_BUTTON) : (DISMISS_BUTTON);
XSendEvent(display, window, 0, 0, &event);
XFlush(display);
}
void read_y_offset(unsigned int **offset, int *id) {
int shm_id = shmget(8432, sizeof(unsigned int), IPC_CREAT | 0660);
if (shm_id == -1) die("shmget failed");
*offset = (unsigned int *)shmat(shm_id, 0, 0);
if (*offset == (unsigned int *)-1) die("shmat failed\n");
*id = shm_id;
}
void free_y_offset(int id) {
shmctl(id, IPC_RMID, NULL);
}
int main(int argc, char *argv[])
{
if (argc == 1)
die("Usage: %s body", argv[0]);
struct sigaction act_expire, act_ignore;
act_expire.sa_handler = expire;
act_expire.sa_flags = SA_RESTART;
sigemptyset(&act_expire.sa_mask);
act_ignore.sa_handler = SIG_IGN;
act_ignore.sa_flags = 0;
sigemptyset(&act_ignore.sa_mask);
sigaction(SIGALRM, &act_expire, 0);
sigaction(SIGTERM, &act_expire, 0);
sigaction(SIGINT, &act_expire, 0);
sigaction(SIGUSR1, &act_ignore, 0);
sigaction(SIGUSR2, &act_ignore, 0);
int opt;
while ((opt = getopt(argc, argv, "d:")) != -1)
{
switch (opt)
{
case 'd':
duration = atoi(optarg); // Parse the duration from the command line
break;
default:
die("Usage: %s [-d duration] body", argv[0]);
}
}
if (optind >= argc)
{
die("Usage: %s [-d duration] body", argv[0]);
}
if (!(display = XOpenDisplay(0)))
{
die("Cannot open display");
}
int screen = DefaultScreen(display);
Visual *visual = DefaultVisual(display, screen);
Colormap colormap = DefaultColormap(display, screen);
int screen_width = DisplayWidth(display, screen);
int screen_height = DisplayHeight(display, screen);
XSetWindowAttributes attributes;
attributes.override_redirect = True;
XftColor color;
XftColorAllocName(display, visual, colormap, background_color, &color);
attributes.background_pixel = color.pixel;
XftColorAllocName(display, visual, colormap, border_color, &color);
attributes.border_pixel = color.pixel;
int num_of_lines = 0;
int max_text_width = width - 2 * padding;
int lines_size = 5;
char **lines = malloc(lines_size * sizeof(char *));
if (!lines)
die("malloc failed");
XftFont *font = XftFontOpenName(display, screen, font_pattern);
for (int i = optind; i < argc; i++)
{
for (unsigned int eol = get_max_len(argv[i], font, max_text_width); eol; argv[i] += eol, num_of_lines++, eol = get_max_len(argv[i], font, max_text_width))
{
if (lines_size <= num_of_lines)
{
lines = realloc(lines, (lines_size += 5) * sizeof(char *));
if (!lines)
die("realloc failed");
}
lines[num_of_lines] = malloc((eol + 1) * sizeof(char));
if (!lines[num_of_lines])
die("malloc failed");
strncpy(lines[num_of_lines], argv[i], eol);
lines[num_of_lines][eol] = '\0';
}
}
int y_offset_id;
unsigned int *y_offset;
read_y_offset(&y_offset, &y_offset_id);
unsigned int text_height = font->ascent - font->descent;
unsigned int height = (num_of_lines - 1) * line_spacing + num_of_lines * text_height + 2 * padding;
unsigned int x = pos_x;
unsigned int y = pos_y + *y_offset;
unsigned int used_y_offset = (*y_offset) += height + padding;
if (corner == TOP_RIGHT || corner == BOTTOM_RIGHT)
x = screen_width - width - border_size * 2 - x;
if (corner == BOTTOM_LEFT || corner == BOTTOM_RIGHT)
y = screen_height - height - border_size * 2 - y;
window = XCreateWindow(display, RootWindow(display, screen), x, y, width, height, border_size, DefaultDepth(display, screen),
CopyFromParent, visual, CWOverrideRedirect | CWBackPixel | CWBorderPixel, &attributes);
XftDraw *draw = XftDrawCreate(display, window, visual, colormap);
XftColorAllocName(display, visual, colormap, font_color, &color);
XClassHint classhint = { "herbe", "herbe" };
XSetClassHint(display, window, &classhint);
XSelectInput(display, window, ExposureMask | ButtonPress);
XMapWindow(display, window);
sigaction(SIGUSR1, &act_expire, 0);
sigaction(SIGUSR2, &act_expire, 0);
if (duration > 0)
{
alarm(duration);
}
for (;;)
{
XEvent event;
XNextEvent(display, &event);
if (event.type == Expose)
{
XClearWindow(display, window);
for (int i = 0; i < num_of_lines; i++)
XftDrawStringUtf8(draw, &color, font, padding, line_spacing * i + text_height * (i + 1) + padding,
(FcChar8 *)lines[i], strlen(lines[i]));
}
else if (event.type == ButtonPress)
{
if (event.xbutton.button == DISMISS_BUTTON)
break;
else if (event.xbutton.button == ACTION_BUTTON)
{
exit_code = EXIT_ACTION;
break;
}
}
}
for (int i = 0; i < num_of_lines; i++)
free(lines[i]);
if (used_y_offset == *y_offset) free_y_offset(y_offset_id);
free(lines);
XftDrawDestroy(draw);
XftColorFree(display, visual, colormap, &color);
XftFontClose(display, font);
XCloseDisplay(display);
return exit_code;
}

View file

@ -1,14 +0,0 @@
#!/usr/bin/env bash
#thanks to anufrievroman for style (https://github.com/anufrievroman/polytiramisu/blob/9c0a039d8cd8b7066bccbbd237cd8939da66e1fb/polytiramisu.sh#L18)
tiramisu -o "#source;#summary;#body;#timeout" | while read line
do
source=$(cut -d ';' -f 1 <<< "$line")
summary=$(cut -d ';' -f 2 <<< "$line")
body=$(cut -d ';' -f 3 <<< "$line")
timeout=$(expr $(cut -d ';' -f 4 <<< "$line") / 1000)
if [ "$timeout" -lt 1 ]; then
timeout=5
fi
herbe "$summary" " " "$body" -d "$timeout"
done

View file

@ -1,100 +0,0 @@
diff --git a/herbe.c b/herbe.c
index 51d3990..8bfdbc1 100644
--- a/herbe.c
+++ b/herbe.c
@@ -7,7 +7,8 @@
#include <string.h>
#include <stdarg.h>
#include <fcntl.h>
-#include <semaphore.h>
+#include <sys/ipc.h>
+#include <sys/shm.h>
#include "config.h"
@@ -79,13 +80,23 @@ void expire(int sig)
XFlush(display);
}
+void read_y_offset(unsigned int **offset, int *id) {
+ int shm_id = shmget(8432, sizeof(unsigned int), IPC_CREAT | 0660);
+ if (shm_id == -1) die("shmget failed");
+
+ *offset = (unsigned int *)shmat(shm_id, 0, 0);
+ if (*offset == (unsigned int *)-1) die("shmat failed\n");
+ *id = shm_id;
+}
+
+void free_y_offset(int id) {
+ shmctl(id, IPC_RMID, NULL);
+}
+
int main(int argc, char *argv[])
{
if (argc == 1)
- {
- sem_unlink("/herbe");
- die("Usage: %s body", argv[0]);
- }
+ die("Usage: %s body", argv[0]);
struct sigaction act_expire, act_ignore;
@@ -151,16 +162,22 @@ int main(int argc, char *argv[])
}
}
- unsigned int x = pos_x;
- unsigned int y = pos_y;
+ int y_offset_id;
+ unsigned int *y_offset;
+ read_y_offset(&y_offset, &y_offset_id);
+
unsigned int text_height = font->ascent - font->descent;
unsigned int height = (num_of_lines - 1) * line_spacing + num_of_lines * text_height + 2 * padding;
+ unsigned int x = pos_x;
+ unsigned int y = pos_y + *y_offset;
+
+ unsigned int used_y_offset = (*y_offset) += height + padding;
if (corner == TOP_RIGHT || corner == BOTTOM_RIGHT)
- x = screen_width - width - border_size * 2 - pos_x;
+ x = screen_width - width - border_size * 2 - x;
if (corner == BOTTOM_LEFT || corner == BOTTOM_RIGHT)
- y = screen_height - height - border_size * 2 - pos_y;
+ y = screen_height - height - border_size * 2 - y;
window = XCreateWindow(display, RootWindow(display, screen), x, y, width, height, border_size, DefaultDepth(display, screen),
CopyFromParent, visual, CWOverrideRedirect | CWBackPixel | CWBorderPixel, &attributes);
@@ -171,9 +188,6 @@ int main(int argc, char *argv[])
XSelectInput(display, window, ExposureMask | ButtonPress);
XMapWindow(display, window);
- sem_t *mutex = sem_open("/herbe", O_CREAT, 0644, 1);
- sem_wait(mutex);
-
sigaction(SIGUSR1, &act_expire, 0);
sigaction(SIGUSR2, &act_expire, 0);
@@ -204,12 +218,11 @@ int main(int argc, char *argv[])
}
}
- sem_post(mutex);
- sem_close(mutex);
for (int i = 0; i < num_of_lines; i++)
free(lines[i]);
+ if (used_y_offset == *y_offset) free_y_offset(y_offset_id);
free(lines);
XftDrawDestroy(draw);
XftColorFree(display, visual, colormap, &color);
@@ -217,4 +230,4 @@ int main(int argc, char *argv[])
XCloseDisplay(display);
return exit_code;
-}
\ No newline at end of file
+}

View file

@ -1,21 +0,0 @@
diff --git a/herbe.c b/herbe.c
index 51d3990..fd66078 100644
--- a/herbe.c
+++ b/herbe.c
@@ -168,6 +168,9 @@ int main(int argc, char *argv[])
XftDraw *draw = XftDrawCreate(display, window, visual, colormap);
XftColorAllocName(display, visual, colormap, font_color, &color);
+ XClassHint classhint = { "herbe", "herbe" };
+ XSetClassHint(display, window, &classhint);
+
XSelectInput(display, window, ExposureMask | ButtonPress);
XMapWindow(display, window);
@@ -217,4 +220,4 @@ int main(int argc, char *argv[])
XCloseDisplay(display);
return exit_code;
-}
\ No newline at end of file
+}

View file

@ -1,49 +0,0 @@
[DBus (name = "org.freedesktop.Notifications")]
public class NotificationDaemon : Object {
public static uint notification_id = 1;
[DBus (name = "GetServerInformation")]
public void get_server_information(out string name,
out string vendor, out string version, out string spec_version)
throws DBusError, IOError {
name = "tiramisu";
vendor = "Sweets";
version = "2.0";
spec_version = "1.2";
}
[DBus (name = "GetCapabilities")]
public string[] get_capabilities() throws DBusError, IOError {
return {"body", "actions", "icon-static"};
}
[DBus (name = "Notify")]
public uint Notify(string app_name, uint replaces_id, string app_icon,
string summary, string body, string[] actions,
GLib.HashTable<string, GLib.Variant> hints,
int expire_timeout) throws DBusError, IOError {
Notification.output(app_name, replaces_id, app_icon, summary,
body, actions, hints, expire_timeout);
if (replaces_id == 0)
return notification_id++;
return replaces_id;
}
[DBus (name = "CloseNotification")]
public void close_notification(uint id) throws DBusError, IOError {
// close notification
}
[DBus (name = "NotificationClosed")]
public void notification_closed(uint id,
uint reason) throws DBusError, IOError {
// notification was closed
}
// action_invoked
}

View file

@ -1,144 +0,0 @@
string sanitize(string subj) {
return subj
.replace("\"", "\\\"")
.replace("\r", "\\r")
.replace("\n", "\\n"); // ideally all control sequences \u0000 to \u001f
}
public class Notification : Object {
public static string image_get_base64_representation(GLib.Variant image) {
uint width = 0, height = 0, row_stride = 0, bits_per_sample = 0,
channels = 0;
bool alpha = false;
uint8[] pixels = {};
GLib.Variant pixel_data = null;
image.get("(iiibii@ay)",
out width, out height, out row_stride, out alpha,
out bits_per_sample, out channels, out pixel_data);
pixels = pixel_data.get_data_as_bytes().get_data();
string encoded_image = GLib.Base64.encode((uchar[])pixels);
return "".concat(@"$(width):$(height):$(row_stride):",
@"$(alpha):$(bits_per_sample):$(channels):", encoded_image);
}
public static string create_hint_json_string(
GLib.HashTable<string, GLib.Variant> hints) {
// Only if Tiramisu.json is true. Implies Tiramisu.sanitize.
string output = "";
string buffer = "";
string separator = "";
GLib.VariantType image_type = new GLib.VariantType("(iiibiiay)");
hints.foreach((key, value) => {
string _key = key;
string _value = "";
_key = sanitize(_key);
buffer = buffer.concat(separator, @"\"$(_key)\": ");
if (value.is_of_type(GLib.VariantType.STRING)) {
_value = value.print(false);
_value = _value.substring(1, _value.length - 2);
_value = sanitize(_value);
_value = @"\"$(_value)\"";
} else if (value.is_of_type(image_type)) {
_value = @"\"$(image_get_base64_representation(value))\"";
} else {
_value = @"\"$(value.print(false))\"";
}
buffer = buffer.concat(@"$(_value)");
output = output.concat(buffer);
buffer = "";
separator = ", ";
});
return @"{$(output)}";
}
public static string create_hint_csv_string(
GLib.HashTable<string, GLib.Variant> hints) {
string output = "";
string buffer = "";
string separator = "";
GLib.VariantType image_type = new GLib.VariantType("(iiibiiay)");
hints.foreach((key, value) => {
string _key = key;
string _value = "";
if (value.is_of_type(GLib.VariantType.STRING)) {
_value = value.print(false);
_value = _value.substring(1, _value.length - 2);
_value = sanitize(_value);
_value = @"'$(_value)'";
} else if (value.is_of_type(image_type)) {
_value = image_get_base64_representation(value);
} else {
_value = value.print(false);
}
if (Tiramisu.sanitize) {
_key = sanitize(_key);
_value = sanitize(_value);
}
buffer = buffer.concat(separator, @"$(_key)=$(_value)");
output = output.concat(buffer);
buffer = "";
separator = ",";
});
return output;
}
public static void output(string source, uint replaces_id, string icon,
string summary, string body, string[] actions,
GLib.HashTable<string, GLib.Variant> hints, int timeout) {
string app_name = source;
string app_icon = icon;
string _summary = summary;
string _body = body;
string hint_string = "";
string fmt = Tiramisu.format;
if (Tiramisu.json) {
fmt = Tiramisu.json_format;
hint_string = create_hint_json_string(hints);
} else {
hint_string = create_hint_csv_string(hints);
}
if (Tiramisu.sanitize) {
app_name = sanitize(app_name);
app_icon = sanitize(app_icon);
_summary = sanitize(_summary);
_body = sanitize(_body);
}
fmt = fmt
.replace("#source", app_name)
.replace("#id", replaces_id.to_string())
.replace("#icon", app_icon)
.replace("#summary", _summary)
.replace("#body", _body)
.replace("#actions", string.joinv(",", actions))
.replace("#hints", hint_string)
.replace("#timeout", timeout.to_string());
stdout.printf(fmt + "\n");
stdout.flush();
}
}

View file

@ -1,56 +0,0 @@
public class Tiramisu : Application {
public static string format = "#source\n#icon\n#id\n#summary\n" +
"#body\n#actions\n#hints\n#timeout";
public static string json_format = "{\"source\": \"#source\", " +
"\"id\": #id, \"summary\": \"#summary\", \"body\": \"#body\", " +
"\"icon\": \"#icon\", \"actions\": \"#actions\", \"hints\": #hints, " +
"\"timeout\": #timeout}";
public static bool sanitize = false;
public static bool json = false;
private const OptionEntry[] options = {
{"format", 'o', OptionFlags.NONE, OptionArg.STRING, ref format,
"Output format specifier", "FORMAT"},
{"json", 'j', OptionFlags.NONE, OptionArg.NONE, ref json,
"Output using JSON (implies --sanitize)", null},
{"sanitize", 's', OptionFlags.NONE, OptionArg.NONE, ref sanitize,
"Sanitize output; escapes quotes", null},
{null}
};
private Tiramisu() {
this.add_main_option_entries(options);
}
public override void activate() {
this.hold();
if (json)
sanitize = true;
Bus.own_name(BusType.SESSION, "org.freedesktop.Notifications",
BusNameOwnerFlags.DO_NOT_QUEUE,
(connection) => {
try {
connection.register_object("/org/freedesktop/Notifications",
new NotificationDaemon());
} catch (IOError _error) {
error("Unable to register object path.");
}
}, // Bus acquired
() => {
}, // Name acquired
() => {
error("Unable to acquired DBus name.");
} // Unable to acquire
);
}
public static int main(string[] arguments) {
Tiramisu tiramisu = new Tiramisu();
return tiramisu.run(arguments);
}
}