Compare commits
59 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
653058beac | ||
![]() |
931bea2759 | ||
![]() |
30cd80b2eb | ||
![]() |
af8ac2f7d7 | ||
![]() |
8cb9be5970 | ||
![]() |
be1533ad7f | ||
![]() |
7e503e6885 | ||
![]() |
1b6380b2a8 | ||
![]() |
9a3d5b3fe4 | ||
![]() |
d1eb9bfe28 | ||
![]() |
d2b797406a | ||
![]() |
4275fa3915 | ||
![]() |
ed1b721a15 | ||
![]() |
54a4c5d0d2 | ||
![]() |
ee046a027e | ||
![]() |
7e4a8a1c5e | ||
![]() |
d1f4a0a8eb | ||
![]() |
dd04d9a258 | ||
![]() |
8613cdc868 | ||
![]() |
6a5869244d | ||
![]() |
0cd2e8eac9 | ||
![]() |
ae0728e2f1 | ||
![]() |
42b9050950 | ||
![]() |
ed1f2dcd53 | ||
![]() |
bcedc20c36 | ||
![]() |
831a89d29d | ||
![]() |
74ba2f375d | ||
![]() |
e498068ef5 | ||
![]() |
605f195585 | ||
![]() |
bf3baba21a | ||
![]() |
71e43ecc80 | ||
![]() |
76b3441bb3 | ||
![]() |
ff04ca3924 | ||
![]() |
1fcef5c966 | ||
![]() |
b07a296bf8 | ||
![]() |
f68dfa4254 | ||
![]() |
e592a53b0b | ||
![]() |
929d0d54b7 | ||
![]() |
2d87e22906 | ||
![]() |
e5de83645c | ||
![]() |
624e467947 | ||
![]() |
8882b225fd | ||
![]() |
a6cf977e90 | ||
![]() |
86166a46b7 | ||
![]() |
305ebf8734 | ||
![]() |
09c14e4ea9 | ||
![]() |
5a23d5eeb5 | ||
![]() |
ab46163f1e | ||
![]() |
2f5253b72b | ||
![]() |
70eaa5b291 | ||
![]() |
b7631c7de5 | ||
![]() |
69e92da659 | ||
![]() |
3ed3ab8fec | ||
![]() |
90688d0fe9 | ||
![]() |
0d368c2f07 | ||
![]() |
7de2c71b98 | ||
![]() |
ea66e68783 | ||
![]() |
237310c627 | ||
![]() |
3069f7120d |
2
.github/workflows/PKGBUILD-builds.yml
vendored
2
.github/workflows/PKGBUILD-builds.yml
vendored
@@ -24,7 +24,7 @@ jobs:
|
||||
- name: Install packages
|
||||
run: |
|
||||
pacman-key --init
|
||||
pacman -Sy
|
||||
pacman -Syu --noconfirm
|
||||
|
||||
- name: Add builduser
|
||||
run: |
|
||||
|
8
.github/workflows/ubuntu-build.yml
vendored
8
.github/workflows/ubuntu-build.yml
vendored
@@ -1,6 +1,6 @@
|
||||
# This is a basic workflow to help you get started with Actions
|
||||
|
||||
name: Check build for latest Ubuntu LTS.
|
||||
name: Check build for latest Ubuntu version.
|
||||
|
||||
# Controls when the workflow will run
|
||||
on:
|
||||
@@ -14,12 +14,12 @@ on:
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
ubuntu-LTS-build:
|
||||
container: ubuntu:22.04
|
||||
ubuntu-build:
|
||||
container: ubuntu:23.10
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
DEBIAN_FRONTEND: noninteractive
|
||||
PACKAGES: meson libwayland-dev libgtk-3-dev gobject-introspection libgirepository1.0-dev valac libjson-glib-dev libhandy-1-dev libgtk-layer-shell-dev scdoc libgee-0.8-dev libpulse-dev
|
||||
PACKAGES: meson libwayland-dev libgtk-3-dev gobject-introspection libgirepository1.0-dev valac libjson-glib-dev libhandy-1-dev libgtk-layer-shell-dev scdoc libgee-0.8-dev libpulse-dev sassc libgranite-dev
|
||||
steps:
|
||||
- name: Install packages
|
||||
run: |
|
||||
|
109
README.md
109
README.md
@@ -15,10 +15,15 @@ support `wlr_layer_shell_unstable_v1` like Sway or anything wlroots based*
|
||||
only tested with the default GTK **Adwaita** theme. Usage of any third-party
|
||||
theme might require extra tweaks to the default CSS style file*
|
||||
|
||||
## Demo
|
||||
|
||||
https://github.com/ErikReider/SwayNotificationCenter/assets/35975961/93ff072f-e653-4064-8200-1c90590b83ef
|
||||
|
||||

|
||||
|
||||
Table of Contents
|
||||
=================
|
||||
|
||||
* [Screenshots](#screenshots)
|
||||
* [Want to show off your sick config?](#want-to-show-off-your-sick-config)
|
||||
* [Features](#features)
|
||||
* [Available Widgets](#available-widgets)
|
||||
@@ -38,24 +43,20 @@ Table of Contents
|
||||
* [Run](#run)
|
||||
* [Control Center Shortcuts](#control-center-shortcuts)
|
||||
* [Configuring](#configuring)
|
||||
* [Toggle Buttons](#toggle-buttons)
|
||||
* [Notification Inhibition](#notification-inhibition)
|
||||
* [Scripting](#scripting)
|
||||
* [Disable scripting](#disable-scripting)
|
||||
* [i3status-rs Example](#i3status-rs-example)
|
||||
* [Waybar Example](#waybar-example)
|
||||
|
||||
## Screenshots
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
## Want to show off your sick config?
|
||||
|
||||
Post your setup here: [Config flex 💪](https://github.com/ErikReider/SwayNotificationCenter/discussions/183)
|
||||
|
||||
## Features
|
||||
|
||||
- Grouped notifications
|
||||
- Keyboard shortcuts
|
||||
- Notification body markup with image support
|
||||
- Inline replies
|
||||
@@ -95,12 +96,19 @@ These widgets can be customized, added, removed and even reordered
|
||||
|
||||
## Install
|
||||
|
||||
### Alpine Linux
|
||||
|
||||
```zsh
|
||||
apk add swaync
|
||||
````
|
||||
|
||||
### Arch
|
||||
|
||||
The package is available on the AUR:
|
||||
```zsh
|
||||
sudo pacman -S swaync
|
||||
```
|
||||
|
||||
- [swaync](https://aur.archlinux.org/packages/swaync/)
|
||||
- [swaync-git](https://aur.archlinux.org/packages/swaync-git/)
|
||||
Alternatively, [swaync-git](https://aur.archlinux.org/packages/swaync-git/) is available on the AUR.
|
||||
|
||||
### Fedora
|
||||
|
||||
@@ -126,7 +134,7 @@ An **unofficial** ebuild is available in [GURU](https://github.com/gentoo/guru)
|
||||
|
||||
```zsh
|
||||
eselect repository enable guru
|
||||
emaint sync -r guru
|
||||
emaint sync --repo guru
|
||||
emerge --ask gui-apps/swaync
|
||||
```
|
||||
|
||||
@@ -140,7 +148,7 @@ sudo zypper install SwayNotificationCenter
|
||||
|
||||
Lunar and later:
|
||||
|
||||
```
|
||||
```zsh
|
||||
sudo apt install sway-notification-center
|
||||
```
|
||||
|
||||
@@ -149,14 +157,14 @@ sudo apt install sway-notification-center
|
||||
|
||||
Bookworm and later:
|
||||
|
||||
```
|
||||
```zsh
|
||||
sudo apt install sway-notification-center
|
||||
```
|
||||
|
||||
### Guix
|
||||
|
||||
The simplest way is to install it to user's profile:
|
||||
```
|
||||
```zsh
|
||||
guix install swaynotificationcenter
|
||||
```
|
||||
|
||||
@@ -172,8 +180,31 @@ But we recommend to use [Guix Home](https://guix.gnu.org/manual/devel/en/html_no
|
||||
|
||||
### Other
|
||||
|
||||
#### Dependencies
|
||||
|
||||
- `vala >= 0.56`
|
||||
- `meson`
|
||||
- `git`
|
||||
- `scdoc`
|
||||
- `sassc`
|
||||
- `gtk3`
|
||||
- `gtk-layer-shell`
|
||||
- `dbus`
|
||||
- `glib2`
|
||||
- `gobject-introspection`
|
||||
- `libgee`
|
||||
- `json-glib`
|
||||
- `libhandy`
|
||||
- `gvfs`
|
||||
- `granite`
|
||||
|
||||
##### Optional Dependencies
|
||||
|
||||
- `libpulse` (requires meson build options change)
|
||||
- `libnotify`
|
||||
|
||||
```zsh
|
||||
meson build
|
||||
meson setup build --prefix=/usr
|
||||
ninja -C build
|
||||
meson install -C build
|
||||
```
|
||||
@@ -226,7 +257,7 @@ To reload css after changes
|
||||
- Shift+D: Toggle Do Not Disturb
|
||||
- Buttons 1-9: Execute alternative actions
|
||||
- Left click button / actions: Activate notification action
|
||||
- Right click notification: Close notification
|
||||
- Middle/Right click notification: Close notification
|
||||
|
||||
## Configuring
|
||||
|
||||
@@ -236,12 +267,40 @@ See `swaync(5)` man page for more information
|
||||
|
||||
To reload the config, you'll need to run `swaync-client --reload-config`
|
||||
|
||||
The main CSS style file is located in `/etc/xdg/swaync/style.css`. Copy it over
|
||||
to your `~/.config/swaync/` folder to customize without needing root access.
|
||||
The main CSS style file is located in `/etc/xdg/swaync/style.css`. Copy it over
|
||||
to your `~/.config/swaync/` folder to customize without needing root access. For
|
||||
more advanced/larger themes, I recommend that you use the SCSS files from source
|
||||
and customize them instead. To use the SCSS files, compile with `sassc`.
|
||||
|
||||
**Tip**: running swaync with `GTK_DEBUG=interactive swaync` will open a inspector
|
||||
**Tip**: running swaync with `GTK_DEBUG=interactive swaync` will open a inspector
|
||||
window that'll allow you to see all of the CSS classes + other information.
|
||||
|
||||
## Toggle Buttons
|
||||
|
||||
To add toggle buttons to your control center you can set the "type" in any acton to "toggle".
|
||||
The toggle button supports different commands depending on the state of the button and
|
||||
an "update-command" to update the state in case of changes from outside swaync. The update-command
|
||||
is called every time the control center is opened.
|
||||
The active toggle button also gains the css-class ".toggle:checked"
|
||||
|
||||
`config.json` example:
|
||||
|
||||
```jsonc
|
||||
{
|
||||
"buttons-grid": { // also works with actions in menubar widget
|
||||
"actions": [
|
||||
{
|
||||
"label": "WiFi",
|
||||
"type": "toggle",
|
||||
"active": true,
|
||||
"command": "sh -c '[[ $SWAYNC_TOGGLE_STATE == true ]] && nmcli radio wifi on || nmcli radio wifi off'",
|
||||
"update-command": "sh -c '[[ $(nmcli radio wifi) == \"enabled\" ]] && echo true || echo false'"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Notification Inhibition
|
||||
|
||||
Notifications can be inhibited through the provided `swaync-client` executable
|
||||
@@ -277,7 +336,7 @@ Notification information can be printed into a terminal by running
|
||||
|
||||
Config properties:
|
||||
|
||||
```json
|
||||
```jsonc
|
||||
{
|
||||
"scripts": {
|
||||
"example-script": {
|
||||
@@ -289,13 +348,13 @@ Config properties:
|
||||
"category": "Notification category Regex"
|
||||
}
|
||||
}
|
||||
other non scripting properties...
|
||||
// other non scripting properties...
|
||||
}
|
||||
```
|
||||
|
||||
`config.json` example:
|
||||
|
||||
```json
|
||||
```jsonc
|
||||
{
|
||||
"scripts": {
|
||||
// This script will only run when Spotify sends a notification containing
|
||||
@@ -307,7 +366,7 @@ Config properties:
|
||||
"body": "Rick Astley - Whenever You Need Somebody"
|
||||
}
|
||||
}
|
||||
other non scripting properties...
|
||||
// other non scripting properties...
|
||||
}
|
||||
```
|
||||
|
||||
@@ -379,9 +438,9 @@ Waybar css file
|
||||
|
||||
Alternatively, the number of notifications can be shown by adding `{}` anywhere in the `format` field in the Waybar config
|
||||
|
||||
```json
|
||||
```jsonc
|
||||
"custom/notification": {
|
||||
"format": "{} {icon}",
|
||||
...
|
||||
// ...
|
||||
},
|
||||
```
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 1.5 MiB |
BIN
assets/panel.png
BIN
assets/panel.png
Binary file not shown.
Before Width: | Height: | Size: 1.4 MiB After Width: | Height: | Size: 1.5 MiB |
@@ -1,7 +1,7 @@
|
||||
# vim: ft=PKGBUILD
|
||||
# vim: ft=sh
|
||||
# Maintainer: Erik Reider <erik.reider@protonmail.com>
|
||||
pkgname=swaync
|
||||
pkgver=0.9.0
|
||||
pkgver=0.10.1
|
||||
pkgrel=1
|
||||
pkgdesc="A simple notification daemon with a GTK panel for checking previous notifications like other DEs"
|
||||
_pkgfoldername=SwayNotificationCenter
|
||||
@@ -12,12 +12,12 @@ arch=(
|
||||
'armv7h' # ARM v7 hardfloat
|
||||
)
|
||||
license=(GPL3)
|
||||
depends=("gtk3" "gtk-layer-shell" "dbus" "glib2" "gobject-introspection" "libgee" "json-glib" "libhandy" "libpulse")
|
||||
depends=("gtk3" "gtk-layer-shell" "dbus" "glib2" "gobject-introspection" "libgee" "json-glib" "libhandy" "libpulse" "gvfs" "libnotify" "granite")
|
||||
conflicts=("swaync" "swaync-client")
|
||||
provides=("swaync" "swaync-client" "notification-daemon")
|
||||
makedepends=(vala meson git scdoc)
|
||||
makedepends=("vala>=0.56" meson git scdoc sassc)
|
||||
source=("${_pkgfoldername}-${pkgver}.tar.gz::${url}/archive/v${pkgver}.tar.gz")
|
||||
sha256sums=('3f00bc858b7b3610e88ef0f6ee64d727892dd82f280f1dfc01dde863c2ea3376')
|
||||
sha256sums=('5586d8a679dde5e530cb8b6f0c86abdd0d5e41362fc1c4e56e2211edea0f7a13')
|
||||
|
||||
build() {
|
||||
arch-meson "${_pkgfoldername}-${pkgver}" build -Dscripting=true
|
||||
|
@@ -1,8 +1,8 @@
|
||||
# vim: ft=PKGBUILD
|
||||
# vim: ft=sh
|
||||
# Maintainer: Erik Reider <erik.reider@protonmail.com>
|
||||
pkgname=swaync-git
|
||||
_pkgname=swaync
|
||||
pkgver=r448.ba4a266
|
||||
pkgver=r517.4275fa3
|
||||
pkgrel=1
|
||||
pkgdesc="A simple notification daemon with a GTK panel for checking previous notifications like other DEs"
|
||||
url="https://github.com/ErikReider/SwayNotificationCenter"
|
||||
@@ -12,10 +12,10 @@ arch=(
|
||||
'armv7h' # ARM v7 hardfloat
|
||||
)
|
||||
license=('GPL3')
|
||||
depends=("gtk3" "gtk-layer-shell" "dbus" "glib2" "gobject-introspection" "libgee" "json-glib" "libhandy" "libpulse" )
|
||||
depends=("gtk3" "gtk-layer-shell" "dbus" "glib2" "gobject-introspection" "libgee" "json-glib" "libhandy" "libpulse" "gvfs" "libnotify" "granite")
|
||||
conflicts=("swaync" "swaync-client")
|
||||
provides=("swaync" "swaync-client" "notification-daemon")
|
||||
makedepends=(vala meson git scdoc)
|
||||
makedepends=("vala>=0.56" meson git scdoc sassc)
|
||||
source=("$_pkgname::git+$url")
|
||||
sha256sums=('SKIP')
|
||||
|
||||
|
@@ -2,7 +2,7 @@
|
||||
%global alt_pkg_name swaync
|
||||
|
||||
Name: {{{ git_dir_name }}}
|
||||
Version: 0.9.0
|
||||
Version: 0.10.1
|
||||
Release: 1%{?dist}
|
||||
Summary: Notification daemon with GTK GUI
|
||||
Provides: desktop-notification-daemon
|
||||
@@ -14,7 +14,7 @@ VCS: {{{ git_dir_vcs }}}
|
||||
Source: {{{ git_dir_pack }}}
|
||||
|
||||
BuildRequires: meson >= 0.51.0
|
||||
BuildRequires: vala
|
||||
BuildRequires: vala >= 0.56
|
||||
BuildRequires: scdoc
|
||||
BuildRequires: pkgconfig(gtk+-3.0) >= 3.22
|
||||
BuildRequires: pkgconfig(gtk-layer-shell-0) >= 0.1
|
||||
@@ -26,9 +26,14 @@ BuildRequires: pkgconfig(gee-0.8) >= 0.20
|
||||
BuildRequires: pkgconfig(bash-completion)
|
||||
BuildRequires: pkgconfig(fish)
|
||||
BuildRequires: pkgconfig(libpulse)
|
||||
BuildRequires: pkgconfig(granite)
|
||||
BuildRequires: systemd-devel
|
||||
BuildRequires: systemd
|
||||
BuildRequires: sassc
|
||||
BuildRequires: granite-devel
|
||||
|
||||
Requires: gvfs
|
||||
Requires: libnotify
|
||||
Requires: dbus
|
||||
%{?systemd_requires}
|
||||
|
||||
|
4
data/icons/meson.build
Normal file
4
data/icons/meson.build
Normal file
@@ -0,0 +1,4 @@
|
||||
app_resources += gnome.compile_resources('icon-resources',
|
||||
'swaync_icons.gresource.xml',
|
||||
c_name: 'sway_notification_center_icons'
|
||||
)
|
4
data/icons/swaync-close-symbolic.svg
Normal file
4
data/icons/swaync-close-symbolic.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg height="16px" viewBox="0 0 16 16" width="16px" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="m 4 4 h 1 h 0.03125 c 0.253906 0.011719 0.511719 0.128906 0.6875 0.3125 l 2.28125 2.28125 l 2.3125 -2.28125 c 0.265625 -0.230469 0.445312 -0.304688 0.6875 -0.3125 h 1 v 1 c 0 0.285156 -0.035156 0.550781 -0.25 0.75 l -2.28125 2.28125 l 2.25 2.25 c 0.1875 0.1875 0.28125 0.453125 0.28125 0.71875 v 1 h -1 c -0.265625 0 -0.53125 -0.09375 -0.71875 -0.28125 l -2.28125 -2.28125 l -2.28125 2.28125 c -0.1875 0.1875 -0.453125 0.28125 -0.71875 0.28125 h -1 v -1 c 0 -0.265625 0.09375 -0.53125 0.28125 -0.71875 l 2.28125 -2.25 l -2.28125 -2.28125 c -0.210938 -0.195312 -0.304688 -0.46875 -0.28125 -0.75 z m 0 0" fill="#2e3436"/>
|
||||
</svg>
|
After Width: | Height: | Size: 767 B |
5
data/icons/swaync-collapse-symbolic.svg
Normal file
5
data/icons/swaync-collapse-symbolic.svg
Normal file
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<svg height="16px" viewBox="0 0 16 16" width="16px" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M 4 2.5 C 4 2.234 4.106 1.98 4.293 1.793 C 4.684 1.402 5.317 1.402 5.707 1.793 L 8 4.086 L 10.293 1.793 C 10.684 1.402 11.317 1.402 11.707 1.793 C 11.895 1.98 12 2.234 12 2.5 C 12 2.766 11.895 3.019 11.707 3.207 L 8.707 6.207 C 8.317 6.598 7.684 6.598 7.293 6.207 L 4.293 3.207 C 4.106 3.019 4 2.766 4 2.5 Z" fill="#2e3436"/>
|
||||
<path d="M 4 13.5 C 4 13.766 4.106 14.019 4.293 14.207 C 4.684 14.598 5.317 14.598 5.707 14.207 L 8 11.914 L 10.293 14.207 C 10.684 14.598 11.317 14.598 11.707 14.207 C 11.895 14.019 12 13.766 12 13.5 C 12 13.234 11.895 12.98 11.707 12.793 L 8.707 9.793 C 8.317 9.402 7.684 9.402 7.293 9.793 L 4.293 12.793 C 4.106 12.98 4 13.234 4 13.5 Z" fill="#2e3436"/>
|
||||
</svg>
|
After Width: | Height: | Size: 830 B |
7
data/icons/swaync_icons.gresource.xml
Normal file
7
data/icons/swaync_icons.gresource.xml
Normal file
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<gresources>
|
||||
<gresource prefix="/org/erikreider/swaync/icons/scalable/actions">
|
||||
<file preprocess="xml-stripblanks">swaync-collapse-symbolic.svg</file>
|
||||
<file preprocess="xml-stripblanks">swaync-close-symbolic.svg</file>
|
||||
</gresource>
|
||||
</gresources>
|
@@ -2,9 +2,35 @@ install_data('org.erikreider.swaync.gschema.xml',
|
||||
install_dir: join_paths(get_option('datadir'), 'glib-2.0/schemas')
|
||||
)
|
||||
|
||||
subdir('icons')
|
||||
|
||||
compile_schemas = find_program('glib-compile-schemas', required: false)
|
||||
if compile_schemas.found()
|
||||
test('Validate schema file', compile_schemas,
|
||||
args: ['--strict', '--dry-run', meson.current_source_dir()]
|
||||
)
|
||||
endif
|
||||
|
||||
# SCSS Dependency
|
||||
sassc = find_program('sassc')
|
||||
assert(sassc.found())
|
||||
|
||||
# SCSS Compilation
|
||||
style_css = custom_target(
|
||||
'SCSS Compilation',
|
||||
build_by_default: true,
|
||||
build_always_stale: true,
|
||||
input : 'style/style.scss',
|
||||
output : 'style.css',
|
||||
install: true,
|
||||
install_dir: config_path,
|
||||
command : [
|
||||
sassc,
|
||||
'-t',
|
||||
'expanded',
|
||||
'@INPUT@',
|
||||
'@OUTPUT@'
|
||||
],
|
||||
)
|
||||
|
||||
message(style_css.full_path())
|
||||
|
347
data/style/style.scss
Normal file
347
data/style/style.scss
Normal file
@@ -0,0 +1,347 @@
|
||||
@define-color cc-bg rgba(46, 46, 46, 0.7);
|
||||
|
||||
@define-color noti-border-color rgba(255, 255, 255, 0.15);
|
||||
@define-color noti-bg rgba(48, 48, 48, 0.8);
|
||||
@define-color noti-bg-opaque rgb(48, 48, 48);
|
||||
@define-color noti-bg-darker rgb(38, 38, 38);
|
||||
@define-color noti-bg-hover rgb(56, 56, 56);
|
||||
@define-color noti-bg-hover-opaque rgb(56, 56, 56);
|
||||
@define-color noti-bg-focus rgba(68, 68, 68, 0.6);
|
||||
@define-color noti-close-bg rgba(255, 255, 255, 0.1);
|
||||
@define-color noti-close-bg-hover rgba(255, 255, 255, 0.15);
|
||||
|
||||
@define-color text-color rgb(255, 255, 255);
|
||||
@define-color text-color-disabled rgb(150, 150, 150);
|
||||
|
||||
@define-color bg-selected rgb(0, 128, 255);
|
||||
|
||||
$border: 1px solid #{"@noti-border-color"};
|
||||
$border-radius: 12px;
|
||||
|
||||
$font-size-body: 15px;
|
||||
$font-size-summary: 16px;
|
||||
|
||||
$hover-tranistion: background 0.15s ease-in-out;
|
||||
$group-collapse-tranistion: opacity 400ms ease-in-out;
|
||||
|
||||
$notification-shadow: 0 0 0 1px rgba(0, 0, 0, 0.3),
|
||||
0 1px 3px 1px rgba(0, 0, 0, 0.7), 0 2px 6px 2px rgba(0, 0, 0, 0.3);
|
||||
|
||||
.notification-row {
|
||||
outline: none;
|
||||
|
||||
&:focus,
|
||||
&:hover {
|
||||
background: #{"@noti-bg-focus"};
|
||||
}
|
||||
.notification-background {
|
||||
padding: 6px 12px;
|
||||
|
||||
.close-button {
|
||||
/* The notification Close Button */
|
||||
background: #{"@noti-close-bg"};
|
||||
color: #{"@text-color"};
|
||||
text-shadow: none;
|
||||
padding: 0;
|
||||
border-radius: 100%;
|
||||
$margin: 5px;
|
||||
margin-top: $margin;
|
||||
margin-right: $margin;
|
||||
box-shadow: none;
|
||||
border: none;
|
||||
min-width: 24px;
|
||||
min-height: 24px;
|
||||
|
||||
&:hover {
|
||||
box-shadow: none;
|
||||
background: #{"@noti-close-bg-hover"};
|
||||
transition: $hover-tranistion;
|
||||
border: none;
|
||||
}
|
||||
}
|
||||
|
||||
.notification {
|
||||
/* The actual notification */
|
||||
border-radius: $border-radius;
|
||||
border: $border;
|
||||
padding: 0;
|
||||
transition: $hover-tranistion;
|
||||
background: #{"@noti-bg"};
|
||||
|
||||
&.low {
|
||||
/* Low Priority Notification */
|
||||
}
|
||||
&.normal {
|
||||
/* Normal Priority Notification */
|
||||
}
|
||||
&.critical {
|
||||
/* Critical Priority Notification */
|
||||
}
|
||||
|
||||
%action {
|
||||
padding: 4px;
|
||||
margin: 0;
|
||||
box-shadow: none;
|
||||
background: transparent;
|
||||
border: none;
|
||||
color: #{"@text-color"};
|
||||
transition: $hover-tranistion;
|
||||
}
|
||||
%action-hover {
|
||||
-gtk-icon-effect: none;
|
||||
background: #{"@noti-bg-hover"};
|
||||
}
|
||||
|
||||
.notification-default-action {
|
||||
/* The large action that also displays the notification summary and body */
|
||||
@extend %action;
|
||||
border-radius: $border-radius;
|
||||
|
||||
&:hover {
|
||||
@extend %action-hover;
|
||||
}
|
||||
|
||||
&:not(:only-child) {
|
||||
/* When alternative actions are visible */
|
||||
border-bottom-left-radius: 0px;
|
||||
border-bottom-right-radius: 0px;
|
||||
}
|
||||
|
||||
.notification-content {
|
||||
background: transparent;
|
||||
border-radius: $border-radius;
|
||||
$margin: 4px;
|
||||
padding: $margin;
|
||||
|
||||
.image {
|
||||
/* Notification Primary Image */
|
||||
-gtk-icon-effect: none;
|
||||
border-radius: 100px; /* Size in px */
|
||||
margin: $margin;
|
||||
}
|
||||
.app-icon {
|
||||
/* Notification app icon (only visible when the primary image is set) */
|
||||
-gtk-icon-effect: none;
|
||||
-gtk-icon-shadow: 0 1px 4px black;
|
||||
margin: 6px;
|
||||
}
|
||||
|
||||
.text-box {
|
||||
.summary {
|
||||
/* Notification summary/title */
|
||||
font-size: $font-size-summary;
|
||||
font-weight: bold;
|
||||
background: transparent;
|
||||
color: #{"@text-color"};
|
||||
text-shadow: none;
|
||||
}
|
||||
.time {
|
||||
/* Notification time-ago */
|
||||
font-size: $font-size-summary;
|
||||
font-weight: bold;
|
||||
background: transparent;
|
||||
color: #{"@text-color"};
|
||||
text-shadow: none;
|
||||
margin-right: 30px;
|
||||
}
|
||||
.body {
|
||||
/* Notification body */
|
||||
font-size: $font-size-body;
|
||||
font-weight: normal;
|
||||
background: transparent;
|
||||
color: #{"@text-color"};
|
||||
text-shadow: none;
|
||||
}
|
||||
}
|
||||
|
||||
progressbar {
|
||||
/* The optional notification progress bar */
|
||||
margin-top: $margin;
|
||||
}
|
||||
|
||||
.body-image {
|
||||
/* The "extra" optional bottom notification image */
|
||||
margin-top: $margin;
|
||||
background-color: white;
|
||||
border-radius: $border-radius;
|
||||
-gtk-icon-effect: none;
|
||||
}
|
||||
|
||||
.inline-reply {
|
||||
/* The inline reply section */
|
||||
margin-top: $margin;
|
||||
|
||||
.inline-reply-entry {
|
||||
background: #{"@noti-bg-darker"};
|
||||
color: #{"@text-color"};
|
||||
caret-color: #{"@text-color"};
|
||||
border: $border;
|
||||
border-radius: $border-radius;
|
||||
}
|
||||
.inline-reply-button {
|
||||
margin-left: 4px;
|
||||
background: #{"@noti-bg"};
|
||||
border: $border;
|
||||
border-radius: $border-radius;
|
||||
color: #{"@text-color"};
|
||||
|
||||
&:disabled {
|
||||
background: initial;
|
||||
color: #{"@text-color-disabled"};
|
||||
border: $border;
|
||||
border-color: transparent;
|
||||
}
|
||||
&:hover {
|
||||
background: #{"@noti-bg-hover"};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.notification-action {
|
||||
/* The alternative actions below the default action */
|
||||
@extend %action;
|
||||
border-top: $border;
|
||||
border-radius: 0px;
|
||||
border-right: $border;
|
||||
|
||||
&:first-child {
|
||||
/* add bottom border radius to eliminate clipping */
|
||||
border-bottom-left-radius: $border-radius;
|
||||
}
|
||||
&:last-child {
|
||||
border-bottom-right-radius: $border-radius;
|
||||
border-right: none;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
@extend %action-hover;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.notification-group {
|
||||
/* Styling only for Grouped Notifications */
|
||||
|
||||
&.low {
|
||||
/* Low Priority Group */
|
||||
}
|
||||
&.normal {
|
||||
/* Low Priority Group */
|
||||
}
|
||||
&.critical {
|
||||
/* Low Priority Group */
|
||||
}
|
||||
|
||||
%header {
|
||||
margin: 0 16px;
|
||||
color: #{"@text-color"};
|
||||
}
|
||||
|
||||
.notification-group-headers {
|
||||
/* Notification Group Headers */
|
||||
@extend %header;
|
||||
|
||||
.notification-group-icon {
|
||||
color: #{"@text-color"};
|
||||
}
|
||||
.notification-group-header {
|
||||
color: #{"@text-color"};
|
||||
}
|
||||
}
|
||||
.notification-group-buttons {
|
||||
/* Notification Group Buttons */
|
||||
@extend %header;
|
||||
}
|
||||
|
||||
&.collapsed {
|
||||
.notification-row {
|
||||
.notification {
|
||||
background-color: #{"@noti-bg-opaque"};
|
||||
}
|
||||
|
||||
&:not(:last-child) {
|
||||
/* Top notification in stack */
|
||||
/* Set lower stacked notifications opacity to 0 */
|
||||
.notification-action,
|
||||
.notification-default-action {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
.notification-row:not(:only-child) {
|
||||
.notification {
|
||||
background-color: #{"@noti-bg-hover-opaque"};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.control-center {
|
||||
/* The Control Center which contains the old notifications + widgets */
|
||||
background: #{"@cc-bg"};
|
||||
color: #{"@text-color"};
|
||||
border-radius: $border-radius;
|
||||
|
||||
.control-center-list-placeholder {
|
||||
/* The placeholder when there are no notifications */
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.control-center-list {
|
||||
/* List of notifications */
|
||||
background: transparent;
|
||||
|
||||
.notification {
|
||||
box-shadow: $notification-shadow;
|
||||
|
||||
.notification-default-action,
|
||||
.notification-action {
|
||||
transition: $group-collapse-tranistion, $hover-tranistion;
|
||||
|
||||
&:hover {
|
||||
background-color: #{"@noti-bg-hover"};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.blank-window {
|
||||
/* Window behind control center and on all other monitors */
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.floating-notifications {
|
||||
background: transparent;
|
||||
|
||||
.notification {
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
|
||||
/*** Widgets ***/
|
||||
/* Title widget */
|
||||
@import "widgets/title";
|
||||
/* DND widget */
|
||||
@import "widgets/dnd";
|
||||
/* Label widget */
|
||||
@import "widgets/label";
|
||||
/* Mpris widget */
|
||||
@import "widgets/mpris";
|
||||
/* Buttons widget */
|
||||
@import "widgets/buttons";
|
||||
/* Menubar widget */
|
||||
@import "widgets/menubar";
|
||||
/* Volume widget */
|
||||
@import "widgets/volume";
|
||||
/* Backlight widget */
|
||||
@import "widgets/backlight";
|
||||
/* Inhibitors widget */
|
||||
@import "widgets/inhibitors";
|
6
data/style/widgets/backlight.scss
Normal file
6
data/style/widgets/backlight.scss
Normal file
@@ -0,0 +1,6 @@
|
||||
.widget-backlight {
|
||||
background-color: #{"@noti-bg"};
|
||||
padding: 8px;
|
||||
margin: 8px;
|
||||
border-radius: 12px;
|
||||
}
|
18
data/style/widgets/buttons.scss
Normal file
18
data/style/widgets/buttons.scss
Normal file
@@ -0,0 +1,18 @@
|
||||
.widget-buttons-grid {
|
||||
padding: 8px;
|
||||
margin: 8px;
|
||||
border-radius: 12px;
|
||||
background-color: #{"@noti-bg"};
|
||||
}
|
||||
|
||||
.widget-buttons-grid > flowbox > flowboxchild > button {
|
||||
background: #{"@noti-bg"};
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
.widget-buttons-grid > flowbox > flowboxchild > button.toggle:checked {
|
||||
/* style given to the active toggle button */
|
||||
}
|
||||
|
||||
.widget-buttons-grid > flowbox > flowboxchild > button:hover {
|
||||
}
|
19
data/style/widgets/dnd.scss
Normal file
19
data/style/widgets/dnd.scss
Normal file
@@ -0,0 +1,19 @@
|
||||
.widget-dnd {
|
||||
color: #{"@text-color"};
|
||||
margin: 8px;
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
.widget-dnd > switch {
|
||||
font-size: initial;
|
||||
border-radius: 12px;
|
||||
background: #{"@noti-bg"};
|
||||
border: 1px solid #{"@noti-border-color"};
|
||||
box-shadow: none;
|
||||
}
|
||||
.widget-dnd > switch:checked {
|
||||
background: #{"@bg-selected"};
|
||||
}
|
||||
.widget-dnd > switch slider {
|
||||
background: #{"@noti-bg-hover"};
|
||||
border-radius: 12px;
|
||||
}
|
16
data/style/widgets/inhibitors.scss
Normal file
16
data/style/widgets/inhibitors.scss
Normal file
@@ -0,0 +1,16 @@
|
||||
.widget-inhibitors {
|
||||
margin: 8px;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
.widget-inhibitors > button {
|
||||
font-size: initial;
|
||||
color: #{"@text-color"};
|
||||
text-shadow: none;
|
||||
background: #{"@noti-bg"};
|
||||
border: 1px solid #{"@noti-border-color"};
|
||||
box-shadow: none;
|
||||
border-radius: 12px;
|
||||
}
|
||||
.widget-inhibitors > button:hover {
|
||||
background: #{"@noti-bg-hover"};
|
||||
}
|
6
data/style/widgets/label.scss
Normal file
6
data/style/widgets/label.scss
Normal file
@@ -0,0 +1,6 @@
|
||||
.widget-label {
|
||||
margin: 8px;
|
||||
}
|
||||
.widget-label > label {
|
||||
font-size: 1.1rem;
|
||||
}
|
26
data/style/widgets/menubar.scss
Normal file
26
data/style/widgets/menubar.scss
Normal file
@@ -0,0 +1,26 @@
|
||||
.widget-menubar > box > .menu-button-bar > button {
|
||||
border: none;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
/* .AnyName { Name defined in config after #
|
||||
background-color: @noti-bg;
|
||||
padding: 8px;
|
||||
margin: 8px;
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
.AnyName>button {
|
||||
background: transparent;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.AnyName>button:hover {
|
||||
background-color: @noti-bg-hover;
|
||||
} */
|
||||
|
||||
.topbar-buttons > button {
|
||||
/* Name defined in config after # */
|
||||
border: none;
|
||||
background: transparent;
|
||||
}
|
47
data/style/widgets/mpris.scss
Normal file
47
data/style/widgets/mpris.scss
Normal file
@@ -0,0 +1,47 @@
|
||||
@define-color mpris-album-art-overlay rgba(0, 0, 0, 0.55);
|
||||
@define-color mpris-button-hover rgba(0, 0, 0, 0.50);
|
||||
|
||||
$mpris-shadow: 0px 0px 10px rgba(0, 0, 0, 0.75);
|
||||
|
||||
.widget-mpris {
|
||||
/* The parent to all players */
|
||||
.widget-mpris-player {
|
||||
padding: 8px;
|
||||
padding: 16px;
|
||||
margin: 16px 20px;
|
||||
background-color: #{"@mpris-album-art-overlay"};
|
||||
border-radius: $border-radius;
|
||||
box-shadow: $mpris-shadow;
|
||||
|
||||
button:hover {
|
||||
/* The media player buttons (play, pause, next, etc...) */
|
||||
background: #{"@noti-bg-hover"};
|
||||
}
|
||||
|
||||
.widget-mpris-album-art {
|
||||
border-radius: $border-radius;
|
||||
box-shadow: $mpris-shadow;
|
||||
}
|
||||
.widget-mpris-title {
|
||||
font-weight: bold;
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
.widget-mpris-subtitle {
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
& > box > button {
|
||||
/* Change player control buttons */
|
||||
&:hover {
|
||||
background-color: #{"@mpris-button-hover"};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
& > box > button {
|
||||
/* Change player side buttons */
|
||||
}
|
||||
& > box > button:disabled {
|
||||
/* Change player side buttons insensitive */
|
||||
}
|
||||
}
|
17
data/style/widgets/title.scss
Normal file
17
data/style/widgets/title.scss
Normal file
@@ -0,0 +1,17 @@
|
||||
.widget-title {
|
||||
color: #{"@text-color"};
|
||||
margin: 8px;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
.widget-title > button {
|
||||
font-size: initial;
|
||||
color: #{"@text-color"};
|
||||
text-shadow: none;
|
||||
background: #{"@noti-bg"};
|
||||
border: 1px solid #{"@noti-border-color"};
|
||||
box-shadow: none;
|
||||
border-radius: 12px;
|
||||
}
|
||||
.widget-title > button:hover {
|
||||
background: #{"@noti-bg-hover"};
|
||||
}
|
18
data/style/widgets/volume.scss
Normal file
18
data/style/widgets/volume.scss
Normal file
@@ -0,0 +1,18 @@
|
||||
.widget-volume {
|
||||
background-color: #{"@noti-bg"};
|
||||
padding: 8px;
|
||||
margin: 8px;
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
.widget-volume > box > button {
|
||||
background: transparent;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.per-app-volume {
|
||||
background-color: #{"@noti-bg-alt"};
|
||||
padding: 4px 8px 8px 8px;
|
||||
margin: 0px 8px 8px 8px;
|
||||
border-radius: 12px;
|
||||
}
|
@@ -44,7 +44,7 @@ swaync - A simple notification daemon with a GTK gui for notifications and the c
|
||||
|
||||
*Left click button / actions*: Activate notification action
|
||||
|
||||
*Right click notification*: Close notification
|
||||
*Middle/Right click notification*: Close notification
|
||||
|
||||
# CONFIGURATION
|
||||
|
||||
|
@@ -68,6 +68,13 @@ config file to be able to detect config errors
|
||||
description: Layer of control center window relative to normal windows. ++
|
||||
background is below all windows, overlay is above all windows.
|
||||
|
||||
*control-center-exclusive-zone* ++
|
||||
type: bool ++
|
||||
default: true ++
|
||||
description: Whether or not the control center should follow the ++
|
||||
compositors exclusive zones. An example would be setting it to ++
|
||||
*false* to cover your panel/dock.
|
||||
|
||||
*notification-2fa-action* ++
|
||||
type: bool ++
|
||||
default: true ++
|
||||
@@ -370,6 +377,22 @@ config file to be able to detect config errors
|
||||
type: string ++
|
||||
default: "" ++
|
||||
description: "Command to be executed on click" ++
|
||||
type: ++
|
||||
type: string ++
|
||||
default: "normal" ++
|
||||
description: Type of the button. ++
|
||||
Toggle buttons receive the '.active' css class ++
|
||||
enum: ["normal", "toggle"] ++
|
||||
update-command: ++
|
||||
type: string ++
|
||||
default: "" ++
|
||||
description: "Command to be executed on visibility change of ++
|
||||
cc to update the active state of the toggle button (should ++
|
||||
echo true or false)" ++
|
||||
active: ++
|
||||
type: bool ++
|
||||
default: false ++
|
||||
description: Wether the toggle button is active as default or not ++
|
||||
description: A list of actions containing a label and a command ++
|
||||
description: A button to reveal a dropdown with action-buttons ++
|
||||
buttons#<name>: ++
|
||||
@@ -395,6 +418,24 @@ config file to be able to detect config errors
|
||||
type: string ++
|
||||
default: "" ++
|
||||
description: "Command to be executed on click" ++
|
||||
type: ++
|
||||
type: string ++
|
||||
default: "normal" ++
|
||||
description: Type of the button ++
|
||||
Toggle buttons receive the '.active' css class and an env ++
|
||||
variable "SWAYNC_TOGGLE_STATE" is set. See example usage in the ++
|
||||
default config.json ++
|
||||
enum: ["normal", "toggle"] ++
|
||||
update-command: ++
|
||||
type: string ++
|
||||
default: "" ++
|
||||
description: "Command to be executed on visibility change of ++
|
||||
cc to update the active state of the toggle button (should ++
|
||||
echo true or false)" ++
|
||||
active: ++
|
||||
type: bool ++
|
||||
default: false ++
|
||||
description: Wether the toggle button is active as default or not ++
|
||||
description: A list of actions containing a label and a command ++
|
||||
description: A list of buttons to be displayed in the menu-button-bar ++
|
||||
*buttons-grid*++
|
||||
@@ -415,6 +456,18 @@ config file to be able to detect config errors
|
||||
type: string ++
|
||||
default: "" ++
|
||||
description: "Command to be executed on click" ++
|
||||
type: ++
|
||||
type: string ++
|
||||
default: "normal" ++
|
||||
description: Type of the button ++
|
||||
Toggle buttons receive the '.active' css class and an env ++
|
||||
variable "SWAYNC_TOGGLE_STATE" is set. See example usage in the ++
|
||||
default config.json ++
|
||||
enum: ["normal", "toggle"] ++
|
||||
active: ++
|
||||
type: bool ++
|
||||
default: false ++
|
||||
description: Wether the toggle button is active as default or not ++
|
||||
description: A list of actions containing a label and a command ++
|
||||
description: A grid of buttons that execute shell commands ++
|
||||
#START pulse-audio
|
||||
|
@@ -1,5 +1,5 @@
|
||||
project('sway-notificaton-center', ['c', 'vala'],
|
||||
version: '0.9.0',
|
||||
version: '0.10.1',
|
||||
meson_version: '>= 0.59.0',
|
||||
default_options: [ 'warning_level=2' ],
|
||||
)
|
||||
@@ -9,6 +9,11 @@ add_project_arguments(['--enable-gobject-tracing'], language: 'vala')
|
||||
add_project_arguments(['--enable-checking'], language: 'vala')
|
||||
|
||||
i18n = import('i18n')
|
||||
gnome = import('gnome')
|
||||
|
||||
app_resources = []
|
||||
|
||||
config_path = join_paths(get_option('sysconfdir'), 'xdg', 'swaync')
|
||||
|
||||
subdir('data')
|
||||
subdir('src')
|
||||
|
150
src/animation/animation.vala
Normal file
150
src/animation/animation.vala
Normal file
@@ -0,0 +1,150 @@
|
||||
namespace SwayNotificationCenter {
|
||||
public class Animation : Object {
|
||||
|
||||
private unowned Gtk.Widget widget;
|
||||
|
||||
double value;
|
||||
|
||||
double value_from;
|
||||
double value_to;
|
||||
int64 duration;
|
||||
|
||||
int64 start_time;
|
||||
uint tick_cb_id;
|
||||
ulong unmap_cb_id;
|
||||
|
||||
unowned AnimationEasingFunc easing_func;
|
||||
unowned AnimationValueCallback value_cb;
|
||||
unowned AnimationDoneCallback done_cb;
|
||||
|
||||
bool is_done;
|
||||
|
||||
|
||||
public delegate void AnimationValueCallback (double value);
|
||||
|
||||
public delegate void AnimationDoneCallback ();
|
||||
|
||||
public delegate double AnimationEasingFunc (double t);
|
||||
|
||||
public Animation (Gtk.Widget widget, int64 duration,
|
||||
AnimationEasingFunc easing_func,
|
||||
AnimationValueCallback value_cb,
|
||||
AnimationDoneCallback done_cb) {
|
||||
this.widget = widget;
|
||||
this.duration = duration;
|
||||
this.easing_func = easing_func;
|
||||
this.value_cb = value_cb;
|
||||
this.done_cb = done_cb;
|
||||
|
||||
this.is_done = false;
|
||||
}
|
||||
|
||||
~Animation () {
|
||||
stop ();
|
||||
}
|
||||
|
||||
void set_value (double value) {
|
||||
this.value = value;
|
||||
this.value_cb (value);
|
||||
}
|
||||
|
||||
void done () {
|
||||
if (is_done) return;
|
||||
|
||||
is_done = true;
|
||||
done_cb ();
|
||||
}
|
||||
|
||||
bool tick_cb (Gtk.Widget widget, Gdk.FrameClock frame_clock) {
|
||||
int64 frame_time = frame_clock.get_frame_time () / 1000; /* ms */
|
||||
double t = (double) (frame_time - start_time) / duration;
|
||||
|
||||
if (t >= 1) {
|
||||
tick_cb_id = 0;
|
||||
|
||||
set_value (value_to);
|
||||
|
||||
if (unmap_cb_id != 0) {
|
||||
SignalHandler.disconnect (widget, unmap_cb_id);
|
||||
unmap_cb_id = 0;
|
||||
}
|
||||
|
||||
done ();
|
||||
|
||||
return Source.REMOVE;
|
||||
}
|
||||
|
||||
set_value (lerp (value_from, value_to, easing_func (t)));
|
||||
|
||||
return Source.CONTINUE;
|
||||
}
|
||||
|
||||
public void start (double from, double to) {
|
||||
this.value_from = from;
|
||||
this.value_to = to;
|
||||
this.value = from;
|
||||
this.is_done = false;
|
||||
|
||||
unowned Gtk.Settings ? gsettings = Gtk.Settings.get_default ();
|
||||
bool animations_enabled =
|
||||
gsettings != null ? gsettings.gtk_enable_animations : true;
|
||||
if (animations_enabled != true ||
|
||||
!widget.get_mapped () || duration <= 0) {
|
||||
set_value (value_to);
|
||||
|
||||
done ();
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
start_time = widget.get_frame_clock ().get_frame_time () / 1000;
|
||||
|
||||
if (tick_cb_id != 0) return;
|
||||
|
||||
unmap_cb_id =
|
||||
Signal.connect_swapped (widget, "unmap", (Callback) stop, this);
|
||||
tick_cb_id = widget.add_tick_callback (tick_cb);
|
||||
}
|
||||
|
||||
public void stop () {
|
||||
if (tick_cb_id != 0) {
|
||||
widget.remove_tick_callback (tick_cb_id);
|
||||
tick_cb_id = 0;
|
||||
}
|
||||
|
||||
if (unmap_cb_id != 0) {
|
||||
SignalHandler.disconnect (widget, unmap_cb_id);
|
||||
unmap_cb_id = 0;
|
||||
}
|
||||
|
||||
done ();
|
||||
}
|
||||
|
||||
public double get_value () {
|
||||
return value;
|
||||
}
|
||||
|
||||
public static double lerp (double a, double b, double t) {
|
||||
return a * (1.0 - t) + b * t;
|
||||
}
|
||||
|
||||
public static double ease_out_cubic (double t) {
|
||||
double p = t - 1;
|
||||
return p * p * p + 1;
|
||||
}
|
||||
|
||||
public static double ease_in_cubic (double t) {
|
||||
return t * t * t;
|
||||
}
|
||||
|
||||
public static double ease_in_out_cubic (double t) {
|
||||
double p = t * 2;
|
||||
|
||||
if (p < 1) return 0.5 * p * p * p;
|
||||
|
||||
p -= 2;
|
||||
|
||||
return 0.5 * (p * p * p + 2);
|
||||
}
|
||||
}
|
||||
}
|
@@ -77,8 +77,9 @@ private void print_help (string[] args) {
|
||||
|
||||
private void on_subscribe (uint count, bool dnd, bool cc_open, bool inhibited) {
|
||||
stdout.printf (
|
||||
"{ \"count\": %u, \"dnd\": %s, \"visible\": %s, \"inhibited\": %s }\n"
|
||||
.printf (count, dnd.to_string (), cc_open.to_string (), inhibited.to_string ()));
|
||||
"{ \"count\": %u, \"dnd\": %s, \"visible\": %s, \"inhibited\": %s }\n",
|
||||
count, dnd.to_string (), cc_open.to_string (), inhibited.to_string ());
|
||||
stdout.flush ();
|
||||
}
|
||||
|
||||
private void print_subscribe () {
|
||||
|
@@ -74,6 +74,17 @@
|
||||
"mpris": {
|
||||
"image-size": 96,
|
||||
"image-radius": 12
|
||||
},
|
||||
"buttons-grid": {
|
||||
"actions": [
|
||||
{
|
||||
"label": "直",
|
||||
"type": "toggle",
|
||||
"active": true,
|
||||
"command": "sh -c '[[ $SWAYNC_TOGGLE_STATE == true ]] && nmcli radio wifi on || nmcli radio wifi off'",
|
||||
"update_command": "sh -c '[[ $(nmcli radio wifi) == \"enabled\" ]] && echo true || echo false'"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -237,44 +237,18 @@ namespace SwayNotificationCenter {
|
||||
public ScriptRunOnType run_on { get; set; default = ScriptRunOnType.RECEIVE; }
|
||||
|
||||
public async bool run_script (NotifyParams param, out string msg) {
|
||||
msg = "";
|
||||
try {
|
||||
string[] spawn_env = Environ.get ();
|
||||
// Export env variables
|
||||
spawn_env += "SWAYNC_APP_NAME=%s".printf (param.app_name);
|
||||
spawn_env += "SWAYNC_SUMMARY=%s".printf (param.summary);
|
||||
spawn_env += "SWAYNC_BODY=%s".printf (param.body);
|
||||
spawn_env += "SWAYNC_URGENCY=%s".printf (param.urgency.to_string ());
|
||||
spawn_env += "SWAYNC_CATEGORY=%s".printf (param.category);
|
||||
spawn_env += "SWAYNC_ID=%s".printf (param.applied_id.to_string ());
|
||||
spawn_env += "SWAYNC_REPLACES_ID=%s".printf (param.replaces_id.to_string ());
|
||||
spawn_env += "SWAYNC_TIME=%s".printf (param.time.to_string ());
|
||||
spawn_env += "SWAYNC_DESKTOP_ENTRY=%s".printf (param.desktop_entry ?? "");
|
||||
string[] spawn_env = {};
|
||||
spawn_env += "SWAYNC_APP_NAME=%s".printf (param.app_name);
|
||||
spawn_env += "SWAYNC_SUMMARY=%s".printf (param.summary);
|
||||
spawn_env += "SWAYNC_BODY=%s".printf (param.body);
|
||||
spawn_env += "SWAYNC_URGENCY=%s".printf (param.urgency.to_string ());
|
||||
spawn_env += "SWAYNC_CATEGORY=%s".printf (param.category);
|
||||
spawn_env += "SWAYNC_ID=%s".printf (param.applied_id.to_string ());
|
||||
spawn_env += "SWAYNC_REPLACES_ID=%s".printf (param.replaces_id.to_string ());
|
||||
spawn_env += "SWAYNC_TIME=%s".printf (param.time.to_string ());
|
||||
spawn_env += "SWAYNC_DESKTOP_ENTRY=%s".printf (param.desktop_entry ?? "");
|
||||
|
||||
Pid child_pid;
|
||||
Process.spawn_async (
|
||||
"/",
|
||||
exec.split (" "),
|
||||
spawn_env,
|
||||
SpawnFlags.SEARCH_PATH | SpawnFlags.DO_NOT_REAP_CHILD,
|
||||
null,
|
||||
out child_pid);
|
||||
|
||||
// Close the child when the spawned process is idling
|
||||
int end_status = 0;
|
||||
ChildWatch.add (child_pid, (pid, status) => {
|
||||
Process.close_pid (pid);
|
||||
end_status = status;
|
||||
run_script.callback ();
|
||||
});
|
||||
// Waits until `run_script.callback()` is called above
|
||||
yield;
|
||||
return end_status == 0;
|
||||
} catch (Error e) {
|
||||
stderr.printf ("Run_Script Error: %s\n", e.message);
|
||||
msg = e.message;
|
||||
return false;
|
||||
}
|
||||
return yield Functions.execute_command (exec, spawn_env, out msg);
|
||||
}
|
||||
|
||||
public override bool matches_notification (NotifyParams param) {
|
||||
@@ -493,6 +467,10 @@ namespace SwayNotificationCenter {
|
||||
get; set; default = Layer.TOP;
|
||||
}
|
||||
|
||||
public bool control_center_exclusive_zone {
|
||||
get; set; default = true;
|
||||
}
|
||||
|
||||
/** Categories settings */
|
||||
public OrderedHashTable<Category> categories_settings {
|
||||
get;
|
||||
|
@@ -75,6 +75,11 @@
|
||||
"default": "none",
|
||||
"enum": ["background", "bottom", "top", "overlay", "none"]
|
||||
},
|
||||
"control-center-exclusive-zone": {
|
||||
"type": "boolean",
|
||||
"description": "Whether or not the control center should follow the compositors exclusive zones. An example would be setting it to \"false\" to cover your panel/dock.",
|
||||
"default": true
|
||||
},
|
||||
"notification-2fa-action": {
|
||||
"type": "boolean",
|
||||
"description": "If each notification should display a 'COPY \"1234\"' action",
|
||||
@@ -87,7 +92,7 @@
|
||||
},
|
||||
"notification-icon-size": {
|
||||
"type": "integer",
|
||||
"description": "The notification icon size (in pixels)",
|
||||
"description": "The notification icon size (in pixels). The app icon size is 1/3",
|
||||
"default": 64,
|
||||
"minimum": 16
|
||||
},
|
||||
@@ -152,7 +157,7 @@
|
||||
},
|
||||
"image-visibility": {
|
||||
"type": "string",
|
||||
"description": "An explanation about the purpose of this instance.",
|
||||
"description": "The notification image visibility when no icon is available.",
|
||||
"default": "when-available",
|
||||
"enum": ["always", "when-available", "never"]
|
||||
},
|
||||
@@ -397,8 +402,14 @@
|
||||
},
|
||||
"image-radius": {
|
||||
"type": "integer",
|
||||
"description": "The border radius of the album art",
|
||||
"description": "The border radius of the album art. Will be overriden by setting the border-radius in the style.css for the \".widget-mpris-album-art\" class",
|
||||
|
||||
"default": 12
|
||||
},
|
||||
"blur": {
|
||||
"type": "boolean",
|
||||
"description": "Appy the artwork as the MPRIS background and blur it",
|
||||
"default": true
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -422,6 +433,22 @@
|
||||
"type": "string",
|
||||
"description": "Command to be executed on click",
|
||||
"default": ""
|
||||
},
|
||||
"type": {
|
||||
"type": "string",
|
||||
"description": "Type of the button; toggle buttons receive the .active css class and an env variable 'SWAYNC_TOGGLE_STATE' is set. See example in the default config.json",
|
||||
"default": "normal",
|
||||
"enum": ["normal", "toggle"]
|
||||
},
|
||||
"update-command": {
|
||||
"type": "string",
|
||||
"description": "Command to be executed on visibility change of cc to update the active state of the toggle button (should echo true or false)",
|
||||
"default": ""
|
||||
},
|
||||
"active": {
|
||||
"type": "boolean",
|
||||
"description": "Wether the toggle button is active as default or not",
|
||||
"default": false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,4 +1,5 @@
|
||||
public class Constants {
|
||||
public const string VERSION = @VERSION@;
|
||||
public const string VERSIONNUM = @VERSION_NUM@;
|
||||
public const uint ANIMATION_DURATION = 400;
|
||||
}
|
||||
|
@@ -20,89 +20,84 @@
|
||||
<property name="vexpand">True</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<child>
|
||||
<object class="GtkScrolledWindow" id="scrolled_window">
|
||||
<object class="GtkBox" id="notifications_box">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="hscrollbar-policy">never</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<child>
|
||||
<object class="GtkViewport" id="viewport">
|
||||
<object class="GtkStack" id="stack">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="vexpand">True</property>
|
||||
<property name="shadow-type">none</property>
|
||||
<property name="transition-type">crossfade</property>
|
||||
<child>
|
||||
<object class="GtkStack" id="stack">
|
||||
<object class="GtkScrolledWindow" id="scrolled_window">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="hscrollbar-policy">never</property>
|
||||
<child>
|
||||
<placeholder/>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="name">notifications-list</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="transition-type">crossfade</property>
|
||||
<property name="halign">center</property>
|
||||
<property name="valign">center</property>
|
||||
<property name="hexpand">True</property>
|
||||
<property name="vexpand">True</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<property name="spacing">12</property>
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<object class="GtkImage">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="halign">center</property>
|
||||
<property name="valign">center</property>
|
||||
<property name="hexpand">True</property>
|
||||
<property name="vexpand">True</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<property name="spacing">12</property>
|
||||
<child>
|
||||
<object class="GtkImage">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="pixel-size">96</property>
|
||||
<property name="icon-name">preferences-system-notifications-symbolic</property>
|
||||
<property name="use-fallback">True</property>
|
||||
<property name="icon_size">0</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="label" translatable="yes">No Notifications</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<style>
|
||||
<class name="control-center-list-placeholder"/>
|
||||
</style>
|
||||
<property name="pixel-size">96</property>
|
||||
<property name="icon-name">preferences-system-notifications-symbolic</property>
|
||||
<property name="use-fallback">True</property>
|
||||
<property name="icon_size">0</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="name">notifications-placeholder</property>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkListBox" id="list_box">
|
||||
<object class="GtkLabel">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="valign">end</property>
|
||||
<property name="selection-mode">none</property>
|
||||
<property name="activate-on-single-click">False</property>
|
||||
<style>
|
||||
<class name="control-center-list"/>
|
||||
</style>
|
||||
<property name="label" translatable="yes">No Notifications</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="name">notifications-list</property>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<style>
|
||||
<class name="control-center-list-placeholder"/>
|
||||
</style>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="name">notifications-placeholder</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
|
@@ -1,18 +1,30 @@
|
||||
namespace SwayNotificationCenter {
|
||||
[GtkTemplate (ui = "/org/erikreider/sway-notification-center/controlCenter/controlCenter.ui")]
|
||||
public class ControlCenter : Gtk.ApplicationWindow {
|
||||
|
||||
[GtkChild]
|
||||
unowned Gtk.ScrolledWindow scrolled_window;
|
||||
[GtkChild]
|
||||
unowned Gtk.Viewport viewport;
|
||||
unowned Gtk.Box notifications_box;
|
||||
[GtkChild]
|
||||
unowned Gtk.Stack stack;
|
||||
[GtkChild]
|
||||
unowned Gtk.ListBox list_box;
|
||||
unowned Gtk.ScrolledWindow scrolled_window;
|
||||
FadedViewport viewport = new FadedViewport (20);
|
||||
Gtk.ListBox list_box = new Gtk.ListBox ();
|
||||
|
||||
[GtkChild]
|
||||
unowned Gtk.Box box;
|
||||
|
||||
unowned NotificationGroup ? expanded_group = null;
|
||||
private double fade_animation_progress = 1.0;
|
||||
private Animation ? notification_fade_animation;
|
||||
private double scroll_animation_progress = 1.0;
|
||||
private Animation ? scroll_animation;
|
||||
|
||||
HashTable<uint32, unowned NotificationGroup> noti_groups_id =
|
||||
new HashTable<uint32, unowned NotificationGroup> (direct_hash, direct_equal);
|
||||
/** NOTE: Only includes groups with ids with length of > 0 */
|
||||
HashTable<string, unowned NotificationGroup> noti_groups_name =
|
||||
new HashTable<string, unowned NotificationGroup> (str_hash, str_equal);
|
||||
|
||||
const string STACK_NOTIFICATIONS_PAGE = "notifications-list";
|
||||
const string STACK_PLACEHOLDER_PAGE = "notifications-placeholder";
|
||||
|
||||
@@ -23,9 +35,8 @@ namespace SwayNotificationCenter {
|
||||
private SwayncDaemon swaync_daemon;
|
||||
private NotiDaemon noti_daemon;
|
||||
|
||||
private uint list_position = 0;
|
||||
private int list_position = 0;
|
||||
|
||||
private double last_upper = 0;
|
||||
private bool list_reverse = false;
|
||||
private Gtk.Align list_align = Gtk.Align.START;
|
||||
|
||||
@@ -38,6 +49,18 @@ namespace SwayNotificationCenter {
|
||||
|
||||
this.swaync_daemon.reloading_css.connect (reload_notifications_style);
|
||||
|
||||
viewport.set_visible (true);
|
||||
viewport.set_vexpand (true);
|
||||
viewport.set_shadow_type (Gtk.ShadowType.NONE);
|
||||
scrolled_window.add (viewport);
|
||||
|
||||
list_box.set_visible (true);
|
||||
list_box.set_valign (Gtk.Align.END);
|
||||
list_box.set_selection_mode (Gtk.SelectionMode.NONE);
|
||||
list_box.set_activate_on_single_click (false);
|
||||
list_box.get_style_context ().add_class ("control-center-list");
|
||||
viewport.add (list_box);
|
||||
|
||||
if (swaync_daemon.use_layer_shell) {
|
||||
if (!GtkLayerShell.is_supported ()) {
|
||||
stderr.printf ("GTKLAYERSHELL IS NOT SUPPORTED!\n");
|
||||
@@ -54,8 +77,6 @@ namespace SwayNotificationCenter {
|
||||
GtkLayerShell.set_anchor (this, GtkLayerShell.Edge.BOTTOM, true);
|
||||
}
|
||||
|
||||
viewport.size_allocate.connect (size_alloc);
|
||||
|
||||
this.map.connect (() => {
|
||||
set_anchor ();
|
||||
// Wait until the layer has attached
|
||||
@@ -152,72 +173,10 @@ namespace SwayNotificationCenter {
|
||||
return true;
|
||||
});
|
||||
|
||||
this.key_press_event.connect ((w, event_key) => {
|
||||
if (this.get_focus () is Gtk.Entry) return false;
|
||||
if (event_key.type == Gdk.EventType.KEY_PRESS) {
|
||||
var children = list_box.get_children ();
|
||||
Notification noti = (Notification)
|
||||
list_box.get_focus_child ();
|
||||
switch (Gdk.keyval_name (event_key.keyval)) {
|
||||
case "Return":
|
||||
if (noti != null) noti.click_default_action ();
|
||||
break;
|
||||
case "Delete":
|
||||
case "BackSpace":
|
||||
if (noti != null) {
|
||||
if (children.length () == 0) break;
|
||||
if (list_reverse &&
|
||||
children.first ().data != noti) {
|
||||
list_position--;
|
||||
} else if (children.last ().data == noti) {
|
||||
if (list_position > 0) list_position--;
|
||||
}
|
||||
close_notification (noti.param.applied_id);
|
||||
}
|
||||
break;
|
||||
case "C":
|
||||
close_all_notifications ();
|
||||
break;
|
||||
case "D":
|
||||
try {
|
||||
swaync_daemon.toggle_dnd ();
|
||||
} catch (Error e) {
|
||||
error ("Error: %s\n", e.message);
|
||||
}
|
||||
break;
|
||||
case "Down":
|
||||
if (list_position + 1 < children.length ()) {
|
||||
++list_position;
|
||||
}
|
||||
break;
|
||||
case "Up":
|
||||
if (list_position > 0) --list_position;
|
||||
break;
|
||||
case "Home":
|
||||
list_position = 0;
|
||||
break;
|
||||
case "End":
|
||||
list_position = children.length () - 1;
|
||||
if (list_position == uint.MAX) list_position = 0;
|
||||
break;
|
||||
default:
|
||||
// Pressing 1-9 to activate a notification action
|
||||
for (int i = 0; i < 9; i++) {
|
||||
uint keyval = Gdk.keyval_from_name (
|
||||
(i + 1).to_string ());
|
||||
if (event_key.keyval == keyval) {
|
||||
if (noti != null) noti.click_alt_action (i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
navigate_list (list_position);
|
||||
}
|
||||
return false;
|
||||
});
|
||||
key_press_event.connect (key_press_event_cb);
|
||||
|
||||
// Switches the stack page depending on the
|
||||
stack.set_visible_child_name (STACK_PLACEHOLDER_PAGE);
|
||||
// Switches the stack page depending on the amount of notifications
|
||||
list_box.add.connect (() => {
|
||||
stack.set_visible_child_name (STACK_NOTIFICATIONS_PAGE);
|
||||
});
|
||||
@@ -228,6 +187,171 @@ namespace SwayNotificationCenter {
|
||||
});
|
||||
|
||||
add_widgets ();
|
||||
|
||||
notification_fade_animation = new Animation (this, Constants.ANIMATION_DURATION,
|
||||
Animation.ease_in_out_cubic,
|
||||
fade_animation_value_cb,
|
||||
fade_animation_done_cb);
|
||||
scroll_animation = new Animation (this, Constants.ANIMATION_DURATION,
|
||||
Animation.ease_in_out_cubic,
|
||||
scroll_animation_value_cb,
|
||||
scroll_animation_done_cb);
|
||||
list_box.draw.connect (list_box_draw_cb);
|
||||
}
|
||||
|
||||
void fade_animation_value_cb (double progress) {
|
||||
this.fade_animation_progress = progress;
|
||||
|
||||
this.queue_draw ();
|
||||
}
|
||||
|
||||
void fade_animation_done_cb () {}
|
||||
|
||||
void fade_animate (double to) {
|
||||
notification_fade_animation.stop ();
|
||||
notification_fade_animation.start (fade_animation_progress, to);
|
||||
}
|
||||
|
||||
void scroll_animation_value_cb (double progress) {
|
||||
this.scroll_animation_progress = progress;
|
||||
|
||||
// Scroll to the top of the group
|
||||
if (scroll_animation_progress > 0) {
|
||||
scrolled_window.vadjustment.set_value (scroll_animation_progress);
|
||||
}
|
||||
}
|
||||
|
||||
void scroll_animation_done_cb () {
|
||||
int y = expanded_group.get_relative_y (list_box);
|
||||
if (y > 0) {
|
||||
scrolled_window.vadjustment.set_value (y);
|
||||
}
|
||||
}
|
||||
|
||||
void scroll_animate (double to) {
|
||||
scroll_animation.stop ();
|
||||
scroll_animation.start (scroll_animation_progress, to);
|
||||
}
|
||||
|
||||
/// Fade non-expanded groups when one group is expanded
|
||||
private bool list_box_draw_cb (Cairo.Context cr) {
|
||||
Cairo.Pattern fade_gradient = new Cairo.Pattern.linear (0, 0, 0, 1);
|
||||
fade_gradient.add_color_stop_rgba (0, 1, 1, 1, 1 - fade_animation_progress - 0.5);
|
||||
|
||||
foreach (unowned Gtk.Widget widget in list_box.get_children ()) {
|
||||
Gtk.Allocation alloc;
|
||||
widget.get_allocated_size (out alloc, null);
|
||||
|
||||
cr.save ();
|
||||
cr.translate (0, alloc.y);
|
||||
|
||||
cr.push_group ();
|
||||
widget.draw (cr);
|
||||
|
||||
cr.scale (alloc.width, alloc.height);
|
||||
if (widget != expanded_group) {
|
||||
cr.set_source (fade_gradient);
|
||||
cr.rectangle (0, 0, alloc.width, alloc.height);
|
||||
cr.set_operator (Cairo.Operator.DEST_OUT);
|
||||
cr.fill ();
|
||||
}
|
||||
|
||||
cr.pop_group_to_source ();
|
||||
cr.paint ();
|
||||
|
||||
cr.restore ();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool key_press_event_cb (Gdk.EventKey event_key) {
|
||||
if (this.get_focus () is Gtk.Entry) return false;
|
||||
if (event_key.type == Gdk.EventType.KEY_PRESS) {
|
||||
var children = list_box.get_children ();
|
||||
var group = (NotificationGroup) list_box.get_focus_child ();
|
||||
switch (Gdk.keyval_name (event_key.keyval)) {
|
||||
case "Return":
|
||||
if (group != null) {
|
||||
var noti = group.get_latest_notification ();
|
||||
if (group.only_single_notification () && noti != null) {
|
||||
noti.click_default_action ();
|
||||
break;
|
||||
}
|
||||
group.on_expand_change (group.toggle_expanded ());
|
||||
}
|
||||
break;
|
||||
case "Delete":
|
||||
case "BackSpace":
|
||||
if (group != null) {
|
||||
int len = (int) children.length ();
|
||||
if (len == 0) break;
|
||||
// Add a delta so that we select the next notification
|
||||
// due to it not being gone from the list yet due to
|
||||
// the fade transition
|
||||
int delta = 2;
|
||||
if (list_reverse) {
|
||||
if (children.first ().data != group) {
|
||||
delta = 0;
|
||||
}
|
||||
list_position--;
|
||||
} else {
|
||||
if (list_position > 0) list_position--;
|
||||
if (children.last ().data == group) {
|
||||
delta = 0;
|
||||
}
|
||||
}
|
||||
var noti = group.get_latest_notification ();
|
||||
if (group.only_single_notification () && noti != null) {
|
||||
close_notification (noti.param.applied_id, true);
|
||||
break;
|
||||
}
|
||||
group.close_all_notifications ();
|
||||
navigate_list (list_position + delta);
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
case "C":
|
||||
close_all_notifications ();
|
||||
break;
|
||||
case "D":
|
||||
try {
|
||||
swaync_daemon.toggle_dnd ();
|
||||
} catch (Error e) {
|
||||
error ("Error: %s\n", e.message);
|
||||
}
|
||||
break;
|
||||
case "Down":
|
||||
if (list_position + 1 < children.length ()) {
|
||||
++list_position;
|
||||
}
|
||||
break;
|
||||
case "Up":
|
||||
if (list_position > 0) --list_position;
|
||||
break;
|
||||
case "Home":
|
||||
list_position = 0;
|
||||
break;
|
||||
case "End":
|
||||
list_position = ((int) children.length ()) - 1;
|
||||
if (list_position == uint.MAX) list_position = 0;
|
||||
break;
|
||||
default:
|
||||
// Pressing 1-9 to activate a notification action
|
||||
for (int i = 0; i < 9; i++) {
|
||||
uint keyval = Gdk.keyval_from_name (
|
||||
(i + 1).to_string ());
|
||||
if (event_key.keyval == keyval && group != null) {
|
||||
var noti = group.get_latest_notification ();
|
||||
noti.click_alt_action (i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
navigate_list (list_position);
|
||||
}
|
||||
// Override the builtin list navigation
|
||||
return true;
|
||||
}
|
||||
|
||||
/** Adds all custom widgets. Removes previous widgets */
|
||||
@@ -242,11 +366,11 @@ namespace SwayNotificationCenter {
|
||||
if (w.length == 0) w = DEFAULT_WIDGETS;
|
||||
bool has_notification = false;
|
||||
foreach (string key in w) {
|
||||
// Reposition the scrolled_window
|
||||
// Reposition the notifications_box
|
||||
if (key == "notifications") {
|
||||
has_notification = true;
|
||||
uint pos = box.get_children ().length ();
|
||||
box.reorder_child (scrolled_window, (int) (pos > 0 ? --pos : 0));
|
||||
box.reorder_child (notifications_box, (int) (pos > 0 ? --pos : 0));
|
||||
continue;
|
||||
}
|
||||
// Add the widget if it is valid
|
||||
@@ -260,23 +384,22 @@ namespace SwayNotificationCenter {
|
||||
if (!has_notification) {
|
||||
warning ("Notification widget not included in \"widgets\" config. Using default bottom position");
|
||||
uint pos = box.get_children ().length ();
|
||||
box.reorder_child (scrolled_window, (int) (pos > 0 ? --pos : 0));
|
||||
box.reorder_child (notifications_box, (int) (pos > 0 ? --pos : 0));
|
||||
}
|
||||
}
|
||||
|
||||
/** Resets the UI positions */
|
||||
private void set_anchor () {
|
||||
if (swaync_daemon.use_layer_shell) {
|
||||
// Set the exlusive zone
|
||||
int exclusive_zone = ConfigModel.instance.control_center_exclusive_zone ? 0 : 100;
|
||||
GtkLayerShell.set_exclusive_zone (this, exclusive_zone);
|
||||
// Grabs the keyboard input until closed
|
||||
bool keyboard_shortcuts = ConfigModel.instance.keyboard_shortcuts;
|
||||
#if HAVE_LATEST_GTK_LAYER_SHELL
|
||||
var mode = keyboard_shortcuts ?
|
||||
GtkLayerShell.KeyboardMode.EXCLUSIVE :
|
||||
GtkLayerShell.KeyboardMode.NONE;
|
||||
GtkLayerShell.set_keyboard_mode (this, mode);
|
||||
#else
|
||||
GtkLayerShell.set_keyboard_interactivity (this, keyboard_shortcuts);
|
||||
#endif
|
||||
|
||||
// Set layer
|
||||
GtkLayerShell.Layer layer;
|
||||
@@ -351,28 +474,37 @@ namespace SwayNotificationCenter {
|
||||
box.set_valign (align_y);
|
||||
|
||||
list_box.set_valign (list_align);
|
||||
list_box.set_sort_func ((w1, w2) => {
|
||||
var a = (Notification) w1;
|
||||
var b = (Notification) w2;
|
||||
if (a == null || b == null) return 0;
|
||||
// Sort the list in reverse if needed
|
||||
if (a.param.time == b.param.time) return 0;
|
||||
int val = list_reverse ? 1 : -1;
|
||||
return a.param.time > b.param.time ? val : val * -1;
|
||||
});
|
||||
list_box.set_sort_func (list_box_sort_func);
|
||||
|
||||
// Always set the size request in all events.
|
||||
box.set_size_request (ConfigModel.instance.control_center_width,
|
||||
ConfigModel.instance.control_center_height);
|
||||
}
|
||||
|
||||
private void size_alloc () {
|
||||
var adj = viewport.vadjustment;
|
||||
double upper = adj.get_upper ();
|
||||
if (last_upper < upper) {
|
||||
scroll_to_start (list_reverse);
|
||||
/**
|
||||
* Returns < 0 if row1 should be before row2, 0 if they are equal
|
||||
* and > 0 otherwise
|
||||
*/
|
||||
private int list_box_sort_func (Gtk.ListBoxRow row1, Gtk.ListBoxRow row2) {
|
||||
int val = list_reverse ? 1 : -1;
|
||||
|
||||
var a_group = (NotificationGroup) row1;
|
||||
var b_group = (NotificationGroup) row2;
|
||||
|
||||
// Check urgency before time
|
||||
var a_urgency = a_group.get_is_urgent ();
|
||||
var b_urgency = b_group.get_is_urgent ();
|
||||
if (a_urgency != b_urgency) {
|
||||
return a_urgency ? val : val * -1;
|
||||
}
|
||||
last_upper = upper;
|
||||
|
||||
// Check time
|
||||
var a_time = a_group.get_time ();
|
||||
var b_time = b_group.get_time ();
|
||||
if (a_time < 0 || b_time < 0) return 0;
|
||||
// Sort the list in reverse if needed
|
||||
if (a_time == b_time) return 0;
|
||||
return a_time > b_time ? val : val * -1;
|
||||
}
|
||||
|
||||
private void scroll_to_start (bool reverse) {
|
||||
@@ -384,20 +516,26 @@ namespace SwayNotificationCenter {
|
||||
}
|
||||
|
||||
public uint notification_count () {
|
||||
return list_box.get_children ().length ();
|
||||
uint count = 0;
|
||||
foreach (unowned Gtk.Widget widget in list_box.get_children ()) {
|
||||
if (widget is NotificationGroup) {
|
||||
count += ((NotificationGroup) widget).get_num_notifications ();
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
public void close_all_notifications () {
|
||||
foreach (var w in list_box.get_children ()) {
|
||||
Notification noti = (Notification) w;
|
||||
if (noti != null) noti.close_notification (false);
|
||||
NotificationGroup group = (NotificationGroup) w;
|
||||
if (group != null) group.close_all_notifications ();
|
||||
}
|
||||
|
||||
try {
|
||||
swaync_daemon.subscribe_v2 (notification_count (),
|
||||
swaync_daemon.get_dnd (),
|
||||
get_visibility (),
|
||||
swaync_daemon.inhibited);
|
||||
swaync_daemon.get_dnd (),
|
||||
get_visibility (),
|
||||
swaync_daemon.inhibited);
|
||||
} catch (Error e) {
|
||||
stderr.printf (e.message + "\n");
|
||||
}
|
||||
@@ -407,11 +545,20 @@ namespace SwayNotificationCenter {
|
||||
}
|
||||
}
|
||||
|
||||
private void navigate_list (uint i) {
|
||||
var widget = list_box.get_children ().nth_data (i);
|
||||
private void navigate_list (int i) {
|
||||
unowned Gtk.Widget ? widget = list_box.get_children ().nth_data (i);
|
||||
if (widget == null) {
|
||||
// Try getting the last widget
|
||||
if (list_reverse) {
|
||||
widget = list_box.get_children ().nth_data (0);
|
||||
} else {
|
||||
int len = ((int) list_box.get_children ().length ()) - 1;
|
||||
widget = list_box.get_children ().nth_data (len);
|
||||
}
|
||||
}
|
||||
if (widget != null) {
|
||||
list_box.set_focus_child (widget);
|
||||
widget.grab_focus ();
|
||||
list_box.set_focus_child (widget);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -424,20 +571,24 @@ namespace SwayNotificationCenter {
|
||||
if (this.visible) {
|
||||
// Focus the first notification
|
||||
list_position = list_reverse ?
|
||||
(list_box.get_children ().length () - 1) : 0;
|
||||
(((int) list_box.get_children ().length ()) - 1) : 0;
|
||||
if (list_position == uint.MAX) list_position = 0;
|
||||
|
||||
list_box.grab_focus ();
|
||||
navigate_list (list_position);
|
||||
foreach (var w in list_box.get_children ()) {
|
||||
var noti = (Notification) w;
|
||||
if (noti != null) noti.set_time ();
|
||||
var group = (NotificationGroup) w;
|
||||
if (group != null) group.update ();
|
||||
}
|
||||
this.get_style_context ().add_class ("open");
|
||||
}
|
||||
else {
|
||||
this.get_style_context ().remove_class ("open");
|
||||
}
|
||||
swaync_daemon.subscribe_v2 (notification_count (),
|
||||
noti_daemon.dnd,
|
||||
this.visible,
|
||||
swaync_daemon.inhibited);
|
||||
noti_daemon.dnd,
|
||||
this.visible,
|
||||
swaync_daemon.inhibited);
|
||||
}
|
||||
|
||||
public bool toggle_visibility () {
|
||||
@@ -455,41 +606,114 @@ namespace SwayNotificationCenter {
|
||||
on_visibility_change ();
|
||||
}
|
||||
|
||||
public void close_notification (uint32 id, bool replaces = false) {
|
||||
foreach (var w in list_box.get_children ()) {
|
||||
public void close_notification (uint32 id, bool dismiss) {
|
||||
unowned NotificationGroup group = null;
|
||||
if (!noti_groups_id.lookup_extended (id, null, out group))return;
|
||||
foreach (var w in group.get_notifications ()) {
|
||||
var noti = (Notification) w;
|
||||
if (noti != null && noti.param.applied_id == id) {
|
||||
if (replaces) {
|
||||
if (!dismiss) {
|
||||
noti.remove_noti_timeout ();
|
||||
noti.destroy ();
|
||||
} else {
|
||||
noti.close_notification (false);
|
||||
list_box.remove (w);
|
||||
group.remove_notification (noti);
|
||||
}
|
||||
noti_groups_id.remove (id);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (group.is_empty ()) {
|
||||
if (group.name_id.length > 0) {
|
||||
noti_groups_name.remove (group.name_id);
|
||||
}
|
||||
if (expanded_group == group) {
|
||||
expanded_group = null;
|
||||
fade_animate (1);
|
||||
}
|
||||
group.destroy ();
|
||||
}
|
||||
}
|
||||
|
||||
public void add_notification (NotifyParams param,
|
||||
NotiDaemon noti_daemon) {
|
||||
public void replace_notification (uint32 id, NotifyParams new_params) {
|
||||
unowned NotificationGroup group = null;
|
||||
if (noti_groups_id.lookup_extended (id, null, out group)) {
|
||||
foreach (var w in group.get_notifications ()) {
|
||||
var noti = (Notification) w;
|
||||
if (noti != null && noti.param.applied_id == id) {
|
||||
noti_groups_id.remove (id);
|
||||
noti_groups_id.set (new_params.applied_id, group);
|
||||
noti.replace_notification (new_params);
|
||||
// Position the notification in the beginning of the list
|
||||
list_box.invalidate_sort ();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add a new notification if the old one isn't visible
|
||||
add_notification (new_params);
|
||||
}
|
||||
|
||||
public void add_notification (NotifyParams param) {
|
||||
var noti = new Notification.regular (param,
|
||||
noti_daemon,
|
||||
NotificationType.CONTROL_CENTER);
|
||||
noti.grab_focus.connect ((w) => {
|
||||
uint i = list_box.get_children ().index (w);
|
||||
int i = list_box.get_children ().index (w);
|
||||
if (list_position != uint.MAX && list_position != i) {
|
||||
list_position = i;
|
||||
}
|
||||
});
|
||||
noti.set_time ();
|
||||
list_box.add (noti);
|
||||
|
||||
NotificationGroup ? group = null;
|
||||
if (param.name_id.length > 0) {
|
||||
noti_groups_name.lookup_extended (param.name_id, null, out group);
|
||||
}
|
||||
if (group == null) {
|
||||
group = new NotificationGroup (param.name_id, param.display_name);
|
||||
// Collapse other groups on expand
|
||||
group.on_expand_change.connect ((expanded) => {
|
||||
if (!expanded) {
|
||||
fade_animate (1);
|
||||
foreach (unowned Gtk.Widget child in list_box.get_children ()) {
|
||||
child.set_sensitive (true);
|
||||
}
|
||||
return;
|
||||
}
|
||||
expanded_group = group;
|
||||
expanded_group.set_sensitive (true);
|
||||
fade_animate (0);
|
||||
int y = expanded_group.get_relative_y (list_box);
|
||||
if (y > 0) {
|
||||
scroll_animate (y);
|
||||
}
|
||||
foreach (unowned Gtk.Widget child in list_box.get_children ()) {
|
||||
NotificationGroup g = (NotificationGroup) child;
|
||||
if (g != null && g != group) {
|
||||
g.set_expanded (false);
|
||||
if (g.only_single_notification ()) {
|
||||
g.set_sensitive (false);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
if (param.name_id.length > 0) {
|
||||
noti_groups_name.set (param.name_id, group);
|
||||
}
|
||||
list_box.add (group);
|
||||
}
|
||||
noti_groups_id.set (param.applied_id, group);
|
||||
|
||||
group.add_notification (noti);
|
||||
list_box.invalidate_sort ();
|
||||
scroll_to_start (list_reverse);
|
||||
try {
|
||||
swaync_daemon.subscribe_v2 (notification_count (),
|
||||
swaync_daemon.get_dnd (),
|
||||
get_visibility (),
|
||||
swaync_daemon.inhibited);
|
||||
swaync_daemon.get_dnd (),
|
||||
get_visibility (),
|
||||
swaync_daemon.inhibited);
|
||||
} catch (Error e) {
|
||||
stderr.printf (e.message + "\n");
|
||||
}
|
||||
@@ -506,8 +730,13 @@ namespace SwayNotificationCenter {
|
||||
/** Forces each notification EventBox to reload its style_context #27 */
|
||||
private void reload_notifications_style () {
|
||||
foreach (var c in list_box.get_children ()) {
|
||||
Notification noti = (Notification) c;
|
||||
if (noti != null) noti.reload_style_context ();
|
||||
NotificationGroup group = (NotificationGroup) c;
|
||||
if (group != null) {
|
||||
foreach (unowned var widget in group.get_notifications ()) {
|
||||
Notification noti = (Notification) widget;
|
||||
noti.reload_style_context ();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -52,7 +52,7 @@ namespace SwayNotificationCenter.Widgets {
|
||||
slider.set_draw_value (false);
|
||||
slider.set_round_digits (0);
|
||||
slider.value_changed.connect (() => {
|
||||
this.client.set_brightness ((float) slider.get_value ());
|
||||
this.client.set_brightness.begin ((float) slider.get_value ());
|
||||
slider.tooltip_text = ((int) slider.get_value ()).to_string ();
|
||||
});
|
||||
|
||||
|
@@ -3,7 +3,7 @@ namespace SwayNotificationCenter.Widgets {
|
||||
|
||||
[DBus (name = "org.freedesktop.login1.Session")]
|
||||
interface Login1 : Object {
|
||||
public abstract void set_brightness (string subsystem,
|
||||
public abstract async void set_brightness (string subsystem,
|
||||
string name, uint32 brightness) throws GLib.Error;
|
||||
}
|
||||
|
||||
@@ -77,14 +77,14 @@ namespace SwayNotificationCenter.Widgets {
|
||||
if (monitor != null) monitor.cancel ();
|
||||
}
|
||||
|
||||
public void set_brightness (float percent) {
|
||||
public async void set_brightness (float percent) {
|
||||
this.close ();
|
||||
try {
|
||||
if (subsystem == "backlight") {
|
||||
int actual = calc_actual (percent);
|
||||
login1.set_brightness (subsystem, device, actual);
|
||||
login1.set_brightness.begin (subsystem, device, actual);
|
||||
} else {
|
||||
login1.set_brightness (subsystem, device, (uint32) percent);
|
||||
login1.set_brightness.begin (subsystem, device, (uint32) percent);
|
||||
}
|
||||
} catch (Error e) {
|
||||
error ("Error %s\n", e.message);
|
||||
|
@@ -1,5 +1,3 @@
|
||||
using Posix;
|
||||
|
||||
namespace SwayNotificationCenter.Widgets {
|
||||
public abstract class BaseWidget : Gtk.Box {
|
||||
public abstract string widget_name { get; }
|
||||
@@ -16,6 +14,20 @@ namespace SwayNotificationCenter.Widgets {
|
||||
public unowned SwayncDaemon swaync_daemon;
|
||||
public unowned NotiDaemon noti_daemon;
|
||||
|
||||
public enum ButtonType {
|
||||
TOGGLE,
|
||||
NORMAL;
|
||||
|
||||
public static ButtonType parse (string value) {
|
||||
switch (value) {
|
||||
case "toggle":
|
||||
return ButtonType.TOGGLE;
|
||||
default:
|
||||
return ButtonType.NORMAL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected BaseWidget (string suffix, SwayncDaemon swaync_daemon, NotiDaemon noti_daemon) {
|
||||
this.suffix = suffix;
|
||||
this.key = widget_name + (suffix.length > 0 ? "#%s".printf (suffix) : "");
|
||||
@@ -93,25 +105,26 @@ namespace SwayNotificationCenter.Widgets {
|
||||
for (int i = 0; i < actions.get_length (); i++) {
|
||||
string label = actions.get_object_element (i).get_string_member_with_default ("label", "label");
|
||||
string command = actions.get_object_element (i).get_string_member_with_default ("command", "");
|
||||
string t = actions.get_object_element (i).get_string_member_with_default ("type", "normal");
|
||||
ButtonType type = ButtonType.parse (t);
|
||||
string update_command =
|
||||
actions.get_object_element (i).get_string_member_with_default ("update-command", "");
|
||||
bool active = actions.get_object_element (i).get_boolean_member_with_default ("active", false);
|
||||
res[i] = Action () {
|
||||
label = label,
|
||||
command = command
|
||||
command = command,
|
||||
type = type,
|
||||
update_command = update_command,
|
||||
active = active
|
||||
};
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
protected void execute_command (string cmd) {
|
||||
pid_t pid;
|
||||
int status;
|
||||
if ((pid = fork ()) < 0) {
|
||||
perror ("fork()");
|
||||
}
|
||||
if (pid == 0) { // Child process
|
||||
execl ("/bin/sh", "sh", "-c", cmd);
|
||||
exit (EXIT_FAILURE); // should not return from execl
|
||||
}
|
||||
waitpid (pid, out status, 1);
|
||||
|
||||
protected async void execute_command (string cmd, string[] env_additions = {}) {
|
||||
string msg = "";
|
||||
yield Functions.execute_command (cmd, env_additions, out msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -10,6 +10,7 @@ namespace SwayNotificationCenter.Widgets {
|
||||
}
|
||||
|
||||
Action[] actions;
|
||||
List<ToggleButton> toggle_buttons;
|
||||
|
||||
public ButtonsGrid (string suffix, SwayncDaemon swaync_daemon, NotiDaemon noti_daemon) {
|
||||
base (suffix, swaync_daemon, noti_daemon);
|
||||
@@ -26,14 +27,29 @@ namespace SwayNotificationCenter.Widgets {
|
||||
|
||||
// add action to container
|
||||
foreach (var act in actions) {
|
||||
Gtk.Button b = new Gtk.Button.with_label (act.label);
|
||||
|
||||
b.clicked.connect (() => execute_command (act.command));
|
||||
|
||||
container.insert (b, -1);
|
||||
switch (act.type) {
|
||||
case ButtonType.TOGGLE:
|
||||
ToggleButton tb = new ToggleButton (act.label, act.command, act.update_command, act.active);
|
||||
container.insert (tb, -1);
|
||||
toggle_buttons.append (tb);
|
||||
break;
|
||||
default:
|
||||
Gtk.Button b = new Gtk.Button.with_label (act.label);
|
||||
b.clicked.connect (() => execute_command.begin (act.command));
|
||||
container.insert (b, -1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
show_all ();
|
||||
}
|
||||
|
||||
public override void on_cc_visibility_change (bool value) {
|
||||
if (value) {
|
||||
foreach (var tb in toggle_buttons) {
|
||||
tb.on_update.begin ();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -25,6 +25,9 @@ namespace SwayNotificationCenter.Widgets {
|
||||
public struct Action {
|
||||
string ? label;
|
||||
string ? command;
|
||||
BaseWidget.ButtonType ? type;
|
||||
string ? update_command;
|
||||
bool ? active;
|
||||
}
|
||||
|
||||
public class Menubar : BaseWidget {
|
||||
@@ -38,6 +41,7 @@ namespace SwayNotificationCenter.Widgets {
|
||||
Gtk.Box topbar_container;
|
||||
|
||||
List<ConfigObject ?> menu_objects;
|
||||
List<ToggleButton> toggle_buttons;
|
||||
|
||||
public Menubar (string suffix, SwayncDaemon swaync_daemon, NotiDaemon noti_daemon) {
|
||||
base (suffix, swaync_daemon, noti_daemon);
|
||||
@@ -74,11 +78,18 @@ namespace SwayNotificationCenter.Widgets {
|
||||
if (obj.name != null) container.get_style_context ().add_class (obj.name);
|
||||
|
||||
foreach (Action a in obj.actions) {
|
||||
Gtk.Button b = new Gtk.Button.with_label (a.label);
|
||||
|
||||
b.clicked.connect (() => execute_command (a.command));
|
||||
|
||||
container.add (b);
|
||||
switch (a.type) {
|
||||
case ButtonType.TOGGLE:
|
||||
ToggleButton tb = new ToggleButton (a.label, a.command, a.update_command, a.active);
|
||||
container.add (tb);
|
||||
toggle_buttons.append (tb);
|
||||
break;
|
||||
default:
|
||||
Gtk.Button b = new Gtk.Button.with_label (a.label);
|
||||
b.clicked.connect (() => execute_command.begin (a.command));
|
||||
container.add (b);
|
||||
break;
|
||||
}
|
||||
}
|
||||
switch (obj.position) {
|
||||
case Position.LEFT:
|
||||
@@ -110,9 +121,18 @@ namespace SwayNotificationCenter.Widgets {
|
||||
});
|
||||
|
||||
foreach (var a in obj.actions) {
|
||||
Gtk.Button b = new Gtk.Button.with_label (a.label);
|
||||
b.clicked.connect (() => execute_command (a.command));
|
||||
menu.pack_start (b, true, true, 0);
|
||||
switch (a.type) {
|
||||
case ButtonType.TOGGLE:
|
||||
ToggleButton tb = new ToggleButton (a.label, a.command, a.update_command, a.active);
|
||||
menu.pack_start (tb, true, true, 0);
|
||||
toggle_buttons.append (tb);
|
||||
break;
|
||||
default:
|
||||
Gtk.Button b = new Gtk.Button.with_label (a.label);
|
||||
b.clicked.connect (() => execute_command.begin (a.command));
|
||||
menu.pack_start (b, true, true, 0);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
switch (obj.position) {
|
||||
@@ -210,6 +230,10 @@ namespace SwayNotificationCenter.Widgets {
|
||||
foreach (var obj in menu_objects) {
|
||||
obj.revealer ?.set_reveal_child (false);
|
||||
}
|
||||
} else {
|
||||
foreach (var tb in toggle_buttons) {
|
||||
tb.on_update.begin ();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -2,6 +2,7 @@ namespace SwayNotificationCenter.Widgets.Mpris {
|
||||
public struct Config {
|
||||
int image_size;
|
||||
int image_radius;
|
||||
bool blur;
|
||||
}
|
||||
|
||||
public class Mpris : BaseWidget {
|
||||
@@ -11,6 +12,8 @@ namespace SwayNotificationCenter.Widgets.Mpris {
|
||||
}
|
||||
}
|
||||
|
||||
private const int FADE_WIDTH = 20;
|
||||
|
||||
const string MPRIS_PREFIX = "org.mpris.MediaPlayer2.";
|
||||
HashTable<string, MprisPlayer> players = new HashTable<string, MprisPlayer> (str_hash, str_equal);
|
||||
|
||||
@@ -26,6 +29,7 @@ namespace SwayNotificationCenter.Widgets.Mpris {
|
||||
Config mpris_config = Config () {
|
||||
image_size = 96,
|
||||
image_radius = 12,
|
||||
blur = true,
|
||||
};
|
||||
|
||||
public Mpris (string suffix, SwayncDaemon swaync_daemon, NotiDaemon noti_daemon) {
|
||||
@@ -53,9 +57,8 @@ namespace SwayNotificationCenter.Widgets.Mpris {
|
||||
carousel = new Hdy.Carousel () {
|
||||
visible = true,
|
||||
};
|
||||
#if HAVE_LATEST_LIBHANDY
|
||||
carousel.allow_scroll_wheel = true;
|
||||
#endif
|
||||
carousel.draw.connect (carousel_draw_cb);
|
||||
carousel.page_changed.connect ((index) => {
|
||||
GLib.List<weak Gtk.Widget> children = carousel.get_children ();
|
||||
int children_length = (int) children.length ();
|
||||
@@ -91,6 +94,11 @@ namespace SwayNotificationCenter.Widgets.Mpris {
|
||||
// Clamp the radius
|
||||
mpris_config.image_radius = mpris_config.image_radius.clamp (
|
||||
0, (int) (mpris_config.image_size * 0.5));
|
||||
|
||||
// Get blur
|
||||
bool blur_found;
|
||||
bool? blur = get_prop<bool> (config, "blur", out blur_found);
|
||||
if (blur_found) mpris_config.blur = blur;
|
||||
}
|
||||
|
||||
hide ();
|
||||
@@ -101,6 +109,51 @@ namespace SwayNotificationCenter.Widgets.Mpris {
|
||||
}
|
||||
}
|
||||
|
||||
private bool carousel_draw_cb (Cairo.Context cr) {
|
||||
Gtk.Allocation alloc;
|
||||
carousel.get_allocated_size (out alloc, null);
|
||||
|
||||
Cairo.Pattern left_fade_gradient = new Cairo.Pattern.linear (0, 0, 1, 0);
|
||||
left_fade_gradient.add_color_stop_rgba (0, 1, 1, 1, 1);
|
||||
left_fade_gradient.add_color_stop_rgba (1, 1, 1, 1, 0);
|
||||
Cairo.Pattern right_fade_gradient = new Cairo.Pattern.linear (0, 0, 1, 0);
|
||||
right_fade_gradient.add_color_stop_rgba (0, 1, 1, 1, 0);
|
||||
right_fade_gradient.add_color_stop_rgba (1, 1, 1, 1, 1);
|
||||
|
||||
cr.save ();
|
||||
cr.push_group ();
|
||||
|
||||
// Draw widgets
|
||||
carousel.draw.disconnect (carousel_draw_cb);
|
||||
carousel.draw (cr);
|
||||
carousel.draw.connect (carousel_draw_cb);
|
||||
|
||||
/// Draw vertical fade
|
||||
|
||||
// Top fade
|
||||
cr.save ();
|
||||
cr.scale (FADE_WIDTH, alloc.height);
|
||||
cr.rectangle (0, 0, FADE_WIDTH, alloc.height);
|
||||
cr.set_source (left_fade_gradient);
|
||||
cr.set_operator (Cairo.Operator.DEST_OUT);
|
||||
cr.fill ();
|
||||
cr.restore ();
|
||||
// Bottom fade
|
||||
cr.save ();
|
||||
cr.translate (alloc.width - FADE_WIDTH, 0);
|
||||
cr.scale (FADE_WIDTH, alloc.height);
|
||||
cr.rectangle (0, 0, FADE_WIDTH, alloc.height);
|
||||
cr.set_source (right_fade_gradient);
|
||||
cr.set_operator (Cairo.Operator.DEST_OUT);
|
||||
cr.fill ();
|
||||
cr.restore ();
|
||||
|
||||
cr.pop_group_to_source ();
|
||||
cr.paint ();
|
||||
cr.restore ();
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Forces the carousel to reload its style_context.
|
||||
* Fixes carousel items not redrawing when window isn't visible.
|
||||
|
@@ -22,6 +22,9 @@
|
||||
<property name="icon-name">audio-x-generic-symbolic</property>
|
||||
<property name="use-fallback">True</property>
|
||||
<property name="icon_size">0</property>
|
||||
<style>
|
||||
<class name="widget-mpris-album-art"/>
|
||||
</style>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
|
@@ -38,6 +38,9 @@ namespace SwayNotificationCenter.Widgets.Mpris {
|
||||
private string prev_art_url;
|
||||
private DesktopAppInfo ? desktop_entry = null;
|
||||
|
||||
private Gdk.Pixbuf ? album_art_pixbuf = null;
|
||||
private Granite.Drawing.BufferSurface ? blurred_cover_surface = null;
|
||||
|
||||
private unowned Config mpris_config;
|
||||
|
||||
public MprisPlayer (MprisSource source, Config mpris_config) {
|
||||
@@ -93,12 +96,84 @@ namespace SwayNotificationCenter.Widgets.Mpris {
|
||||
update_buttons (source.media_player.metadata);
|
||||
});
|
||||
});
|
||||
album_art.set_pixel_size (mpris_config.image_size);
|
||||
}
|
||||
|
||||
public void before_destroy () {
|
||||
source.properties_changed.disconnect (properties_changed);
|
||||
}
|
||||
|
||||
public override bool draw (Cairo.Context cr) {
|
||||
unowned Gdk.Window ? window = get_window ();
|
||||
if (!mpris_config.blur || window == null ||
|
||||
!(album_art_pixbuf is Gdk.Pixbuf)) {
|
||||
return base.draw (cr);
|
||||
}
|
||||
|
||||
const double DEGREES = Math.PI / 180.0;
|
||||
unowned Gtk.StyleContext style_ctx = this.get_style_context ();
|
||||
unowned Gtk.StateFlags state = style_ctx.get_state ();
|
||||
Gtk.Border border = style_ctx.get_border (state);
|
||||
Gtk.Border margin = style_ctx.get_margin (state);
|
||||
Value radius_value = style_ctx.get_property (
|
||||
Gtk.STYLE_PROPERTY_BORDER_RADIUS, state);
|
||||
int radius = 0;
|
||||
if (!radius_value.holds (Type.INT) ||
|
||||
(radius = radius_value.get_int ()) == 0) {
|
||||
radius = mpris_config.image_radius;
|
||||
}
|
||||
int scale = style_ctx.get_scale ();
|
||||
int width = get_allocated_width ()
|
||||
- margin.left - margin.right
|
||||
- border.left - border.right;
|
||||
int height = get_allocated_height ()
|
||||
- margin.top - margin.bottom
|
||||
- border.top - border.bottom;
|
||||
|
||||
cr.save ();
|
||||
cr.new_sub_path ();
|
||||
// Top Right
|
||||
cr.arc (width - radius + margin.right,
|
||||
radius + margin.top,
|
||||
radius, -90 * DEGREES, 0 * DEGREES);
|
||||
// Bottom Right
|
||||
cr.arc (width - radius + margin.right,
|
||||
height - radius + margin.bottom,
|
||||
radius, 0 * DEGREES, 90 * DEGREES);
|
||||
// Bottom Left
|
||||
cr.arc (radius + margin.left,
|
||||
height - radius + margin.bottom,
|
||||
radius, 90 * DEGREES, 180 * DEGREES);
|
||||
// Top Left
|
||||
cr.arc (radius + margin.left,
|
||||
radius + margin.top,
|
||||
radius, 180 * DEGREES, 270 * DEGREES);
|
||||
cr.close_path ();
|
||||
|
||||
cr.set_source_rgba (0, 0, 0, 0);
|
||||
cr.clip ();
|
||||
|
||||
// Draw blurred player background
|
||||
if (blurred_cover_surface == null
|
||||
|| blurred_cover_surface.width != width
|
||||
|| blurred_cover_surface.height != height) {
|
||||
var buffer = new Granite.Drawing.BufferSurface (width, height);
|
||||
Cairo.Surface surface = Functions.scale_pixbuf (
|
||||
album_art_pixbuf, width, height, scale);
|
||||
|
||||
buffer.context.set_source_surface (surface, 0, 0);
|
||||
buffer.context.paint ();
|
||||
buffer.fast_blur (8, 2);
|
||||
blurred_cover_surface = buffer;
|
||||
}
|
||||
cr.set_source_surface (blurred_cover_surface.surface, margin.left, margin.top);
|
||||
cr.paint ();
|
||||
|
||||
cr.restore ();
|
||||
|
||||
return base.draw (cr);
|
||||
}
|
||||
|
||||
private void properties_changed (string iface,
|
||||
HashTable<string, Variant> changed,
|
||||
string[] invalid) {
|
||||
@@ -246,7 +321,6 @@ namespace SwayNotificationCenter.Widgets.Mpris {
|
||||
|
||||
int scale = get_style_context ().get_scale ();
|
||||
|
||||
Gdk.Pixbuf ? pixbuf = null;
|
||||
// Cancel previous download, reset the state and download again
|
||||
album_art_cancellable.cancel ();
|
||||
album_art_cancellable.reset ();
|
||||
@@ -255,34 +329,79 @@ namespace SwayNotificationCenter.Widgets.Mpris {
|
||||
InputStream stream = yield file.read_async (Priority.DEFAULT,
|
||||
album_art_cancellable);
|
||||
|
||||
pixbuf = yield new Gdk.Pixbuf.from_stream_async (
|
||||
this.album_art_pixbuf = yield new Gdk.Pixbuf.from_stream_async (
|
||||
stream, album_art_cancellable);
|
||||
} catch (Error e) {
|
||||
debug ("Could not download album art for %s. Using fallback...",
|
||||
source.media_player.identity);
|
||||
this.album_art_pixbuf = null;
|
||||
}
|
||||
if (pixbuf != null) {
|
||||
pixbuf = Functions.scale_round_pixbuf (pixbuf,
|
||||
mpris_config.image_size,
|
||||
mpris_config.image_size,
|
||||
scale,
|
||||
mpris_config.image_radius);
|
||||
album_art.set_from_pixbuf (pixbuf);
|
||||
if (this.album_art_pixbuf != null) {
|
||||
unowned Gtk.StyleContext style_ctx = album_art.get_style_context ();
|
||||
Value br_value =
|
||||
style_ctx.get_property (Gtk.STYLE_PROPERTY_BORDER_RADIUS,
|
||||
style_ctx.get_state ());
|
||||
int radius = 0;
|
||||
if (!br_value.holds (Type.INT) ||
|
||||
(radius = br_value.get_int ()) == 0) {
|
||||
radius = mpris_config.image_radius;
|
||||
}
|
||||
var surface = Functions.scale_pixbuf (this.album_art_pixbuf,
|
||||
mpris_config.image_size,
|
||||
mpris_config.image_size,
|
||||
scale);
|
||||
this.album_art_pixbuf = Gdk.pixbuf_get_from_surface (surface,
|
||||
0, 0,
|
||||
mpris_config.image_size,
|
||||
mpris_config.image_size);
|
||||
this.blurred_cover_surface = null;
|
||||
surface = Functions.round_surface (surface,
|
||||
mpris_config.image_size,
|
||||
mpris_config.image_size,
|
||||
scale,
|
||||
radius);
|
||||
var pix_buf = Gdk.pixbuf_get_from_surface (surface,
|
||||
0, 0,
|
||||
mpris_config.image_size,
|
||||
mpris_config.image_size);
|
||||
surface.finish ();
|
||||
album_art.set_from_pixbuf (pix_buf);
|
||||
album_art.get_style_context ().set_scale (1);
|
||||
this.queue_draw ();
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
this.album_art_pixbuf = null;
|
||||
this.blurred_cover_surface = null;
|
||||
// Get the app icon
|
||||
Icon ? icon = null;
|
||||
if (desktop_entry is DesktopAppInfo) {
|
||||
icon = desktop_entry.get_icon ();
|
||||
}
|
||||
Gtk.IconInfo ? icon_info = null;
|
||||
if (icon != null) {
|
||||
album_art.set_from_gicon (icon, mpris_config.image_size);
|
||||
album_art.set_from_gicon (icon, Gtk.IconSize.INVALID);
|
||||
icon_info = Gtk.IconTheme.get_default ().lookup_by_gicon (icon,
|
||||
mpris_config.image_size,
|
||||
Gtk.IconLookupFlags.USE_BUILTIN);
|
||||
} else {
|
||||
// Default icon
|
||||
album_art.set_from_icon_name ("audio-x-generic-symbolic",
|
||||
mpris_config.image_size);
|
||||
string icon_name = "audio-x-generic-symbolic";
|
||||
album_art.set_from_icon_name (icon_name,
|
||||
Gtk.IconSize.INVALID);
|
||||
icon_info = Gtk.IconTheme.get_default ().lookup_icon (icon_name,
|
||||
mpris_config.image_size,
|
||||
Gtk.IconLookupFlags.USE_BUILTIN);
|
||||
}
|
||||
|
||||
if (icon_info != null) {
|
||||
try {
|
||||
this.album_art_pixbuf = icon_info.load_icon ();
|
||||
} catch (Error e) {
|
||||
warning ("Could not load icon: %s", e.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
47
src/controlCenter/widgets/shared/toggleButton.vala
Normal file
47
src/controlCenter/widgets/shared/toggleButton.vala
Normal file
@@ -0,0 +1,47 @@
|
||||
namespace SwayNotificationCenter.Widgets {
|
||||
class ToggleButton : Gtk.ToggleButton {
|
||||
|
||||
private string command;
|
||||
private string update_command;
|
||||
private ulong handler_id;
|
||||
|
||||
public ToggleButton (string label, string command, string update_command, bool active) {
|
||||
this.command = command;
|
||||
this.update_command = update_command;
|
||||
this.label = label;
|
||||
|
||||
if (active) {
|
||||
this.active = true;
|
||||
}
|
||||
|
||||
this.handler_id = this.toggled.connect (on_toggle);
|
||||
}
|
||||
|
||||
private async void on_toggle () {
|
||||
string msg = "";
|
||||
string[] env_additions = { "SWAYNC_TOGGLE_STATE=" + this.active.to_string () };
|
||||
yield Functions.execute_command (this.command, env_additions, out msg);
|
||||
}
|
||||
|
||||
public async void on_update () {
|
||||
if (update_command == "") return;
|
||||
string msg = "";
|
||||
string[] env_additions = { "SWAYNC_TOGGLE_STATE=" + this.active.to_string () };
|
||||
yield Functions.execute_command (this.update_command, env_additions, out msg);
|
||||
try {
|
||||
// remove trailing whitespaces
|
||||
Regex regex = new Regex ("\\s+$");
|
||||
string res = regex.replace (msg, msg.length, 0, "");
|
||||
GLib.SignalHandler.block (this, this.handler_id);
|
||||
if (res.up () == "TRUE") {
|
||||
this.active = true;
|
||||
} else {
|
||||
this.active = false;
|
||||
}
|
||||
GLib.SignalHandler.unblock (this, this.handler_id);
|
||||
} catch (RegexError e) {
|
||||
stderr.printf ("RegexError: %s\n", e.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
162
src/fadedViewport/fadedViewport.vala
Normal file
162
src/fadedViewport/fadedViewport.vala
Normal file
@@ -0,0 +1,162 @@
|
||||
namespace SwayNotificationCenter {
|
||||
public class FadedViewport : Gtk.Viewport {
|
||||
private int fade_height = 30;
|
||||
|
||||
private FadedViewportChild container;
|
||||
|
||||
public FadedViewport (int fade_height) {
|
||||
if (fade_height > 0) this.fade_height = fade_height;
|
||||
this.container = new FadedViewportChild (this.fade_height);
|
||||
|
||||
base.add (container);
|
||||
}
|
||||
|
||||
public override void add (Gtk.Widget widget) {
|
||||
container.add (widget);
|
||||
}
|
||||
|
||||
public override void remove (Gtk.Widget widget) {
|
||||
container.remove (widget);
|
||||
}
|
||||
|
||||
public override bool draw (Cairo.Context cr) {
|
||||
Gtk.Allocation alloc;
|
||||
get_allocated_size (out alloc, null);
|
||||
|
||||
Cairo.Pattern top_fade_gradient = new Cairo.Pattern.linear (0, 0, 0, 1);
|
||||
top_fade_gradient.add_color_stop_rgba (0, 1, 1, 1, 1);
|
||||
top_fade_gradient.add_color_stop_rgba (1, 1, 1, 1, 0);
|
||||
Cairo.Pattern bottom_fade_gradient = new Cairo.Pattern.linear (0, 0, 0, 1);
|
||||
bottom_fade_gradient.add_color_stop_rgba (0, 1, 1, 1, 0);
|
||||
bottom_fade_gradient.add_color_stop_rgba (1, 1, 1, 1, 1);
|
||||
|
||||
cr.save ();
|
||||
cr.push_group ();
|
||||
|
||||
// Draw widgets
|
||||
base.draw (cr);
|
||||
|
||||
/// Draw vertical fade
|
||||
|
||||
// Top fade
|
||||
cr.save ();
|
||||
cr.scale (alloc.width, fade_height);
|
||||
cr.rectangle (0, 0, alloc.width, fade_height);
|
||||
cr.set_source (top_fade_gradient);
|
||||
cr.set_operator (Cairo.Operator.DEST_OUT);
|
||||
cr.fill ();
|
||||
cr.restore ();
|
||||
// Bottom fade
|
||||
cr.save ();
|
||||
cr.translate (0, alloc.height - fade_height);
|
||||
cr.scale (alloc.width, fade_height);
|
||||
cr.rectangle (0, 0, alloc.width, fade_height);
|
||||
cr.set_source (bottom_fade_gradient);
|
||||
cr.set_operator (Cairo.Operator.DEST_OUT);
|
||||
cr.fill ();
|
||||
cr.restore ();
|
||||
|
||||
cr.pop_group_to_source ();
|
||||
cr.paint ();
|
||||
cr.restore ();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class FadedViewportChild : Gtk.Container {
|
||||
private int y_padding;
|
||||
|
||||
private unowned Gtk.Widget _child;
|
||||
|
||||
public FadedViewportChild (int y_padding) {
|
||||
base.set_has_window (false);
|
||||
base.set_can_focus (true);
|
||||
base.set_redraw_on_allocate (false);
|
||||
|
||||
// Half due to the fade basically stopping at 50% of the height
|
||||
this.y_padding = y_padding / 2;
|
||||
this._child = null;
|
||||
|
||||
this.show ();
|
||||
}
|
||||
|
||||
public override void add (Gtk.Widget widget) {
|
||||
if (this._child == null) {
|
||||
widget.set_parent (this);
|
||||
this._child = widget;
|
||||
}
|
||||
}
|
||||
|
||||
public override void remove (Gtk.Widget widget) {
|
||||
if (this._child == widget) {
|
||||
widget.unparent ();
|
||||
this._child = null;
|
||||
if (this.get_visible () && widget.get_visible ()) {
|
||||
this.queue_resize_no_redraw ();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void forall_internal (bool include_internals, Gtk.Callback callback) {
|
||||
if (this._child != null) {
|
||||
callback (this._child);
|
||||
}
|
||||
}
|
||||
|
||||
public override Gtk.SizeRequestMode get_request_mode () {
|
||||
if (this._child != null) {
|
||||
return this._child.get_request_mode ();
|
||||
} else {
|
||||
return Gtk.SizeRequestMode.HEIGHT_FOR_WIDTH;
|
||||
}
|
||||
}
|
||||
|
||||
public override void size_allocate (Gtk.Allocation allocation) {
|
||||
Gtk.Allocation child_allocation = Gtk.Allocation ();
|
||||
uint border_width = this.get_border_width ();
|
||||
if (this._child != null && this._child.get_visible ()) {
|
||||
child_allocation.x = allocation.x + (int) border_width;
|
||||
child_allocation.y = allocation.y + (int) border_width;
|
||||
Gtk.Align align_y = _child.get_valign ();
|
||||
if (align_y == Gtk.Align.END) {
|
||||
child_allocation.y -= y_padding;
|
||||
} else {
|
||||
child_allocation.y += y_padding;
|
||||
}
|
||||
child_allocation.width = allocation.width - 2 * (int) border_width;
|
||||
child_allocation.height = allocation.height - 2 * (int) border_width;
|
||||
this._child.size_allocate (child_allocation);
|
||||
if (this.get_realized ()) {
|
||||
this._child.show ();
|
||||
}
|
||||
}
|
||||
if (this.get_realized ()) {
|
||||
if (this._child != null) {
|
||||
this._child.set_child_visible (true);
|
||||
}
|
||||
}
|
||||
base.size_allocate (allocation);
|
||||
}
|
||||
|
||||
public override void get_preferred_height_for_width (int width,
|
||||
out int minimum_height,
|
||||
out int natural_height) {
|
||||
minimum_height = 0;
|
||||
natural_height = 0;
|
||||
|
||||
if (_child != null && _child.get_visible ()) {
|
||||
_child.get_preferred_height_for_width (width,
|
||||
out minimum_height,
|
||||
out natural_height);
|
||||
|
||||
minimum_height += y_padding * 2;
|
||||
natural_height += y_padding * 2;
|
||||
}
|
||||
}
|
||||
|
||||
public override bool draw (Cairo.Context cr) {
|
||||
base.draw (cr);
|
||||
return false;
|
||||
}
|
||||
}
|
@@ -8,42 +8,53 @@ namespace SwayNotificationCenter {
|
||||
public static void init () {
|
||||
system_css_provider = new Gtk.CssProvider ();
|
||||
user_css_provider = new Gtk.CssProvider ();
|
||||
|
||||
// Init resources
|
||||
var theme = Gtk.IconTheme.get_default ();
|
||||
theme.add_resource_path ("/org/erikreider/swaync/icons");
|
||||
}
|
||||
|
||||
public static void set_image_path (owned string path,
|
||||
Gtk.Image img,
|
||||
int icon_size,
|
||||
bool file_exists) {
|
||||
if ((path.length > 6 && path.slice (0, 7) == "file://") || file_exists) {
|
||||
public static void set_image_uri (owned string uri,
|
||||
Gtk.Image img,
|
||||
int icon_size,
|
||||
int radius,
|
||||
bool file_exists) {
|
||||
const string URI_PREFIX = "file://";
|
||||
const uint PREFIX_SIZE = 7;
|
||||
bool is_uri = (uri.length >= PREFIX_SIZE
|
||||
&& uri.slice (0, PREFIX_SIZE) == URI_PREFIX);
|
||||
if (is_uri || file_exists) {
|
||||
// Try as a URI (file:// is the only URI schema supported right now)
|
||||
try {
|
||||
if (!file_exists) path = path.slice (7, path.length);
|
||||
if (is_uri) uri = uri.slice (PREFIX_SIZE, uri.length);
|
||||
|
||||
var pixbuf = new Gdk.Pixbuf.from_file_at_scale (
|
||||
path,
|
||||
uri,
|
||||
icon_size * img.scale_factor,
|
||||
icon_size * img.scale_factor,
|
||||
true);
|
||||
var surface = Gdk.cairo_surface_create_from_pixbuf (
|
||||
pixbuf,
|
||||
img.scale_factor,
|
||||
img.get_window ());
|
||||
// Scale and round the image. Scales to fit the size
|
||||
var surface = scale_round_pixbuf (pixbuf,
|
||||
icon_size,
|
||||
icon_size,
|
||||
img.scale_factor,
|
||||
radius);
|
||||
img.set_from_surface (surface);
|
||||
return;
|
||||
} catch (Error e) {
|
||||
stderr.printf (e.message + "\n");
|
||||
}
|
||||
} else if (Gtk.IconTheme.get_default ().has_icon (path)) {
|
||||
// Try as a freedesktop.org-compliant icon theme
|
||||
img.set_from_icon_name (path, Notification.icon_size);
|
||||
} else {
|
||||
img.set_from_icon_name (
|
||||
"image-missing",
|
||||
Notification.icon_size);
|
||||
}
|
||||
|
||||
// Try as icon name
|
||||
if (img.storage_type == Gtk.ImageType.EMPTY) {
|
||||
img.set_from_icon_name (uri, Gtk.IconSize.INVALID);
|
||||
}
|
||||
}
|
||||
|
||||
public static void set_image_data (ImageData data, Gtk.Image img, int icon_size) {
|
||||
public static void set_image_data (ImageData data,
|
||||
Gtk.Image img,
|
||||
int icon_size,
|
||||
int radius) {
|
||||
// Rebuild and scale the image
|
||||
var pixbuf = new Gdk.Pixbuf.with_unowned_data (data.data,
|
||||
Gdk.Colorspace.RGB,
|
||||
@@ -54,14 +65,11 @@ namespace SwayNotificationCenter {
|
||||
data.rowstride,
|
||||
null);
|
||||
|
||||
pixbuf = pixbuf.scale_simple (
|
||||
icon_size * img.scale_factor,
|
||||
icon_size * img.scale_factor,
|
||||
Gdk.InterpType.BILINEAR);
|
||||
var surface = Gdk.cairo_surface_create_from_pixbuf (
|
||||
pixbuf,
|
||||
img.scale_factor,
|
||||
img.get_window ());
|
||||
var surface = scale_round_pixbuf (pixbuf,
|
||||
icon_size,
|
||||
icon_size,
|
||||
img.scale_factor,
|
||||
radius);
|
||||
img.set_from_surface (surface);
|
||||
}
|
||||
|
||||
@@ -211,12 +219,15 @@ namespace SwayNotificationCenter {
|
||||
return type;
|
||||
}
|
||||
|
||||
/** Scales the pixbuf to fit the given dimensions */
|
||||
public static Gdk.Pixbuf scale_round_pixbuf (Gdk.Pixbuf pixbuf,
|
||||
int buffer_width,
|
||||
int buffer_height,
|
||||
int img_scale,
|
||||
int radius) {
|
||||
/** Roundes the Cairo Surface to the given radii */
|
||||
public static Cairo.Surface round_surface (Cairo.Surface base_surf,
|
||||
int buffer_width,
|
||||
int buffer_height,
|
||||
int img_scale,
|
||||
int radius) {
|
||||
// Limit radii size
|
||||
radius = int.min (radius, int.min (buffer_width / 2, buffer_height / 2));
|
||||
|
||||
Cairo.Surface surface = new Cairo.ImageSurface (Cairo.Format.ARGB32,
|
||||
buffer_width,
|
||||
buffer_height);
|
||||
@@ -230,12 +241,31 @@ namespace SwayNotificationCenter {
|
||||
cr.arc (radius, buffer_height - radius, radius, 90 * DEGREES, 180 * DEGREES);
|
||||
cr.arc (radius, radius, radius, 180 * DEGREES, 270 * DEGREES);
|
||||
cr.close_path ();
|
||||
cr.set_source_rgb (0, 0, 0);
|
||||
cr.set_source_rgba (0, 0, 0, 0);
|
||||
cr.clip ();
|
||||
cr.paint ();
|
||||
|
||||
cr.save ();
|
||||
Cairo.Surface scale_surf = Gdk.cairo_surface_create_from_pixbuf (pixbuf,
|
||||
cr.set_source_surface (base_surf, 0, 0);
|
||||
cr.paint ();
|
||||
cr.restore ();
|
||||
|
||||
return surface;
|
||||
}
|
||||
|
||||
/** Scales the pixbuf to fit the given dimensions */
|
||||
public static Cairo.Surface scale_pixbuf (Gdk.Pixbuf pixbuf,
|
||||
int buffer_width,
|
||||
int buffer_height,
|
||||
int img_scale) {
|
||||
|
||||
Cairo.Surface surface = new Cairo.ImageSurface (Cairo.Format.ARGB32,
|
||||
buffer_width,
|
||||
buffer_height);
|
||||
var cr = new Cairo.Context (surface);
|
||||
|
||||
cr.save ();
|
||||
Cairo.Surface base_surf = Gdk.cairo_surface_create_from_pixbuf (pixbuf,
|
||||
img_scale,
|
||||
null);
|
||||
int width = pixbuf.width / img_scale;
|
||||
@@ -245,23 +275,41 @@ namespace SwayNotificationCenter {
|
||||
if (window_ratio > bg_ratio) { // Taller wallpaper than monitor
|
||||
double scale = (double) buffer_width / width;
|
||||
if (scale * height < buffer_height) {
|
||||
draw_scale_wide (buffer_width, width, buffer_height, height, cr, scale_surf);
|
||||
draw_scale_wide (buffer_width, width, buffer_height, height, cr, base_surf);
|
||||
} else {
|
||||
draw_scale_tall (buffer_width, width, buffer_height, height, cr, scale_surf);
|
||||
draw_scale_tall (buffer_width, width, buffer_height, height, cr, base_surf);
|
||||
}
|
||||
} else { // Wider wallpaper than monitor
|
||||
double scale = (double) buffer_height / height;
|
||||
if (scale * width < buffer_width) {
|
||||
draw_scale_tall (buffer_width, width, buffer_height, height, cr, scale_surf);
|
||||
draw_scale_tall (buffer_width, width, buffer_height, height, cr, base_surf);
|
||||
} else {
|
||||
draw_scale_wide (buffer_width, width, buffer_height, height, cr, scale_surf);
|
||||
draw_scale_wide (buffer_width, width, buffer_height, height, cr, base_surf);
|
||||
}
|
||||
}
|
||||
cr.paint ();
|
||||
cr.restore ();
|
||||
|
||||
scale_surf.finish ();
|
||||
return Gdk.pixbuf_get_from_surface (surface, 0, 0, buffer_width, buffer_height);
|
||||
base_surf.finish ();
|
||||
return surface;
|
||||
}
|
||||
|
||||
/** Scales the pixbuf to fit the given dimensions */
|
||||
public static Cairo.Surface scale_round_pixbuf (Gdk.Pixbuf pixbuf,
|
||||
int buffer_width,
|
||||
int buffer_height,
|
||||
int img_scale,
|
||||
int radius) {
|
||||
var surface = Functions.scale_pixbuf (pixbuf,
|
||||
buffer_width,
|
||||
buffer_height,
|
||||
img_scale);
|
||||
surface = Functions.round_surface (surface,
|
||||
buffer_width,
|
||||
buffer_height,
|
||||
img_scale,
|
||||
radius);
|
||||
return surface;
|
||||
}
|
||||
|
||||
private static void draw_scale_tall (int buffer_width,
|
||||
@@ -299,5 +347,68 @@ namespace SwayNotificationCenter {
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public static async bool execute_command (string cmd, string[] env_additions = {}, out string msg) {
|
||||
msg = "";
|
||||
try {
|
||||
string[] spawn_env = Environ.get ();
|
||||
// Export env variables
|
||||
foreach (string additions in env_additions) {
|
||||
spawn_env += additions;
|
||||
}
|
||||
|
||||
string[] argvp = {};
|
||||
Shell.parse_argv (cmd, out argvp);
|
||||
|
||||
Pid child_pid;
|
||||
int std_output;
|
||||
Process.spawn_async_with_pipes (
|
||||
"/",
|
||||
argvp,
|
||||
spawn_env,
|
||||
SpawnFlags.SEARCH_PATH | SpawnFlags.DO_NOT_REAP_CHILD,
|
||||
null,
|
||||
out child_pid,
|
||||
null,
|
||||
out std_output,
|
||||
null);
|
||||
|
||||
// stdout:
|
||||
string res = "";
|
||||
IOChannel output = new IOChannel.unix_new (std_output);
|
||||
output.add_watch (IOCondition.IN | IOCondition.HUP, (channel, condition) => {
|
||||
if (condition == IOCondition.HUP) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
channel.read_line (out res, null, null);
|
||||
return true;
|
||||
} catch (IOChannelError e) {
|
||||
stderr.printf ("stdout: IOChannelError: %s\n", e.message);
|
||||
return false;
|
||||
} catch (ConvertError e) {
|
||||
stderr.printf ("stdout: ConvertError: %s\n", e.message);
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
// Close the child when the spawned process is idling
|
||||
int end_status = 0;
|
||||
ChildWatch.add (child_pid, (pid, status) => {
|
||||
Process.close_pid (pid);
|
||||
GLib.FileUtils.close (std_output);
|
||||
end_status = status;
|
||||
execute_command.callback ();
|
||||
});
|
||||
// Waits until `run_script.callback()` is called above
|
||||
yield;
|
||||
msg = res;
|
||||
return end_status == 0;
|
||||
} catch (Error e) {
|
||||
stderr.printf ("Run_Script Error: %s\n", e.message);
|
||||
msg = e.message;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -26,6 +26,7 @@ widget_sources = [
|
||||
# Helpers
|
||||
'controlCenter/widgets/baseWidget.vala',
|
||||
'controlCenter/widgets/factory.vala',
|
||||
'controlCenter/widgets/shared/toggleButton.vala',
|
||||
# Widget: Title
|
||||
'controlCenter/widgets/title/title.vala',
|
||||
# Widget: Dnd
|
||||
@@ -49,13 +50,16 @@ widget_sources = [
|
||||
|
||||
app_sources = [
|
||||
'main.vala',
|
||||
'animation/animation.vala',
|
||||
'orderedHashTable/orderedHashTable.vala',
|
||||
'configModel/configModel.vala',
|
||||
'swayncDaemon/swayncDaemon.vala',
|
||||
'notiDaemon/notiDaemon.vala',
|
||||
'notiModel/notiModel.vala',
|
||||
'fadedViewport/fadedViewport.vala',
|
||||
'notificationWindow/notificationWindow.vala',
|
||||
'notification/notification.vala',
|
||||
'notificationGroup/notificationGroup.vala',
|
||||
'controlCenter/controlCenter.vala',
|
||||
widget_sources,
|
||||
'blankWindow/blankWindow.vala',
|
||||
@@ -63,13 +67,19 @@ app_sources = [
|
||||
constants,
|
||||
]
|
||||
|
||||
assert(meson.get_compiler('vala').version() >= '0.56')
|
||||
|
||||
app_deps = [
|
||||
dependency('gio-2.0', version: '>= 2.50'),
|
||||
dependency('gio-unix-2.0', version: '>= 2.50'),
|
||||
dependency('gtk+-3.0', version: '>= 3.22'),
|
||||
dependency('json-glib-1.0', version: '>= 1.0'),
|
||||
dependency('libhandy-1', version: '>= 1.2.3'),
|
||||
meson.get_compiler('c').find_library('gtk-layer-shell'),
|
||||
dependency('libhandy-1', version: '>= 1.8.0'),
|
||||
dependency('granite', version: '>= 6.2.0'),
|
||||
dependency('gtk-layer-shell-0',
|
||||
fallback: ['gtk-layer-shell-0', 'gtk-layer-shell'],
|
||||
version: '>= 0.8.0'
|
||||
),
|
||||
meson.get_compiler('c').find_library('m', required : true),
|
||||
meson.get_compiler('vala').find_library('posix'),
|
||||
dependency('gee-0.8'),
|
||||
@@ -97,36 +107,18 @@ if get_option('pulse-audio')
|
||||
]
|
||||
endif
|
||||
|
||||
# Detect libhandy version
|
||||
libhandy = dependency('libhandy-1')
|
||||
if libhandy.version() >= '1.3.9'
|
||||
add_project_arguments('-D', 'HAVE_LATEST_LIBHANDY', language: 'vala')
|
||||
endif
|
||||
|
||||
# Detect gtk-layer-shell version
|
||||
gtk_layer_shell = dependency(
|
||||
'gtk-layer-shell-0',
|
||||
fallback: ['gtk-layer-shell-0', 'gtk-layer-shell'],
|
||||
)
|
||||
if gtk_layer_shell.version() >= '0.6.0'
|
||||
add_project_arguments('-D', 'HAVE_LATEST_GTK_LAYER_SHELL', language: 'vala')
|
||||
endif
|
||||
|
||||
args = [
|
||||
'--target-glib=2.50',
|
||||
'--pkg=GtkLayerShell-0.1',
|
||||
]
|
||||
|
||||
sysconfdir = get_option('sysconfdir')
|
||||
gnome = import('gnome')
|
||||
|
||||
app_sources += gnome.compile_resources('sway_notification_center-resources',
|
||||
app_resources += gnome.compile_resources('sway_notification_center-resources',
|
||||
'sway_notification_center.gresource.xml',
|
||||
c_name: 'sway_notification_center'
|
||||
)
|
||||
|
||||
executable('swaync',
|
||||
app_sources,
|
||||
[ app_sources, app_resources ],
|
||||
vala_args: args,
|
||||
dependencies: app_deps,
|
||||
install: true,
|
||||
@@ -139,8 +131,6 @@ executable('swaync-client',
|
||||
install: true,
|
||||
)
|
||||
|
||||
config_path = join_paths(sysconfdir, 'xdg/swaync')
|
||||
|
||||
config_data = configuration_data()
|
||||
config_data.set_quoted('JSONPATH', join_paths('/', config_path, 'configSchema.json'))
|
||||
config_json = configure_file(
|
||||
@@ -149,6 +139,5 @@ config_json = configure_file(
|
||||
configuration : config_data
|
||||
)
|
||||
|
||||
install_data('style.css', install_dir : config_path)
|
||||
install_data(config_json, install_dir : config_path)
|
||||
install_data('configSchema.json', install_dir : config_path)
|
||||
|
@@ -62,9 +62,9 @@ namespace SwayNotificationCenter {
|
||||
/** Method to close notification and send DISMISSED signal */
|
||||
public void manually_close_notification (uint32 id, bool timeout)
|
||||
throws DBusError, IOError {
|
||||
NotificationWindow.instance.close_notification (id, false);
|
||||
NotificationWindow.instance.close_notification (id, true);
|
||||
if (!timeout) {
|
||||
control_center.close_notification (id);
|
||||
control_center.close_notification (id, true);
|
||||
NotificationClosed (id, ClosedReasons.DISMISSED);
|
||||
|
||||
swaync_daemon.subscribe_v2 (control_center.notification_count (),
|
||||
@@ -170,11 +170,10 @@ namespace SwayNotificationCenter {
|
||||
|
||||
debug ("Notification: %s\n", param.to_string ());
|
||||
|
||||
// Replace notification logic
|
||||
// Get the notification id to replace
|
||||
uint32 replace_notification = 0;
|
||||
if (id == replaces_id) {
|
||||
param.replaces = true;
|
||||
NotificationWindow.instance.close_notification (id, true);
|
||||
control_center.close_notification (id, true);
|
||||
replace_notification = id;
|
||||
} else if (param.synchronous != null
|
||||
&& param.synchronous.length > 0) {
|
||||
// Tries replacing without replaces_id instead
|
||||
@@ -182,29 +181,47 @@ namespace SwayNotificationCenter {
|
||||
// if there is any notification to replace
|
||||
if (synchronous_ids.lookup_extended (
|
||||
param.synchronous, null, out r_id)) {
|
||||
param.replaces = true;
|
||||
// Close the notification
|
||||
NotificationWindow.instance.close_notification (r_id, true);
|
||||
control_center.close_notification (r_id, true);
|
||||
replace_notification = r_id;
|
||||
}
|
||||
synchronous_ids.set (param.synchronous, id);
|
||||
}
|
||||
|
||||
// Only show popup notification if it is ENABLED or TRANSIENT
|
||||
if ((state == NotificationStatusEnum.ENABLED || state == NotificationStatusEnum.TRANSIENT)
|
||||
&& !control_center.get_visibility ()) {
|
||||
// Also check if urgency is Critical and not inhibited and dnd
|
||||
if (param.urgency == UrgencyLevels.CRITICAL ||
|
||||
(!dnd && !swaync_daemon.inhibited
|
||||
&& param.urgency != UrgencyLevels.CRITICAL)) {
|
||||
NotificationWindow.instance.add_notification (param, this);
|
||||
}
|
||||
bool show_notification = state == NotificationStatusEnum.ENABLED
|
||||
|| state == NotificationStatusEnum.TRANSIENT;
|
||||
// Don't show the notification window if the control center is open
|
||||
if (control_center.get_visibility ()) {
|
||||
show_notification = false;
|
||||
}
|
||||
|
||||
bool bypass_dnd = param.urgency == UrgencyLevels.CRITICAL || param.swaync_bypass_dnd;
|
||||
// Don't show the notification window if dnd or inhibited
|
||||
if (!bypass_dnd && (dnd || swaync_daemon.inhibited)) {
|
||||
show_notification = false;
|
||||
}
|
||||
|
||||
if (show_notification) {
|
||||
if (replace_notification > 0) {
|
||||
NotificationWindow.instance.replace_notification (replace_notification, param);
|
||||
} else {
|
||||
NotificationWindow.instance.add_notification (param);
|
||||
}
|
||||
} else if (replace_notification > 0) {
|
||||
// Remove the old notification due to it not being replaced
|
||||
NotificationWindow.instance.close_notification (replace_notification, false);
|
||||
}
|
||||
|
||||
// Only add notification to CC if it isn't IGNORED and not transient/TRANSIENT
|
||||
if (state != NotificationStatusEnum.IGNORED
|
||||
&& state != NotificationStatusEnum.TRANSIENT
|
||||
&& !param.transient) {
|
||||
control_center.add_notification (param, this);
|
||||
if (replace_notification > 0) {
|
||||
control_center.replace_notification (replace_notification, param);
|
||||
} else {
|
||||
control_center.add_notification (param);
|
||||
}
|
||||
} else if (replace_notification > 0) {
|
||||
// Remove the old notification due to it not being replaced
|
||||
control_center.close_notification (replace_notification, false);
|
||||
}
|
||||
|
||||
#if WANT_SCRIPTING
|
||||
@@ -292,8 +309,8 @@ namespace SwayNotificationCenter {
|
||||
*/
|
||||
[DBus (name = "CloseNotification")]
|
||||
public void close_notification (uint32 id) throws DBusError, IOError {
|
||||
NotificationWindow.instance.close_notification (id, false);
|
||||
control_center.close_notification (id);
|
||||
NotificationWindow.instance.close_notification (id, true);
|
||||
control_center.close_notification (id, true);
|
||||
NotificationClosed (id, ClosedReasons.CLOSED_BY_CLOSENOTIFICATION);
|
||||
}
|
||||
|
||||
|
@@ -110,10 +110,16 @@ namespace SwayNotificationCenter {
|
||||
/** Disables scripting for notification */
|
||||
public bool swaync_no_script { get; set; }
|
||||
|
||||
/** Always show the notification, regardless of dnd/inhibit state */
|
||||
public bool swaync_bypass_dnd { get; set; }
|
||||
|
||||
public Array<Action> actions { get; set; }
|
||||
|
||||
/** If the notification replaces another */
|
||||
public bool replaces { get; set; }
|
||||
public DesktopAppInfo ? desktop_app_info = null;
|
||||
|
||||
public string name_id;
|
||||
|
||||
public string display_name;
|
||||
|
||||
public NotifyParams (uint32 applied_id,
|
||||
string app_name,
|
||||
@@ -134,12 +140,37 @@ namespace SwayNotificationCenter {
|
||||
this.expire_timeout = expire_timeout;
|
||||
this.time = (int64) (get_real_time () * 0.000001);
|
||||
|
||||
this.replaces = false;
|
||||
this.has_synch = false;
|
||||
|
||||
parse_hints ();
|
||||
|
||||
parse_actions (actions);
|
||||
|
||||
// Try to get the desktop file
|
||||
string[] entries = {};
|
||||
if (desktop_entry != null) entries += desktop_entry.replace (".desktop", "");
|
||||
if (app_name != null) entries += app_name.replace (".desktop", "");
|
||||
foreach (string entry in entries) {
|
||||
var app_info = new DesktopAppInfo ("%s.desktop".printf (entry));
|
||||
// Checks if the .desktop file actually exists or not
|
||||
if (app_info is DesktopAppInfo) {
|
||||
desktop_app_info = app_info;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Set name_id
|
||||
this.name_id = this.desktop_entry ?? this.app_name ?? "";
|
||||
|
||||
// Set display_name and make the first letter upper case
|
||||
string ? display_name = this.desktop_entry ?? this.app_name;
|
||||
if (desktop_app_info != null) {
|
||||
display_name = desktop_app_info.get_display_name ();
|
||||
}
|
||||
if (display_name == null || display_name.length == 0) {
|
||||
display_name = "Unknown";
|
||||
}
|
||||
this.display_name = display_name.splice (0, 1, display_name.up (1));
|
||||
}
|
||||
|
||||
private void parse_hints () {
|
||||
@@ -151,6 +182,11 @@ namespace SwayNotificationCenter {
|
||||
swaync_no_script = hint_value.get_boolean ();
|
||||
}
|
||||
break;
|
||||
case "SWAYNC_BYPASS_DND":
|
||||
if (hint_value.is_of_type (VariantType.BOOLEAN)) {
|
||||
swaync_bypass_dnd = hint_value.get_boolean ();
|
||||
}
|
||||
break;
|
||||
case "value":
|
||||
if (hint_value.is_of_type (VariantType.INT32)) {
|
||||
this.has_synch = true;
|
||||
|
@@ -60,15 +60,37 @@
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<child>
|
||||
<object class="GtkImage" id="img">
|
||||
<object class="GtkOverlay">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="halign">center</property>
|
||||
<property name="valign">center</property>
|
||||
<property name="margin-end">12</property>
|
||||
<property name="icon_size">6</property>
|
||||
<style>
|
||||
<class name="image"/>
|
||||
</style>
|
||||
<child>
|
||||
<object class="GtkImage" id="img">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="valign">center</property>
|
||||
<property name="icon_size">6</property>
|
||||
<style>
|
||||
<class name="image"/>
|
||||
</style>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="index">-1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child type="overlay">
|
||||
<object class="GtkImage" id="img_app_icon">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="halign">end</property>
|
||||
<property name="valign">end</property>
|
||||
<property name="icon_size">6</property>
|
||||
<style>
|
||||
<class name="app-icon"/>
|
||||
</style>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
@@ -81,7 +103,6 @@
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="valign">center</property>
|
||||
<property name="margin-end">14</property>
|
||||
<property name="hexpand">True</property>
|
||||
<property name="vexpand">True</property>
|
||||
<property name="orientation">vertical</property>
|
||||
@@ -153,6 +174,9 @@
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<style>
|
||||
<class name="text-box"/>
|
||||
</style>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
@@ -281,7 +305,7 @@
|
||||
<object class="GtkImage">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="icon-name">window-close-symbolic</property>
|
||||
<property name="icon-name">swaync-close-symbolic</property>
|
||||
</object>
|
||||
</child>
|
||||
<style>
|
||||
|
@@ -30,7 +30,7 @@ namespace SwayNotificationCenter {
|
||||
[GtkChild]
|
||||
unowned Gtk.Button close_button;
|
||||
|
||||
private Gtk.ButtonBox alt_actions_box = new Gtk.ButtonBox (Gtk.Orientation.HORIZONTAL);
|
||||
private Gtk.ButtonBox ? alt_actions_box = null;
|
||||
|
||||
[GtkChild]
|
||||
unowned Gtk.Label summary;
|
||||
@@ -41,6 +41,8 @@ namespace SwayNotificationCenter {
|
||||
[GtkChild]
|
||||
unowned Gtk.Image img;
|
||||
[GtkChild]
|
||||
unowned Gtk.Image img_app_icon;
|
||||
[GtkChild]
|
||||
unowned Gtk.Image body_image;
|
||||
|
||||
// Inline Reply
|
||||
@@ -70,8 +72,8 @@ namespace SwayNotificationCenter {
|
||||
|
||||
public bool is_timed { get; construct; default = false; }
|
||||
|
||||
public NotifyParams param { get; construct; }
|
||||
public NotiDaemon noti_daemon { get; construct; }
|
||||
public NotifyParams param { get; private set; }
|
||||
public unowned NotiDaemon noti_daemon { get; construct; }
|
||||
|
||||
public NotificationType notification_type {
|
||||
get;
|
||||
@@ -112,8 +114,9 @@ namespace SwayNotificationCenter {
|
||||
NotiDaemon noti_daemon,
|
||||
NotificationType notification_type) {
|
||||
Object (noti_daemon: noti_daemon,
|
||||
param: param,
|
||||
notification_type: notification_type);
|
||||
this.param = param;
|
||||
build_noti ();
|
||||
}
|
||||
|
||||
/** Show a timed notification */
|
||||
@@ -124,7 +127,6 @@ namespace SwayNotificationCenter {
|
||||
uint timeout_low,
|
||||
uint timeout_critical) {
|
||||
Object (noti_daemon: noti_daemon,
|
||||
param: param,
|
||||
notification_type: notification_type,
|
||||
is_timed: true,
|
||||
timeout_delay: timeout,
|
||||
@@ -132,11 +134,13 @@ namespace SwayNotificationCenter {
|
||||
timeout_critical_delay: timeout_critical,
|
||||
number_of_body_lines: 5
|
||||
);
|
||||
this.param = param;
|
||||
build_noti ();
|
||||
}
|
||||
|
||||
construct {
|
||||
try {
|
||||
code_regex = new Regex ("(?<= |^)(\\d{3}(-| )\\d{3}|\\d{4,7})(?= |$|\\.|,)",
|
||||
code_regex = new Regex ("(?<= |^)(\\d{3}(-| )\\d{3}|\\d{4,8})(?= |$|\\.|,)",
|
||||
RegexCompileFlags.MULTILINE);
|
||||
string joined_tags = string.joinv ("|", TAGS);
|
||||
tag_regex = new Regex ("<(/?(?:%s))>".printf (joined_tags));
|
||||
@@ -196,12 +200,85 @@ namespace SwayNotificationCenter {
|
||||
});
|
||||
|
||||
this.transition_time = ConfigModel.instance.transition_time;
|
||||
build_noti ();
|
||||
|
||||
if (is_timed) {
|
||||
///
|
||||
/// Signals
|
||||
///
|
||||
|
||||
this.button_press_event.connect ((event) => {
|
||||
// Close notification on middle and right button click
|
||||
switch (event.button) {
|
||||
case Gdk.BUTTON_MIDDLE:
|
||||
case Gdk.BUTTON_SECONDARY:
|
||||
this.close_notification ();
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
// Adds CSS :hover selector to EventBox
|
||||
default_action.enter_notify_event.connect ((event) => {
|
||||
if (event.detail != Gdk.NotifyType.INFERIOR
|
||||
&& event.window == default_action.get_window ()) {
|
||||
default_action_in = true;
|
||||
default_action_update_state ();
|
||||
}
|
||||
return true;
|
||||
});
|
||||
default_action.leave_notify_event.connect ((event) => {
|
||||
if (event.detail != Gdk.NotifyType.INFERIOR
|
||||
&& event.window == default_action.get_window ()) {
|
||||
default_action_in = false;
|
||||
default_action_update_state ();
|
||||
}
|
||||
return true;
|
||||
});
|
||||
default_action.unmap.connect (() => default_action_in = false);
|
||||
|
||||
close_button.clicked.connect (() => close_notification ());
|
||||
|
||||
this.event_box.enter_notify_event.connect ((event) => {
|
||||
close_revealer.set_reveal_child (true);
|
||||
remove_noti_timeout ();
|
||||
return false;
|
||||
});
|
||||
this.event_box.leave_notify_event.connect ((event) => {
|
||||
if (event.detail == Gdk.NotifyType.INFERIOR) return true;
|
||||
close_revealer.set_reveal_child (false);
|
||||
add_notification_timeout ();
|
||||
this.size_allocate.connect (on_size_allocation);
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
this.carousel.page_changed.connect ((_, i) => {
|
||||
if (i != this.carousel_empty_widget_index) return;
|
||||
remove_noti_timeout ();
|
||||
try {
|
||||
noti_daemon.manually_close_notification (
|
||||
param.applied_id, false);
|
||||
} catch (Error e) {
|
||||
print ("Error: %s\n", e.message);
|
||||
this.destroy ();
|
||||
}
|
||||
});
|
||||
|
||||
inline_reply_entry.key_release_event.connect ((w, event_key) => {
|
||||
switch (Gdk.keyval_name (event_key.keyval)) {
|
||||
case "Return":
|
||||
inline_reply_button.clicked ();
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
});
|
||||
inline_reply_button.clicked.connect (() => {
|
||||
string text = inline_reply_entry.get_text ().strip ();
|
||||
if (text.length == 0) return;
|
||||
noti_daemon.NotificationReplied (param.applied_id, text);
|
||||
// Dismiss notification without activating Action
|
||||
action_clicked (null);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
private void default_action_update_state () {
|
||||
@@ -235,50 +312,8 @@ namespace SwayNotificationCenter {
|
||||
this.summary.set_text (param.summary ?? param.app_name);
|
||||
this.summary.set_ellipsize (Pango.EllipsizeMode.END);
|
||||
|
||||
this.button_press_event.connect ((event) => {
|
||||
if (event.button != Gdk.BUTTON_SECONDARY) return false;
|
||||
// Right click
|
||||
this.close_notification ();
|
||||
return true;
|
||||
});
|
||||
|
||||
// Adds CSS :hover selector to EventBox
|
||||
default_action.enter_notify_event.connect ((event) => {
|
||||
if (event.detail != Gdk.NotifyType.INFERIOR
|
||||
&& event.window == default_action.get_window ()) {
|
||||
default_action_in = true;
|
||||
default_action_update_state ();
|
||||
}
|
||||
return true;
|
||||
});
|
||||
default_action.leave_notify_event.connect ((event) => {
|
||||
if (event.detail != Gdk.NotifyType.INFERIOR
|
||||
&& event.window == default_action.get_window ()) {
|
||||
default_action_in = false;
|
||||
default_action_update_state ();
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
default_action.unmap.connect (() => default_action_in = false);
|
||||
|
||||
close_revealer.set_transition_duration (this.transition_time);
|
||||
|
||||
close_button.clicked.connect (() => close_notification ());
|
||||
|
||||
this.event_box.enter_notify_event.connect ((event) => {
|
||||
close_revealer.set_reveal_child (true);
|
||||
remove_noti_timeout ();
|
||||
return false;
|
||||
});
|
||||
|
||||
this.event_box.leave_notify_event.connect ((event) => {
|
||||
if (event.detail == Gdk.NotifyType.INFERIOR) return true;
|
||||
close_revealer.set_reveal_child (false);
|
||||
add_notification_timeout ();
|
||||
return false;
|
||||
});
|
||||
|
||||
this.revealer.set_transition_duration (this.transition_time);
|
||||
|
||||
this.carousel.set_animation_duration (this.transition_time);
|
||||
@@ -295,20 +330,9 @@ namespace SwayNotificationCenter {
|
||||
this.carousel_empty_widget_index = 0;
|
||||
break;
|
||||
}
|
||||
this.carousel.page_changed.connect ((_, i) => {
|
||||
if (i != this.carousel_empty_widget_index) return;
|
||||
remove_noti_timeout ();
|
||||
try {
|
||||
noti_daemon.manually_close_notification (
|
||||
param.applied_id, false);
|
||||
} catch (Error e) {
|
||||
print ("Error: %s\n", e.message);
|
||||
this.destroy ();
|
||||
}
|
||||
});
|
||||
#if HAVE_LATEST_LIBHANDY
|
||||
// Reset state
|
||||
this.carousel.scroll_to (event_box);
|
||||
this.carousel.allow_scroll_wheel = false;
|
||||
#endif
|
||||
|
||||
if (this.progress_bar.visible = param.has_synch) {
|
||||
this.progress_bar.set_fraction (param.value * 0.01);
|
||||
@@ -322,13 +346,16 @@ namespace SwayNotificationCenter {
|
||||
|
||||
this.show ();
|
||||
|
||||
if (param.replaces) {
|
||||
Timeout.add (0, () => {
|
||||
this.revealer.set_reveal_child (true);
|
||||
} else {
|
||||
Timeout.add (0, () => {
|
||||
this.revealer.set_reveal_child (true);
|
||||
return Source.REMOVE;
|
||||
});
|
||||
return Source.REMOVE;
|
||||
});
|
||||
|
||||
remove_noti_timeout ();
|
||||
this.size_allocate.disconnect (on_size_allocation);
|
||||
if (is_timed) {
|
||||
add_notification_timeout ();
|
||||
this.size_allocate.connect (on_size_allocation);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -337,6 +364,9 @@ namespace SwayNotificationCenter {
|
||||
|
||||
this.body.set_lines (this.number_of_body_lines);
|
||||
|
||||
// Reset state
|
||||
this.body_image.hide ();
|
||||
|
||||
// Removes all image tags and adds them to an array
|
||||
if (text.length > 0) {
|
||||
try {
|
||||
@@ -374,22 +404,29 @@ namespace SwayNotificationCenter {
|
||||
|
||||
// Markup
|
||||
try {
|
||||
// Escapes all characters
|
||||
string escaped = Markup.escape_text (text);
|
||||
// Replace all valid tags brackets with <,</,> so that the
|
||||
// markup parser only parses valid tags
|
||||
// Ex: <b>BOLD</b> -> <b>BOLD</b>
|
||||
escaped = tag_regex.replace (escaped, escaped.length, 0, "<\\1>");
|
||||
|
||||
// Unescape a few characters that may have been double escaped
|
||||
// Sending "<" in Discord would result in "&lt;" without this
|
||||
// &lt; -> <
|
||||
escaped = tag_unescape_regex.replace_literal (escaped, escaped.length, 0, "&");
|
||||
|
||||
// Turns it back to markdown, defaults to original if not valid
|
||||
Pango.AttrList ? attr = null;
|
||||
string ? buf = null;
|
||||
Pango.parse_markup (escaped, -1, 0, out attr, out buf, null);
|
||||
try {
|
||||
// Try parsing without any hacks
|
||||
Pango.parse_markup (text, -1, 0, out attr, out buf, null);
|
||||
} catch (Error e) {
|
||||
// Default to hack if the initial markup couldn't be parsed
|
||||
|
||||
// Escapes all characters
|
||||
string escaped = Markup.escape_text (text);
|
||||
// Replace all valid tags brackets with <,</,> so that the
|
||||
// markup parser only parses valid tags
|
||||
// Ex: <b>BOLD</b> -> <b>BOLD</b>
|
||||
escaped = tag_regex.replace (escaped, escaped.length, 0, "<\\1>");
|
||||
|
||||
// Unescape a few characters that may have been double escaped
|
||||
// Sending "<" in Discord would result in "&lt;" without this
|
||||
// &lt; -> <
|
||||
escaped = tag_unescape_regex.replace_literal (escaped, escaped.length, 0, "&");
|
||||
|
||||
// Turns it back to markup, defaults to original if not valid
|
||||
Pango.parse_markup (escaped, -1, 0, out attr, out buf, null);
|
||||
}
|
||||
|
||||
this.body.set_text (buf);
|
||||
if (attr != null) this.body.set_attributes (attr);
|
||||
@@ -421,6 +458,7 @@ namespace SwayNotificationCenter {
|
||||
}
|
||||
|
||||
public void click_alt_action (uint index) {
|
||||
if (alt_actions_box == null) return;
|
||||
List<weak Gtk.Widget> ? children = alt_actions_box.get_children ();
|
||||
uint length = children.length ();
|
||||
if (length == 0 || index >= length) return;
|
||||
@@ -452,6 +490,11 @@ namespace SwayNotificationCenter {
|
||||
}
|
||||
|
||||
private void set_style_urgency () {
|
||||
// Reset state
|
||||
base_box.get_style_context ().remove_class ("low");
|
||||
base_box.get_style_context ().remove_class ("normal");
|
||||
base_box.get_style_context ().remove_class ("critical");
|
||||
|
||||
switch (param.urgency) {
|
||||
case UrgencyLevels.LOW:
|
||||
base_box.get_style_context ().add_class ("low");
|
||||
@@ -467,6 +510,8 @@ namespace SwayNotificationCenter {
|
||||
}
|
||||
|
||||
private void set_inline_reply () {
|
||||
// Reset state
|
||||
inline_reply_box.hide ();
|
||||
// Only show inline replies in popup notifications if the compositor
|
||||
// supports ON_DEMAND layer shell keyboard interactivity
|
||||
if (!ConfigModel.instance.notification_inline_replies
|
||||
@@ -494,32 +539,23 @@ namespace SwayNotificationCenter {
|
||||
},
|
||||
null);
|
||||
|
||||
inline_reply_entry.key_release_event.connect ((w, event_key) => {
|
||||
switch (Gdk.keyval_name (event_key.keyval)) {
|
||||
case "Return":
|
||||
inline_reply_button.clicked ();
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
inline_reply_button.set_label (param.inline_reply.name ?? "Reply");
|
||||
inline_reply_button.clicked.connect (() => {
|
||||
string text = inline_reply_entry.get_text ().strip ();
|
||||
if (text.length == 0) return;
|
||||
noti_daemon.NotificationReplied (param.applied_id, text);
|
||||
// Dismiss notification without activating Action
|
||||
action_clicked (null);
|
||||
});
|
||||
}
|
||||
|
||||
private void set_actions () {
|
||||
// Reset state
|
||||
foreach (Gtk.Widget child in base_box.get_children ()) {
|
||||
if (child is Gtk.ScrolledWindow) {
|
||||
child.destroy ();
|
||||
}
|
||||
}
|
||||
|
||||
// Check for security codes
|
||||
string ? code = parse_body_codes ();
|
||||
if (param.actions.length > 0 || code != null) {
|
||||
var viewport = new Gtk.Viewport (null, null);
|
||||
var scroll = new Gtk.ScrolledWindow (null, null);
|
||||
alt_actions_box = new Gtk.ButtonBox (Gtk.Orientation.HORIZONTAL);
|
||||
alt_actions_box.set_homogeneous (true);
|
||||
alt_actions_box.set_layout (Gtk.ButtonBoxStyle.EXPAND);
|
||||
|
||||
@@ -614,59 +650,104 @@ namespace SwayNotificationCenter {
|
||||
});
|
||||
}
|
||||
|
||||
public void replace_notification (NotifyParams new_params) {
|
||||
this.param = new_params;
|
||||
build_noti ();
|
||||
}
|
||||
|
||||
private void set_icon () {
|
||||
img.clear ();
|
||||
img.set_visible (true);
|
||||
img_app_icon.clear ();
|
||||
img_app_icon.set_visible (true);
|
||||
|
||||
Icon ? app_icon_name = null;
|
||||
string ? app_icon_uri = null;
|
||||
if (param.desktop_app_info != null) {
|
||||
app_icon_name = param.desktop_app_info.get_icon ();
|
||||
}
|
||||
if (param.app_icon != null && param.app_icon != "") {
|
||||
app_icon_uri = param.app_icon;
|
||||
}
|
||||
|
||||
var image_visibility = ConfigModel.instance.image_visibility;
|
||||
if (image_visibility == ImageVisibility.NEVER) {
|
||||
img.set_visible (false);
|
||||
img_app_icon.set_visible (false);
|
||||
return;
|
||||
}
|
||||
|
||||
img.set_pixel_size (notification_icon_size);
|
||||
img.height_request = notification_icon_size;
|
||||
img.width_request = notification_icon_size;
|
||||
int app_icon_size = notification_icon_size / 3;
|
||||
img_app_icon.set_pixel_size (app_icon_size);
|
||||
|
||||
var img_path_exists = File.new_for_path (
|
||||
var img_path_exists = File.new_for_uri (
|
||||
param.image_path ?? "").query_exists ();
|
||||
var app_icon_exists = File.new_for_path (
|
||||
param.app_icon ?? "").query_exists ();
|
||||
if (param.image_path != null && !img_path_exists) {
|
||||
// Check if it's not a URI
|
||||
img_path_exists = File.new_for_path (
|
||||
param.image_path ?? "").query_exists ();
|
||||
}
|
||||
var app_icon_exists = File.new_for_uri (
|
||||
app_icon_uri ?? "").query_exists ();
|
||||
if (app_icon_uri != null && !img_path_exists) {
|
||||
// Check if it's not a URI
|
||||
app_icon_exists = File.new_for_path (
|
||||
app_icon_uri ?? "").query_exists ();
|
||||
}
|
||||
|
||||
// Get the image CSS corner radius in pixels
|
||||
int radius = 0;
|
||||
unowned var ctx = img.get_style_context ();
|
||||
var value = ctx.get_property (Gtk.STYLE_PROPERTY_BORDER_RADIUS,
|
||||
ctx.get_state ());
|
||||
if (value.type () == Type.INT) {
|
||||
radius = value.get_int ();
|
||||
}
|
||||
|
||||
// Set the main image to the provided image
|
||||
if (param.image_data.is_initialized) {
|
||||
Functions.set_image_data (param.image_data, img,
|
||||
notification_icon_size);
|
||||
notification_icon_size, radius);
|
||||
} else if (param.image_path != null &&
|
||||
param.image_path != "" &&
|
||||
img_path_exists) {
|
||||
Functions.set_image_path (param.image_path, img,
|
||||
Functions.set_image_uri (param.image_path, img,
|
||||
notification_icon_size,
|
||||
radius,
|
||||
img_path_exists);
|
||||
} else if (param.app_icon != null && param.app_icon != "") {
|
||||
Functions.set_image_path (param.app_icon, img,
|
||||
notification_icon_size,
|
||||
app_icon_exists);
|
||||
} else if (param.icon_data.is_initialized) {
|
||||
Functions.set_image_data (param.icon_data, img,
|
||||
notification_icon_size);
|
||||
} else {
|
||||
notification_icon_size, radius);
|
||||
}
|
||||
|
||||
if (img.storage_type == Gtk.ImageType.EMPTY) {
|
||||
// Get the app icon
|
||||
Icon ? icon = null;
|
||||
if (param.desktop_entry != null) {
|
||||
string entry = param.desktop_entry;
|
||||
entry = entry.replace (".desktop", "");
|
||||
DesktopAppInfo entry_info = new DesktopAppInfo (
|
||||
"%s.desktop".printf (entry));
|
||||
// Checks if the .desktop file actually exists or not
|
||||
if (entry_info is DesktopAppInfo) {
|
||||
icon = entry_info.get_icon ();
|
||||
}
|
||||
}
|
||||
if (icon != null) {
|
||||
img.set_from_gicon (icon, icon_size);
|
||||
if (app_icon_uri != null) {
|
||||
Functions.set_image_uri (app_icon_uri, img,
|
||||
notification_icon_size,
|
||||
radius,
|
||||
app_icon_exists);
|
||||
} else if (app_icon_name != null) {
|
||||
img.set_from_gicon (app_icon_name, icon_size);
|
||||
} else if (image_visibility == ImageVisibility.ALWAYS) {
|
||||
// Default icon
|
||||
img.set_from_icon_name ("image-missing", icon_size);
|
||||
} else {
|
||||
img.set_visible (false);
|
||||
}
|
||||
} else {
|
||||
// We only set the app icon if the main image is set
|
||||
if (app_icon_uri != null) {
|
||||
Functions.set_image_uri (app_icon_uri, img_app_icon,
|
||||
app_icon_size,
|
||||
0,
|
||||
app_icon_exists);
|
||||
} else if (app_icon_name != null) {
|
||||
img_app_icon.set_from_gicon (app_icon_name, Gtk.IconSize.INVALID);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
586
src/notificationGroup/notificationGroup.vala
Normal file
586
src/notificationGroup/notificationGroup.vala
Normal file
@@ -0,0 +1,586 @@
|
||||
namespace SwayNotificationCenter {
|
||||
public class NotificationGroup : Gtk.ListBoxRow {
|
||||
const string STYLE_CLASS_URGENT = "critical";
|
||||
const string STYLE_CLASS_COLLAPSED = "collapsed";
|
||||
|
||||
public string name_id;
|
||||
|
||||
private ExpandableGroup group;
|
||||
private Gtk.Revealer revealer = new Gtk.Revealer ();
|
||||
private Gtk.Image app_icon;
|
||||
private Gtk.Label app_label;
|
||||
|
||||
private Gtk.GestureMultiPress gesture;
|
||||
private bool gesture_down = false;
|
||||
private bool gesture_in = false;
|
||||
|
||||
private HashTable<uint32, bool> urgent_notifications
|
||||
= new HashTable<uint32, bool> (direct_hash, direct_equal);
|
||||
|
||||
public signal void on_expand_change (bool state);
|
||||
|
||||
public NotificationGroup (string name_id, string display_name) {
|
||||
this.name_id = name_id;
|
||||
get_style_context ().add_class ("notification-group");
|
||||
|
||||
Gtk.Box box = new Gtk.Box (Gtk.Orientation.VERTICAL, 0);
|
||||
|
||||
revealer.set_transition_type (Gtk.RevealerTransitionType.SLIDE_UP);
|
||||
revealer.set_reveal_child (false);
|
||||
revealer.set_transition_duration (Constants.ANIMATION_DURATION);
|
||||
|
||||
// Add top controls
|
||||
Gtk.Box controls_box = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 4);
|
||||
Gtk.Box end_box = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 4);
|
||||
end_box.set_halign (Gtk.Align.END);
|
||||
end_box.get_style_context ().add_class ("notification-group-buttons");
|
||||
|
||||
// Collapse button
|
||||
Gtk.Button collapse_button = new Gtk.Button.from_icon_name (
|
||||
"swaync-collapse-symbolic", Gtk.IconSize.BUTTON);
|
||||
collapse_button.get_style_context ().add_class ("flat");
|
||||
collapse_button.get_style_context ().add_class ("circular");
|
||||
collapse_button.get_style_context ().add_class ("notification-group-collapse-button");
|
||||
collapse_button.set_relief (Gtk.ReliefStyle.NORMAL);
|
||||
collapse_button.set_halign (Gtk.Align.END);
|
||||
collapse_button.set_valign (Gtk.Align.CENTER);
|
||||
collapse_button.clicked.connect (() => {
|
||||
set_expanded (false);
|
||||
on_expand_change (false);
|
||||
});
|
||||
end_box.add (collapse_button);
|
||||
|
||||
// Close all button
|
||||
Gtk.Button close_all_button = new Gtk.Button.from_icon_name (
|
||||
"swaync-close-symbolic", Gtk.IconSize.BUTTON);
|
||||
close_all_button.get_style_context ().add_class ("flat");
|
||||
close_all_button.get_style_context ().add_class ("circular");
|
||||
close_all_button.get_style_context ().add_class ("notification-group-close-all-button");
|
||||
close_all_button.set_relief (Gtk.ReliefStyle.NORMAL);
|
||||
close_all_button.set_halign (Gtk.Align.END);
|
||||
close_all_button.set_valign (Gtk.Align.CENTER);
|
||||
close_all_button.clicked.connect (() => {
|
||||
close_all_notifications ();
|
||||
on_expand_change (false);
|
||||
});
|
||||
end_box.add (close_all_button);
|
||||
|
||||
// Group name label
|
||||
Gtk.Box start_box = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 4);
|
||||
start_box.set_halign (Gtk.Align.START);
|
||||
start_box.get_style_context ().add_class ("notification-group-headers");
|
||||
// App Icon
|
||||
app_icon = new Gtk.Image ();
|
||||
app_icon.set_pixel_size (32);
|
||||
app_icon.set_valign (Gtk.Align.CENTER);
|
||||
app_icon.get_style_context ().add_class ("notification-group-icon");
|
||||
start_box.add (app_icon);
|
||||
// App Label
|
||||
app_label = new Gtk.Label (display_name);
|
||||
app_label.xalign = 0;
|
||||
app_label.get_style_context ().add_class ("title-1");
|
||||
app_label.get_style_context ().add_class ("notification-group-header");
|
||||
start_box.add (app_label);
|
||||
|
||||
controls_box.pack_start (start_box);
|
||||
controls_box.pack_end (end_box);
|
||||
revealer.add (controls_box);
|
||||
box.add (revealer);
|
||||
|
||||
set_activatable (false);
|
||||
|
||||
group = new ExpandableGroup (Constants.ANIMATION_DURATION, (state) => {
|
||||
revealer.set_reveal_child (state);
|
||||
|
||||
// Change CSS Class
|
||||
if (parent != null) {
|
||||
set_classes ();
|
||||
}
|
||||
});
|
||||
set_classes ();
|
||||
box.add (group);
|
||||
add (box);
|
||||
|
||||
show_all ();
|
||||
|
||||
/*
|
||||
* Handling of group presses
|
||||
*/
|
||||
gesture = new Gtk.GestureMultiPress (this);
|
||||
gesture.set_touch_only (false);
|
||||
gesture.set_exclusive (true);
|
||||
gesture.set_button (Gdk.BUTTON_PRIMARY);
|
||||
gesture.set_propagation_phase (Gtk.PropagationPhase.CAPTURE);
|
||||
gesture.pressed.connect ((_gesture, _n_press, x, y) => {
|
||||
gesture_in = true;
|
||||
gesture_down = true;
|
||||
});
|
||||
gesture.released.connect ((gesture, _n_press, _x, _y) => {
|
||||
// Emit released
|
||||
if (!gesture_down) return;
|
||||
gesture_down = false;
|
||||
if (gesture_in) {
|
||||
bool single_noti = only_single_notification ();
|
||||
if (!group.is_expanded && !single_noti) {
|
||||
group.set_expanded (true);
|
||||
on_expand_change (true);
|
||||
}
|
||||
group.set_sensitive (single_noti || group.is_expanded);
|
||||
}
|
||||
|
||||
Gdk.EventSequence ? sequence = gesture.get_current_sequence ();
|
||||
if (sequence == null) {
|
||||
gesture_in = false;
|
||||
}
|
||||
});
|
||||
gesture.update.connect ((gesture, sequence) => {
|
||||
Gtk.GestureSingle gesture_single = (Gtk.GestureSingle) gesture;
|
||||
if (sequence != gesture_single.get_current_sequence ()) return;
|
||||
|
||||
Gtk.Allocation allocation;
|
||||
double x, y;
|
||||
|
||||
get_allocation (out allocation);
|
||||
gesture.get_point (sequence, out x, out y);
|
||||
bool intersects = (x >= 0 && y >= 0 && x < allocation.width && y < allocation.height);
|
||||
if (gesture_in != intersects) {
|
||||
gesture_in = intersects;
|
||||
}
|
||||
});
|
||||
gesture.cancel.connect ((gesture, sequence) => {
|
||||
if (gesture_down) {
|
||||
gesture_down = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void set_classes () {
|
||||
unowned Gtk.StyleContext ctx = get_style_context ();
|
||||
ctx.remove_class (STYLE_CLASS_COLLAPSED);
|
||||
if (!group.is_expanded) {
|
||||
if (!ctx.has_class (STYLE_CLASS_COLLAPSED)) {
|
||||
ctx.add_class (STYLE_CLASS_COLLAPSED);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void set_icon () {
|
||||
if (is_empty ()) return;
|
||||
|
||||
unowned Notification first = (Notification) group.widgets.first ().data;
|
||||
unowned NotifyParams param = first.param;
|
||||
// Get the app icon
|
||||
Icon ? icon = null;
|
||||
if (param.desktop_app_info != null
|
||||
&& (icon = param.desktop_app_info.get_icon ()) != null) {
|
||||
app_icon.set_from_gicon (icon, Gtk.IconSize.INVALID);
|
||||
app_icon.show ();
|
||||
} else {
|
||||
app_icon.set_from_icon_name ("application-x-executable-symbolic",
|
||||
Gtk.IconSize.INVALID);
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns if there's more than one notification
|
||||
public bool only_single_notification () {
|
||||
unowned Gtk.Widget ? widget = group.widgets.nth_data (1);
|
||||
return widget == null;
|
||||
}
|
||||
|
||||
public void set_expanded (bool state) {
|
||||
group.set_expanded (state);
|
||||
group.set_sensitive (only_single_notification () || group.is_expanded);
|
||||
}
|
||||
|
||||
public bool toggle_expanded () {
|
||||
bool state = !group.is_expanded;
|
||||
set_expanded (state);
|
||||
return state;
|
||||
}
|
||||
|
||||
public void add_notification (Notification noti) {
|
||||
if (noti.param.urgency == UrgencyLevels.CRITICAL) {
|
||||
urgent_notifications.insert (noti.param.applied_id, true);
|
||||
unowned Gtk.StyleContext ctx = get_style_context ();
|
||||
if (!ctx.has_class (STYLE_CLASS_URGENT)) {
|
||||
ctx.add_class (STYLE_CLASS_URGENT);
|
||||
}
|
||||
}
|
||||
group.add (noti);
|
||||
if (!only_single_notification ()) {
|
||||
if (!group.is_expanded) {
|
||||
group.set_sensitive (false);
|
||||
}
|
||||
} else {
|
||||
set_icon ();
|
||||
}
|
||||
}
|
||||
|
||||
public void remove_notification (Notification noti) {
|
||||
urgent_notifications.remove (noti.param.applied_id);
|
||||
if (urgent_notifications.length == 0) {
|
||||
get_style_context ().remove_class (STYLE_CLASS_URGENT);
|
||||
}
|
||||
group.remove (noti);
|
||||
if (only_single_notification ()) {
|
||||
set_expanded (false);
|
||||
on_expand_change (false);
|
||||
}
|
||||
}
|
||||
|
||||
public List<weak Gtk.Widget> get_notifications () {
|
||||
return group.widgets.copy ();
|
||||
}
|
||||
|
||||
public unowned Notification ? get_latest_notification () {
|
||||
return (Notification ?) group.widgets.last ().data;
|
||||
}
|
||||
|
||||
public int64 get_time () {
|
||||
if (group.widgets.is_empty ()) return -1;
|
||||
return ((Notification) group.widgets.last ().data).param.time;
|
||||
}
|
||||
|
||||
public bool get_is_urgent () {
|
||||
return urgent_notifications.length > 0;
|
||||
}
|
||||
|
||||
public uint get_num_notifications () {
|
||||
return group.widgets.length ();
|
||||
}
|
||||
|
||||
public bool is_empty () {
|
||||
return group.widgets.is_empty ();
|
||||
}
|
||||
|
||||
public void close_all_notifications () {
|
||||
urgent_notifications.remove_all ();
|
||||
foreach (unowned Gtk.Widget widget in group.widgets) {
|
||||
var noti = (Notification) widget;
|
||||
if (noti != null) noti.close_notification (false);
|
||||
}
|
||||
}
|
||||
|
||||
public void update () {
|
||||
set_icon ();
|
||||
foreach (unowned Gtk.Widget widget in group.widgets) {
|
||||
var noti = (Notification) widget;
|
||||
if (noti != null) noti.set_time ();
|
||||
}
|
||||
}
|
||||
|
||||
public int get_relative_y (Gtk.Widget parent) {
|
||||
int dest_y;
|
||||
translate_coordinates (parent, 0, 0, null, out dest_y);
|
||||
return dest_y;
|
||||
}
|
||||
}
|
||||
|
||||
private class ExpandableGroup : Gtk.Container {
|
||||
const int NUM_STACKED_NOTIFICATIONS = 3;
|
||||
const int COLLAPSED_NOTIFICATION_OFFSET = 8;
|
||||
|
||||
public bool is_expanded { get; private set; default = true; }
|
||||
|
||||
private double animation_progress = 1.0;
|
||||
private double animation_progress_inv {
|
||||
get {
|
||||
return (1 - animation_progress);
|
||||
}
|
||||
}
|
||||
private Animation ? animation;
|
||||
|
||||
private unowned on_expand_change change_cb;
|
||||
|
||||
public List<unowned Gtk.Widget> widgets = new List<unowned Gtk.Widget> ();
|
||||
|
||||
public delegate void on_expand_change (bool state);
|
||||
|
||||
public ExpandableGroup (uint animation_duration, on_expand_change change_cb) {
|
||||
base.set_has_window (false);
|
||||
base.set_can_focus (true);
|
||||
base.set_redraw_on_allocate (false);
|
||||
|
||||
this.change_cb = change_cb;
|
||||
animation = new Animation (this, animation_duration,
|
||||
Animation.ease_in_out_cubic,
|
||||
animation_value_cb,
|
||||
animation_done_cb);
|
||||
|
||||
this.show ();
|
||||
|
||||
set_expanded (false);
|
||||
}
|
||||
|
||||
public void set_expanded (bool value) {
|
||||
if (is_expanded == value) return;
|
||||
is_expanded = value;
|
||||
|
||||
animate (is_expanded ? 1 : 0);
|
||||
|
||||
this.queue_resize ();
|
||||
|
||||
change_cb (is_expanded);
|
||||
}
|
||||
|
||||
public override void add (Gtk.Widget widget) {
|
||||
widget.set_parent (this);
|
||||
widgets.append (widget);
|
||||
}
|
||||
|
||||
public override void remove (Gtk.Widget widget) {
|
||||
widget.unparent ();
|
||||
widgets.remove (widget);
|
||||
if (this.get_visible () && widget.get_visible ()) {
|
||||
this.queue_resize_no_redraw ();
|
||||
}
|
||||
}
|
||||
|
||||
public override void forall_internal (bool include_internals, Gtk.Callback callback) {
|
||||
foreach (unowned Gtk.Widget widget in widgets) {
|
||||
callback (widget);
|
||||
}
|
||||
}
|
||||
|
||||
public override Gtk.SizeRequestMode get_request_mode () {
|
||||
return Gtk.SizeRequestMode.HEIGHT_FOR_WIDTH;
|
||||
}
|
||||
|
||||
public override void size_allocate (Gtk.Allocation allocation) {
|
||||
base.size_allocate (allocation);
|
||||
|
||||
int length = (int) widgets.length ();
|
||||
if (length == 0) return;
|
||||
|
||||
uint border_width = get_border_width ();
|
||||
|
||||
Gtk.Allocation prev_allocation = Gtk.Allocation ();
|
||||
prev_allocation.y = allocation.y;
|
||||
|
||||
// The height of the most recent notification
|
||||
unowned Gtk.Widget last = widgets.last ().data;
|
||||
int target_height = 0;
|
||||
last.get_preferred_height_for_width (allocation.width,
|
||||
out target_height, null);
|
||||
|
||||
for (int i = length - 1; i >= 0; i--) {
|
||||
unowned Gtk.Widget widget = widgets.nth_data (i);
|
||||
if (widget != null && widget.get_visible ()) {
|
||||
int height;
|
||||
widget.get_preferred_height_for_width (allocation.width,
|
||||
out height, null);
|
||||
|
||||
Gtk.Allocation alloc = Gtk.Allocation ();
|
||||
alloc.x = allocation.x + (int) border_width;
|
||||
alloc.y = (int) (prev_allocation.y +
|
||||
animation_progress * prev_allocation.height +
|
||||
border_width);
|
||||
alloc.width = allocation.width - 2 * (int) border_width;
|
||||
alloc.height = height;
|
||||
// Expand smaller stacked notifications to the expected height
|
||||
// But only when the animation has finished
|
||||
if (target_height > height && !is_expanded && animation_progress == 0) {
|
||||
alloc.height = target_height;
|
||||
}
|
||||
alloc.height -= 2 * (int) border_width;
|
||||
|
||||
// Add the collapsed offset to only stacked notifications.
|
||||
// Excludes notifications index > NUM_STACKED_NOTIFICATIONS
|
||||
if (i < length - 1 && length - 1 - i < NUM_STACKED_NOTIFICATIONS) {
|
||||
alloc.y += (int) (animation_progress_inv * COLLAPSED_NOTIFICATION_OFFSET);
|
||||
}
|
||||
|
||||
prev_allocation = alloc;
|
||||
widget.size_allocate (alloc);
|
||||
|
||||
if (get_realized ()) {
|
||||
widget.show ();
|
||||
}
|
||||
}
|
||||
if (get_realized ()) {
|
||||
widget.set_child_visible (true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void get_preferred_height_for_width (int width,
|
||||
out int minimum_height,
|
||||
out int natural_height) {
|
||||
minimum_height = 0;
|
||||
natural_height = 0;
|
||||
|
||||
foreach (unowned Gtk.Widget widget in widgets) {
|
||||
if (widget != null && widget.get_visible ()) {
|
||||
int widget_minimum_height = 0;
|
||||
int widget_natural_height = 0;
|
||||
widget.get_preferred_height_for_width (width,
|
||||
out widget_minimum_height,
|
||||
out widget_natural_height);
|
||||
|
||||
minimum_height += widget_minimum_height;
|
||||
natural_height += widget_natural_height;
|
||||
}
|
||||
}
|
||||
|
||||
int target_minimum_height;
|
||||
int target_natural_height;
|
||||
get_height_for_latest_notifications (width,
|
||||
out target_minimum_height,
|
||||
out target_natural_height);
|
||||
minimum_height = (int) Animation.lerp (minimum_height,
|
||||
target_minimum_height,
|
||||
animation_progress_inv);
|
||||
natural_height = (int) Animation.lerp (natural_height,
|
||||
target_natural_height,
|
||||
animation_progress_inv);
|
||||
}
|
||||
|
||||
public override bool draw (Cairo.Context cr) {
|
||||
int length = (int) widgets.length ();
|
||||
if (length == 0) return true;
|
||||
|
||||
Gtk.Allocation alloc;
|
||||
get_allocated_size (out alloc, null);
|
||||
|
||||
unowned Gtk.Widget latest = widgets.nth_data (length - 1);
|
||||
Gtk.Allocation latest_alloc;
|
||||
latest.get_allocated_size (out latest_alloc, null);
|
||||
|
||||
Cairo.Pattern hover_gradient = new Cairo.Pattern.linear (0, 0, 0, 1);
|
||||
hover_gradient.add_color_stop_rgba (0, 1, 1, 1, 1);
|
||||
hover_gradient.add_color_stop_rgba (1, 1, 1, 1, 1);
|
||||
|
||||
// Fades from the bottom at 0.5 -> top at 0.0 opacity
|
||||
Cairo.Pattern fade_gradient = new Cairo.Pattern.linear (0, 0, 0, 1);
|
||||
fade_gradient.add_color_stop_rgba (0, 1, 1, 1, animation_progress_inv);
|
||||
fade_gradient.add_color_stop_rgba (1, 1, 1, 1, 0);
|
||||
// Cross-fades in the non visible stacked notifications when expanded
|
||||
Cairo.Pattern cross_fade_pattern =
|
||||
new Cairo.Pattern.rgba (1, 1, 1, 1.5 * animation_progress_inv);
|
||||
|
||||
int width = alloc.width;
|
||||
|
||||
for (int i = 0; i < length; i++) {
|
||||
// Skip drawing excess notifications
|
||||
if (!is_expanded &&
|
||||
animation_progress == 0 &&
|
||||
i < length - NUM_STACKED_NOTIFICATIONS) {
|
||||
continue;
|
||||
}
|
||||
|
||||
unowned Gtk.Widget widget = widgets.nth_data (i);
|
||||
int preferred_height;
|
||||
widget.get_preferred_height_for_width (width,
|
||||
out preferred_height, null);
|
||||
Gtk.Allocation widget_alloc;
|
||||
widget.get_allocated_size (out widget_alloc, null);
|
||||
|
||||
int height_diff = latest_alloc.height - widget_alloc.height;
|
||||
|
||||
cr.save ();
|
||||
|
||||
// Translate to the widgets allocated y
|
||||
double translate_y = widget_alloc.y - alloc.y;
|
||||
// Move down even more if the height is larger than the latest
|
||||
// in the stack (helps with only rendering the bottom portion)
|
||||
translate_y += height_diff * animation_progress_inv;
|
||||
cr.translate (0, translate_y);
|
||||
|
||||
// Scale down lower notifications in the stack
|
||||
if (i + 1 != length) {
|
||||
double scale = double.min (
|
||||
animation_progress + Math.pow (0.95, length - 1 - i), 1);
|
||||
// Moves the scaled notification to the center of X and bottom y
|
||||
cr.translate ((widget_alloc.width - width * scale) * 0.5,
|
||||
widget_alloc.height * (1 - scale));
|
||||
cr.scale (scale, scale);
|
||||
}
|
||||
|
||||
int lerped_y = (int) Animation.lerp (-height_diff, 0, animation_progress);
|
||||
int lerped_height = (int) Animation.lerp (latest_alloc.height,
|
||||
widget_alloc.height,
|
||||
animation_progress);
|
||||
// Clip to the size of the latest notification
|
||||
// (fixes issue where a larger bottom notification would
|
||||
// be visible above)
|
||||
cr.rectangle (0, lerped_y, width, lerped_height);
|
||||
cr.clip ();
|
||||
|
||||
// Draw patterns on the notification
|
||||
cr.push_group ();
|
||||
widget.draw (cr);
|
||||
if (i + 1 != length) {
|
||||
// Draw Fade Gradient
|
||||
cr.save ();
|
||||
cr.translate (0, lerped_y);
|
||||
cr.scale (1, lerped_height * 0.5);
|
||||
cr.set_source (fade_gradient);
|
||||
cr.rectangle (0, 0, width, lerped_height * 0.5);
|
||||
cr.set_operator (Cairo.Operator.DEST_OUT);
|
||||
cr.fill ();
|
||||
cr.restore ();
|
||||
}
|
||||
// Draw notification cross-fade
|
||||
if (i < length - NUM_STACKED_NOTIFICATIONS) {
|
||||
cr.save ();
|
||||
cr.translate (0, lerped_y);
|
||||
cr.scale (1, lerped_height);
|
||||
cr.set_source (cross_fade_pattern);
|
||||
cr.rectangle (0, 0, width, lerped_height);
|
||||
cr.set_operator (Cairo.Operator.DEST_OUT);
|
||||
cr.fill ();
|
||||
cr.restore ();
|
||||
}
|
||||
cr.pop_group_to_source ();
|
||||
cr.paint ();
|
||||
|
||||
cr.restore ();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/** Gets the collapsed height (first notification + stacked) */
|
||||
private void get_height_for_latest_notifications (int width,
|
||||
out int minimum_height,
|
||||
out int natural_height) {
|
||||
minimum_height = 0;
|
||||
natural_height = 0;
|
||||
|
||||
uint length = widgets.length ();
|
||||
|
||||
if (length == 0) return;
|
||||
|
||||
int offset = 0;
|
||||
for (uint i = 1;
|
||||
i < length && i < NUM_STACKED_NOTIFICATIONS;
|
||||
i++) {
|
||||
offset += COLLAPSED_NOTIFICATION_OFFSET;
|
||||
}
|
||||
|
||||
unowned Gtk.Widget last = widgets.last ().data;
|
||||
last.get_preferred_height_for_width (width,
|
||||
out minimum_height,
|
||||
out natural_height);
|
||||
|
||||
minimum_height += offset;
|
||||
natural_height += offset;
|
||||
}
|
||||
|
||||
void animation_value_cb (double progress) {
|
||||
this.animation_progress = progress;
|
||||
|
||||
this.queue_resize ();
|
||||
}
|
||||
|
||||
void animation_done_cb () {
|
||||
animation.dispose ();
|
||||
|
||||
this.queue_allocate ();
|
||||
}
|
||||
|
||||
void animate (double to) {
|
||||
animation.stop ();
|
||||
animation.start (animation_progress, to);
|
||||
}
|
||||
}
|
||||
}
|
@@ -151,6 +151,7 @@ namespace SwayNotificationCenter {
|
||||
public void change_visibility (bool value) {
|
||||
if (!value) {
|
||||
close_all_notifications ();
|
||||
close ();
|
||||
} else {
|
||||
this.set_anchor ();
|
||||
}
|
||||
@@ -170,10 +171,9 @@ namespace SwayNotificationCenter {
|
||||
}
|
||||
}
|
||||
|
||||
private void remove_notification (Notification ? noti, bool replaces) {
|
||||
private void remove_notification (Notification ? noti, bool dismiss) {
|
||||
// Remove notification and its destruction timeout
|
||||
if (noti != null) {
|
||||
#if HAVE_LATEST_GTK_LAYER_SHELL
|
||||
if (noti.has_inline_reply) {
|
||||
inline_reply_notifications.remove (noti.param.applied_id);
|
||||
if (inline_reply_notifications.size == 0
|
||||
@@ -183,12 +183,11 @@ namespace SwayNotificationCenter {
|
||||
this, GtkLayerShell.KeyboardMode.NONE);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
noti.remove_noti_timeout ();
|
||||
noti.destroy ();
|
||||
}
|
||||
|
||||
if (!replaces
|
||||
if (dismiss
|
||||
&& (!get_realized ()
|
||||
|| !get_mapped ()
|
||||
|| !(get_child () is Gtk.Widget)
|
||||
@@ -198,15 +197,13 @@ namespace SwayNotificationCenter {
|
||||
}
|
||||
}
|
||||
|
||||
public void add_notification (NotifyParams param,
|
||||
NotiDaemon noti_daemon) {
|
||||
public void add_notification (NotifyParams param) {
|
||||
var noti = new Notification.timed (param,
|
||||
noti_daemon,
|
||||
swaync_daemon.noti_daemon,
|
||||
NotificationType.POPUP,
|
||||
ConfigModel.instance.timeout,
|
||||
ConfigModel.instance.timeout_low,
|
||||
ConfigModel.instance.timeout_critical);
|
||||
#if HAVE_LATEST_GTK_LAYER_SHELL
|
||||
if (noti.has_inline_reply) {
|
||||
inline_reply_notifications.add (param.applied_id);
|
||||
|
||||
@@ -216,7 +213,6 @@ namespace SwayNotificationCenter {
|
||||
this, GtkLayerShell.KeyboardMode.ON_DEMAND);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
if (list_reverse) {
|
||||
box.pack_start (noti);
|
||||
@@ -234,16 +230,31 @@ namespace SwayNotificationCenter {
|
||||
scroll_to_start (list_reverse);
|
||||
}
|
||||
|
||||
public void close_notification (uint32 id, bool replaces) {
|
||||
public void close_notification (uint32 id, bool dismiss) {
|
||||
foreach (var w in box.get_children ()) {
|
||||
var noti = (Notification) w;
|
||||
if (noti != null && noti.param.applied_id == id) {
|
||||
remove_notification (noti, replaces);
|
||||
remove_notification (noti, dismiss);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void replace_notification (uint32 id, NotifyParams new_params) {
|
||||
foreach (var w in box.get_children ()) {
|
||||
var noti = (Notification) w;
|
||||
if (noti != null && noti.param.applied_id == id) {
|
||||
noti.replace_notification (new_params);
|
||||
// Position the notification in the beginning of the list
|
||||
box.reorder_child (noti, (int) box.get_children ().length ());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Display a new notification if the old one isn't visible
|
||||
add_notification (new_params);
|
||||
}
|
||||
|
||||
public uint32 ? get_latest_notification () {
|
||||
List<weak Gtk.Widget> children = box.get_children ();
|
||||
if (children.is_empty ()) return null;
|
||||
|
361
src/style.css
361
src/style.css
@@ -1,361 +0,0 @@
|
||||
/*
|
||||
* vim: ft=less
|
||||
*/
|
||||
|
||||
@define-color cc-bg rgba(0, 0, 0, 0.7);
|
||||
|
||||
@define-color noti-border-color rgba(255, 255, 255, 0.15);
|
||||
@define-color noti-bg rgb(48, 48, 48);
|
||||
@define-color noti-bg-darker rgb(38, 38, 38);
|
||||
@define-color noti-bg-hover rgb(56, 56, 56);
|
||||
@define-color noti-bg-focus rgba(68, 68, 68, 0.6);
|
||||
@define-color noti-close-bg rgba(255, 255, 255, 0.1);
|
||||
@define-color noti-close-bg-hover rgba(255, 255, 255, 0.15);
|
||||
|
||||
@define-color text-color rgb(255, 255, 255);
|
||||
@define-color text-color-disabled rgb(150, 150, 150);
|
||||
|
||||
@define-color bg-selected rgb(0, 128, 255);
|
||||
|
||||
.notification-row {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.notification-row:focus,
|
||||
.notification-row:hover {
|
||||
background: @noti-bg-focus;
|
||||
}
|
||||
|
||||
.notification {
|
||||
border-radius: 12px;
|
||||
margin: 6px 12px;
|
||||
box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.3), 0 1px 3px 1px rgba(0, 0, 0, 0.7),
|
||||
0 2px 6px 2px rgba(0, 0, 0, 0.3);
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/* Uncomment to enable specific urgency colors
|
||||
.low {
|
||||
background: yellow;
|
||||
padding: 6px;
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
.normal {
|
||||
background: green;
|
||||
padding: 6px;
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
.critical {
|
||||
background: red;
|
||||
padding: 6px;
|
||||
border-radius: 12px;
|
||||
}
|
||||
*/
|
||||
|
||||
.notification-content {
|
||||
background: transparent;
|
||||
padding: 6px;
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
.close-button {
|
||||
background: @noti-close-bg;
|
||||
color: @text-color;
|
||||
text-shadow: none;
|
||||
padding: 0;
|
||||
border-radius: 100%;
|
||||
margin-top: 10px;
|
||||
margin-right: 16px;
|
||||
box-shadow: none;
|
||||
border: none;
|
||||
min-width: 24px;
|
||||
min-height: 24px;
|
||||
}
|
||||
|
||||
.close-button:hover {
|
||||
box-shadow: none;
|
||||
background: @noti-close-bg-hover;
|
||||
transition: all 0.15s ease-in-out;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.notification-default-action,
|
||||
.notification-action {
|
||||
padding: 4px;
|
||||
margin: 0;
|
||||
box-shadow: none;
|
||||
background: @noti-bg;
|
||||
border: 1px solid @noti-border-color;
|
||||
color: @text-color;
|
||||
transition: all 0.15s ease-in-out;
|
||||
}
|
||||
|
||||
.notification-default-action:hover,
|
||||
.notification-action:hover {
|
||||
-gtk-icon-effect: none;
|
||||
background: @noti-bg-hover;
|
||||
}
|
||||
|
||||
.notification-default-action {
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
/* When alternative actions are visible */
|
||||
.notification-default-action:not(:only-child) {
|
||||
border-bottom-left-radius: 0px;
|
||||
border-bottom-right-radius: 0px;
|
||||
}
|
||||
|
||||
.notification-action {
|
||||
border-radius: 0px;
|
||||
border-top: none;
|
||||
border-right: none;
|
||||
}
|
||||
|
||||
/* add bottom border radius to eliminate clipping */
|
||||
.notification-action:first-child {
|
||||
border-bottom-left-radius: 10px;
|
||||
}
|
||||
|
||||
.notification-action:last-child {
|
||||
border-bottom-right-radius: 10px;
|
||||
border-right: 1px solid @noti-border-color;
|
||||
}
|
||||
|
||||
.inline-reply {
|
||||
margin-top: 8px;
|
||||
}
|
||||
.inline-reply-entry {
|
||||
background: @noti-bg-darker;
|
||||
color: @text-color;
|
||||
caret-color: @text-color;
|
||||
border: 1px solid @noti-border-color;
|
||||
border-radius: 12px;
|
||||
}
|
||||
.inline-reply-button {
|
||||
margin-left: 4px;
|
||||
background: @noti-bg;
|
||||
border: 1px solid @noti-border-color;
|
||||
border-radius: 12px;
|
||||
color: @text-color;
|
||||
}
|
||||
.inline-reply-button:disabled {
|
||||
background: initial;
|
||||
color: @text-color-disabled;
|
||||
border: 1px solid transparent;
|
||||
}
|
||||
.inline-reply-button:hover {
|
||||
background: @noti-bg-hover;
|
||||
}
|
||||
|
||||
.image {
|
||||
}
|
||||
|
||||
.body-image {
|
||||
margin-top: 6px;
|
||||
background-color: white;
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
.summary {
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
background: transparent;
|
||||
color: @text-color;
|
||||
text-shadow: none;
|
||||
}
|
||||
|
||||
.time {
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
background: transparent;
|
||||
color: @text-color;
|
||||
text-shadow: none;
|
||||
margin-right: 18px;
|
||||
}
|
||||
|
||||
.body {
|
||||
font-size: 15px;
|
||||
font-weight: normal;
|
||||
background: transparent;
|
||||
color: @text-color;
|
||||
text-shadow: none;
|
||||
}
|
||||
|
||||
.control-center {
|
||||
background: @cc-bg;
|
||||
}
|
||||
|
||||
.control-center-list {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.control-center-list-placeholder {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.floating-notifications {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
/* Window behind control center and on all other monitors */
|
||||
.blank-window {
|
||||
background: alpha(black, 0.25);
|
||||
}
|
||||
|
||||
/*** Widgets ***/
|
||||
|
||||
/* Title widget */
|
||||
.widget-title {
|
||||
margin: 8px;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
.widget-title > button {
|
||||
font-size: initial;
|
||||
color: @text-color;
|
||||
text-shadow: none;
|
||||
background: @noti-bg;
|
||||
border: 1px solid @noti-border-color;
|
||||
box-shadow: none;
|
||||
border-radius: 12px;
|
||||
}
|
||||
.widget-title > button:hover {
|
||||
background: @noti-bg-hover;
|
||||
}
|
||||
|
||||
/* DND widget */
|
||||
.widget-dnd {
|
||||
margin: 8px;
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
.widget-dnd > switch {
|
||||
font-size: initial;
|
||||
border-radius: 12px;
|
||||
background: @noti-bg;
|
||||
border: 1px solid @noti-border-color;
|
||||
box-shadow: none;
|
||||
}
|
||||
.widget-dnd > switch:checked {
|
||||
background: @bg-selected;
|
||||
}
|
||||
.widget-dnd > switch slider {
|
||||
background: @noti-bg-hover;
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
/* Label widget */
|
||||
.widget-label {
|
||||
margin: 8px;
|
||||
}
|
||||
.widget-label > label {
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
/* Mpris widget */
|
||||
.widget-mpris {
|
||||
/* The parent to all players */
|
||||
}
|
||||
.widget-mpris-player {
|
||||
padding: 8px;
|
||||
margin: 8px;
|
||||
}
|
||||
.widget-mpris-title {
|
||||
font-weight: bold;
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
.widget-mpris-subtitle {
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
/* Buttons widget */
|
||||
.widget-buttons-grid {
|
||||
padding: 8px;
|
||||
margin: 8px;
|
||||
border-radius: 12px;
|
||||
background-color: @noti-bg;
|
||||
}
|
||||
|
||||
.widget-buttons-grid>flowbox>flowboxchild>button{
|
||||
background: @noti-bg;
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
.widget-buttons-grid>flowbox>flowboxchild>button:hover {
|
||||
background: @noti-bg-hover;
|
||||
}
|
||||
|
||||
/* Menubar widget */
|
||||
.widget-menubar>box>.menu-button-bar>button {
|
||||
border: none;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
/* .AnyName { Name defined in config after #
|
||||
background-color: @noti-bg;
|
||||
padding: 8px;
|
||||
margin: 8px;
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
.AnyName>button {
|
||||
background: transparent;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.AnyName>button:hover {
|
||||
background-color: @noti-bg-hover;
|
||||
} */
|
||||
|
||||
.topbar-buttons>button { /* Name defined in config after # */
|
||||
border: none;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
/* Volume widget */
|
||||
|
||||
.widget-volume {
|
||||
background-color: @noti-bg;
|
||||
padding: 8px;
|
||||
margin: 8px;
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
.widget-volume>box>button {
|
||||
background: transparent;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.per-app-volume {
|
||||
background-color: @noti-bg-alt;
|
||||
padding: 4px 8px 8px 8px;
|
||||
margin: 0px 8px 8px 8px;
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
/* Backlight widget */
|
||||
.widget-backlight {
|
||||
background-color: @noti-bg;
|
||||
padding: 8px;
|
||||
margin: 8px;
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
/* Title widget */
|
||||
.widget-inhibitors {
|
||||
margin: 8px;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
.widget-inhibitors > button {
|
||||
font-size: initial;
|
||||
color: @text-color;
|
||||
text-shadow: none;
|
||||
background: @noti-bg;
|
||||
border: 1px solid @noti-border-color;
|
||||
box-shadow: none;
|
||||
border-radius: 12px;
|
||||
}
|
||||
.widget-inhibitors > button:hover {
|
||||
background: @noti-bg-hover;
|
||||
}
|
@@ -251,7 +251,7 @@ namespace SwayNotificationCenter {
|
||||
|
||||
/** Closes a specific notification with the `id` */
|
||||
public void close_notification (uint32 id) throws DBusError, IOError {
|
||||
noti_daemon.control_center.close_notification (id);
|
||||
noti_daemon.control_center.close_notification (id, true);
|
||||
}
|
||||
|
||||
/**
|
||||
|
Reference in New Issue
Block a user