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
|
- name: Install packages
|
||||||
run: |
|
run: |
|
||||||
pacman-key --init
|
pacman-key --init
|
||||||
pacman -Sy
|
pacman -Syu --noconfirm
|
||||||
|
|
||||||
- name: Add builduser
|
- name: Add builduser
|
||||||
run: |
|
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
|
# 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
|
# Controls when the workflow will run
|
||||||
on:
|
on:
|
||||||
@@ -14,12 +14,12 @@ on:
|
|||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
ubuntu-LTS-build:
|
ubuntu-build:
|
||||||
container: ubuntu:22.04
|
container: ubuntu:23.10
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
env:
|
env:
|
||||||
DEBIAN_FRONTEND: noninteractive
|
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:
|
steps:
|
||||||
- name: Install packages
|
- name: Install packages
|
||||||
run: |
|
run: |
|
||||||
|
105
README.md
105
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
|
only tested with the default GTK **Adwaita** theme. Usage of any third-party
|
||||||
theme might require extra tweaks to the default CSS style file*
|
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
|
Table of Contents
|
||||||
=================
|
=================
|
||||||
|
|
||||||
* [Screenshots](#screenshots)
|
|
||||||
* [Want to show off your sick config?](#want-to-show-off-your-sick-config)
|
* [Want to show off your sick config?](#want-to-show-off-your-sick-config)
|
||||||
* [Features](#features)
|
* [Features](#features)
|
||||||
* [Available Widgets](#available-widgets)
|
* [Available Widgets](#available-widgets)
|
||||||
@@ -38,24 +43,20 @@ Table of Contents
|
|||||||
* [Run](#run)
|
* [Run](#run)
|
||||||
* [Control Center Shortcuts](#control-center-shortcuts)
|
* [Control Center Shortcuts](#control-center-shortcuts)
|
||||||
* [Configuring](#configuring)
|
* [Configuring](#configuring)
|
||||||
|
* [Toggle Buttons](#toggle-buttons)
|
||||||
* [Notification Inhibition](#notification-inhibition)
|
* [Notification Inhibition](#notification-inhibition)
|
||||||
* [Scripting](#scripting)
|
* [Scripting](#scripting)
|
||||||
* [Disable scripting](#disable-scripting)
|
* [Disable scripting](#disable-scripting)
|
||||||
* [i3status-rs Example](#i3status-rs-example)
|
* [i3status-rs Example](#i3status-rs-example)
|
||||||
* [Waybar Example](#waybar-example)
|
* [Waybar Example](#waybar-example)
|
||||||
|
|
||||||
## Screenshots
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
## Want to show off your sick config?
|
## Want to show off your sick config?
|
||||||
|
|
||||||
Post your setup here: [Config flex 💪](https://github.com/ErikReider/SwayNotificationCenter/discussions/183)
|
Post your setup here: [Config flex 💪](https://github.com/ErikReider/SwayNotificationCenter/discussions/183)
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
|
- Grouped notifications
|
||||||
- Keyboard shortcuts
|
- Keyboard shortcuts
|
||||||
- Notification body markup with image support
|
- Notification body markup with image support
|
||||||
- Inline replies
|
- Inline replies
|
||||||
@@ -95,12 +96,19 @@ These widgets can be customized, added, removed and even reordered
|
|||||||
|
|
||||||
## Install
|
## Install
|
||||||
|
|
||||||
|
### Alpine Linux
|
||||||
|
|
||||||
|
```zsh
|
||||||
|
apk add swaync
|
||||||
|
````
|
||||||
|
|
||||||
### Arch
|
### Arch
|
||||||
|
|
||||||
The package is available on the AUR:
|
```zsh
|
||||||
|
sudo pacman -S swaync
|
||||||
|
```
|
||||||
|
|
||||||
- [swaync](https://aur.archlinux.org/packages/swaync/)
|
Alternatively, [swaync-git](https://aur.archlinux.org/packages/swaync-git/) is available on the AUR.
|
||||||
- [swaync-git](https://aur.archlinux.org/packages/swaync-git/)
|
|
||||||
|
|
||||||
### Fedora
|
### Fedora
|
||||||
|
|
||||||
@@ -126,7 +134,7 @@ An **unofficial** ebuild is available in [GURU](https://github.com/gentoo/guru)
|
|||||||
|
|
||||||
```zsh
|
```zsh
|
||||||
eselect repository enable guru
|
eselect repository enable guru
|
||||||
emaint sync -r guru
|
emaint sync --repo guru
|
||||||
emerge --ask gui-apps/swaync
|
emerge --ask gui-apps/swaync
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -140,7 +148,7 @@ sudo zypper install SwayNotificationCenter
|
|||||||
|
|
||||||
Lunar and later:
|
Lunar and later:
|
||||||
|
|
||||||
```
|
```zsh
|
||||||
sudo apt install sway-notification-center
|
sudo apt install sway-notification-center
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -149,14 +157,14 @@ sudo apt install sway-notification-center
|
|||||||
|
|
||||||
Bookworm and later:
|
Bookworm and later:
|
||||||
|
|
||||||
```
|
```zsh
|
||||||
sudo apt install sway-notification-center
|
sudo apt install sway-notification-center
|
||||||
```
|
```
|
||||||
|
|
||||||
### Guix
|
### Guix
|
||||||
|
|
||||||
The simplest way is to install it to user's profile:
|
The simplest way is to install it to user's profile:
|
||||||
```
|
```zsh
|
||||||
guix install swaynotificationcenter
|
guix install swaynotificationcenter
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -172,8 +180,31 @@ But we recommend to use [Guix Home](https://guix.gnu.org/manual/devel/en/html_no
|
|||||||
|
|
||||||
### Other
|
### 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
|
```zsh
|
||||||
meson build
|
meson setup build --prefix=/usr
|
||||||
ninja -C build
|
ninja -C build
|
||||||
meson install -C build
|
meson install -C build
|
||||||
```
|
```
|
||||||
@@ -226,7 +257,7 @@ To reload css after changes
|
|||||||
- Shift+D: Toggle Do Not Disturb
|
- Shift+D: Toggle Do Not Disturb
|
||||||
- Buttons 1-9: Execute alternative actions
|
- Buttons 1-9: Execute alternative actions
|
||||||
- Left click button / actions: Activate notification action
|
- Left click button / actions: Activate notification action
|
||||||
- Right click notification: Close notification
|
- Middle/Right click notification: Close notification
|
||||||
|
|
||||||
## Configuring
|
## Configuring
|
||||||
|
|
||||||
@@ -237,11 +268,39 @@ See `swaync(5)` man page for more information
|
|||||||
To reload the config, you'll need to run `swaync-client --reload-config`
|
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
|
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.
|
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.
|
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
|
## Notification Inhibition
|
||||||
|
|
||||||
Notifications can be inhibited through the provided `swaync-client` executable
|
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:
|
Config properties:
|
||||||
|
|
||||||
```json
|
```jsonc
|
||||||
{
|
{
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"example-script": {
|
"example-script": {
|
||||||
@@ -289,13 +348,13 @@ Config properties:
|
|||||||
"category": "Notification category Regex"
|
"category": "Notification category Regex"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
other non scripting properties...
|
// other non scripting properties...
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
`config.json` example:
|
`config.json` example:
|
||||||
|
|
||||||
```json
|
```jsonc
|
||||||
{
|
{
|
||||||
"scripts": {
|
"scripts": {
|
||||||
// This script will only run when Spotify sends a notification containing
|
// This script will only run when Spotify sends a notification containing
|
||||||
@@ -307,7 +366,7 @@ Config properties:
|
|||||||
"body": "Rick Astley - Whenever You Need Somebody"
|
"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
|
Alternatively, the number of notifications can be shown by adding `{}` anywhere in the `format` field in the Waybar config
|
||||||
|
|
||||||
```json
|
```jsonc
|
||||||
"custom/notification": {
|
"custom/notification": {
|
||||||
"format": "{} {icon}",
|
"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>
|
# Maintainer: Erik Reider <erik.reider@protonmail.com>
|
||||||
pkgname=swaync
|
pkgname=swaync
|
||||||
pkgver=0.9.0
|
pkgver=0.10.1
|
||||||
pkgrel=1
|
pkgrel=1
|
||||||
pkgdesc="A simple notification daemon with a GTK panel for checking previous notifications like other DEs"
|
pkgdesc="A simple notification daemon with a GTK panel for checking previous notifications like other DEs"
|
||||||
_pkgfoldername=SwayNotificationCenter
|
_pkgfoldername=SwayNotificationCenter
|
||||||
@@ -12,12 +12,12 @@ arch=(
|
|||||||
'armv7h' # ARM v7 hardfloat
|
'armv7h' # ARM v7 hardfloat
|
||||||
)
|
)
|
||||||
license=(GPL3)
|
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")
|
conflicts=("swaync" "swaync-client")
|
||||||
provides=("swaync" "swaync-client" "notification-daemon")
|
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")
|
source=("${_pkgfoldername}-${pkgver}.tar.gz::${url}/archive/v${pkgver}.tar.gz")
|
||||||
sha256sums=('3f00bc858b7b3610e88ef0f6ee64d727892dd82f280f1dfc01dde863c2ea3376')
|
sha256sums=('5586d8a679dde5e530cb8b6f0c86abdd0d5e41362fc1c4e56e2211edea0f7a13')
|
||||||
|
|
||||||
build() {
|
build() {
|
||||||
arch-meson "${_pkgfoldername}-${pkgver}" build -Dscripting=true
|
arch-meson "${_pkgfoldername}-${pkgver}" build -Dscripting=true
|
||||||
|
@@ -1,8 +1,8 @@
|
|||||||
# vim: ft=PKGBUILD
|
# vim: ft=sh
|
||||||
# Maintainer: Erik Reider <erik.reider@protonmail.com>
|
# Maintainer: Erik Reider <erik.reider@protonmail.com>
|
||||||
pkgname=swaync-git
|
pkgname=swaync-git
|
||||||
_pkgname=swaync
|
_pkgname=swaync
|
||||||
pkgver=r448.ba4a266
|
pkgver=r517.4275fa3
|
||||||
pkgrel=1
|
pkgrel=1
|
||||||
pkgdesc="A simple notification daemon with a GTK panel for checking previous notifications like other DEs"
|
pkgdesc="A simple notification daemon with a GTK panel for checking previous notifications like other DEs"
|
||||||
url="https://github.com/ErikReider/SwayNotificationCenter"
|
url="https://github.com/ErikReider/SwayNotificationCenter"
|
||||||
@@ -12,10 +12,10 @@ arch=(
|
|||||||
'armv7h' # ARM v7 hardfloat
|
'armv7h' # ARM v7 hardfloat
|
||||||
)
|
)
|
||||||
license=('GPL3')
|
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")
|
conflicts=("swaync" "swaync-client")
|
||||||
provides=("swaync" "swaync-client" "notification-daemon")
|
provides=("swaync" "swaync-client" "notification-daemon")
|
||||||
makedepends=(vala meson git scdoc)
|
makedepends=("vala>=0.56" meson git scdoc sassc)
|
||||||
source=("$_pkgname::git+$url")
|
source=("$_pkgname::git+$url")
|
||||||
sha256sums=('SKIP')
|
sha256sums=('SKIP')
|
||||||
|
|
||||||
|
@@ -2,7 +2,7 @@
|
|||||||
%global alt_pkg_name swaync
|
%global alt_pkg_name swaync
|
||||||
|
|
||||||
Name: {{{ git_dir_name }}}
|
Name: {{{ git_dir_name }}}
|
||||||
Version: 0.9.0
|
Version: 0.10.1
|
||||||
Release: 1%{?dist}
|
Release: 1%{?dist}
|
||||||
Summary: Notification daemon with GTK GUI
|
Summary: Notification daemon with GTK GUI
|
||||||
Provides: desktop-notification-daemon
|
Provides: desktop-notification-daemon
|
||||||
@@ -14,7 +14,7 @@ VCS: {{{ git_dir_vcs }}}
|
|||||||
Source: {{{ git_dir_pack }}}
|
Source: {{{ git_dir_pack }}}
|
||||||
|
|
||||||
BuildRequires: meson >= 0.51.0
|
BuildRequires: meson >= 0.51.0
|
||||||
BuildRequires: vala
|
BuildRequires: vala >= 0.56
|
||||||
BuildRequires: scdoc
|
BuildRequires: scdoc
|
||||||
BuildRequires: pkgconfig(gtk+-3.0) >= 3.22
|
BuildRequires: pkgconfig(gtk+-3.0) >= 3.22
|
||||||
BuildRequires: pkgconfig(gtk-layer-shell-0) >= 0.1
|
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(bash-completion)
|
||||||
BuildRequires: pkgconfig(fish)
|
BuildRequires: pkgconfig(fish)
|
||||||
BuildRequires: pkgconfig(libpulse)
|
BuildRequires: pkgconfig(libpulse)
|
||||||
|
BuildRequires: pkgconfig(granite)
|
||||||
BuildRequires: systemd-devel
|
BuildRequires: systemd-devel
|
||||||
BuildRequires: systemd
|
BuildRequires: systemd
|
||||||
|
BuildRequires: sassc
|
||||||
|
BuildRequires: granite-devel
|
||||||
|
|
||||||
|
Requires: gvfs
|
||||||
|
Requires: libnotify
|
||||||
Requires: dbus
|
Requires: dbus
|
||||||
%{?systemd_requires}
|
%{?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')
|
install_dir: join_paths(get_option('datadir'), 'glib-2.0/schemas')
|
||||||
)
|
)
|
||||||
|
|
||||||
|
subdir('icons')
|
||||||
|
|
||||||
compile_schemas = find_program('glib-compile-schemas', required: false)
|
compile_schemas = find_program('glib-compile-schemas', required: false)
|
||||||
if compile_schemas.found()
|
if compile_schemas.found()
|
||||||
test('Validate schema file', compile_schemas,
|
test('Validate schema file', compile_schemas,
|
||||||
args: ['--strict', '--dry-run', meson.current_source_dir()]
|
args: ['--strict', '--dry-run', meson.current_source_dir()]
|
||||||
)
|
)
|
||||||
endif
|
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
|
*Left click button / actions*: Activate notification action
|
||||||
|
|
||||||
*Right click notification*: Close notification
|
*Middle/Right click notification*: Close notification
|
||||||
|
|
||||||
# CONFIGURATION
|
# CONFIGURATION
|
||||||
|
|
||||||
|
@@ -68,6 +68,13 @@ config file to be able to detect config errors
|
|||||||
description: Layer of control center window relative to normal windows. ++
|
description: Layer of control center window relative to normal windows. ++
|
||||||
background is below all windows, overlay is above all 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* ++
|
*notification-2fa-action* ++
|
||||||
type: bool ++
|
type: bool ++
|
||||||
default: true ++
|
default: true ++
|
||||||
@@ -370,6 +377,22 @@ config file to be able to detect config errors
|
|||||||
type: string ++
|
type: string ++
|
||||||
default: "" ++
|
default: "" ++
|
||||||
description: "Command to be executed on click" ++
|
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 list of actions containing a label and a command ++
|
||||||
description: A button to reveal a dropdown with action-buttons ++
|
description: A button to reveal a dropdown with action-buttons ++
|
||||||
buttons#<name>: ++
|
buttons#<name>: ++
|
||||||
@@ -395,6 +418,24 @@ config file to be able to detect config errors
|
|||||||
type: string ++
|
type: string ++
|
||||||
default: "" ++
|
default: "" ++
|
||||||
description: "Command to be executed on click" ++
|
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 actions containing a label and a command ++
|
||||||
description: A list of buttons to be displayed in the menu-button-bar ++
|
description: A list of buttons to be displayed in the menu-button-bar ++
|
||||||
*buttons-grid*++
|
*buttons-grid*++
|
||||||
@@ -415,6 +456,18 @@ config file to be able to detect config errors
|
|||||||
type: string ++
|
type: string ++
|
||||||
default: "" ++
|
default: "" ++
|
||||||
description: "Command to be executed on click" ++
|
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 list of actions containing a label and a command ++
|
||||||
description: A grid of buttons that execute shell commands ++
|
description: A grid of buttons that execute shell commands ++
|
||||||
#START pulse-audio
|
#START pulse-audio
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
project('sway-notificaton-center', ['c', 'vala'],
|
project('sway-notificaton-center', ['c', 'vala'],
|
||||||
version: '0.9.0',
|
version: '0.10.1',
|
||||||
meson_version: '>= 0.59.0',
|
meson_version: '>= 0.59.0',
|
||||||
default_options: [ 'warning_level=2' ],
|
default_options: [ 'warning_level=2' ],
|
||||||
)
|
)
|
||||||
@@ -9,6 +9,11 @@ add_project_arguments(['--enable-gobject-tracing'], language: 'vala')
|
|||||||
add_project_arguments(['--enable-checking'], language: 'vala')
|
add_project_arguments(['--enable-checking'], language: 'vala')
|
||||||
|
|
||||||
i18n = import('i18n')
|
i18n = import('i18n')
|
||||||
|
gnome = import('gnome')
|
||||||
|
|
||||||
|
app_resources = []
|
||||||
|
|
||||||
|
config_path = join_paths(get_option('sysconfdir'), 'xdg', 'swaync')
|
||||||
|
|
||||||
subdir('data')
|
subdir('data')
|
||||||
subdir('src')
|
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) {
|
private void on_subscribe (uint count, bool dnd, bool cc_open, bool inhibited) {
|
||||||
stdout.printf (
|
stdout.printf (
|
||||||
"{ \"count\": %u, \"dnd\": %s, \"visible\": %s, \"inhibited\": %s }\n"
|
"{ \"count\": %u, \"dnd\": %s, \"visible\": %s, \"inhibited\": %s }\n",
|
||||||
.printf (count, dnd.to_string (), cc_open.to_string (), inhibited.to_string ()));
|
count, dnd.to_string (), cc_open.to_string (), inhibited.to_string ());
|
||||||
|
stdout.flush ();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void print_subscribe () {
|
private void print_subscribe () {
|
||||||
|
@@ -74,6 +74,17 @@
|
|||||||
"mpris": {
|
"mpris": {
|
||||||
"image-size": 96,
|
"image-size": 96,
|
||||||
"image-radius": 12
|
"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 ScriptRunOnType run_on { get; set; default = ScriptRunOnType.RECEIVE; }
|
||||||
|
|
||||||
public async bool run_script (NotifyParams param, out string msg) {
|
public async bool run_script (NotifyParams param, out string msg) {
|
||||||
msg = "";
|
string[] spawn_env = {};
|
||||||
try {
|
spawn_env += "SWAYNC_APP_NAME=%s".printf (param.app_name);
|
||||||
string[] spawn_env = Environ.get ();
|
spawn_env += "SWAYNC_SUMMARY=%s".printf (param.summary);
|
||||||
// Export env variables
|
spawn_env += "SWAYNC_BODY=%s".printf (param.body);
|
||||||
spawn_env += "SWAYNC_APP_NAME=%s".printf (param.app_name);
|
spawn_env += "SWAYNC_URGENCY=%s".printf (param.urgency.to_string ());
|
||||||
spawn_env += "SWAYNC_SUMMARY=%s".printf (param.summary);
|
spawn_env += "SWAYNC_CATEGORY=%s".printf (param.category);
|
||||||
spawn_env += "SWAYNC_BODY=%s".printf (param.body);
|
spawn_env += "SWAYNC_ID=%s".printf (param.applied_id.to_string ());
|
||||||
spawn_env += "SWAYNC_URGENCY=%s".printf (param.urgency.to_string ());
|
spawn_env += "SWAYNC_REPLACES_ID=%s".printf (param.replaces_id.to_string ());
|
||||||
spawn_env += "SWAYNC_CATEGORY=%s".printf (param.category);
|
spawn_env += "SWAYNC_TIME=%s".printf (param.time.to_string ());
|
||||||
spawn_env += "SWAYNC_ID=%s".printf (param.applied_id.to_string ());
|
spawn_env += "SWAYNC_DESKTOP_ENTRY=%s".printf (param.desktop_entry ?? "");
|
||||||
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;
|
return yield Functions.execute_command (exec, spawn_env, out msg);
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override bool matches_notification (NotifyParams param) {
|
public override bool matches_notification (NotifyParams param) {
|
||||||
@@ -493,6 +467,10 @@ namespace SwayNotificationCenter {
|
|||||||
get; set; default = Layer.TOP;
|
get; set; default = Layer.TOP;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool control_center_exclusive_zone {
|
||||||
|
get; set; default = true;
|
||||||
|
}
|
||||||
|
|
||||||
/** Categories settings */
|
/** Categories settings */
|
||||||
public OrderedHashTable<Category> categories_settings {
|
public OrderedHashTable<Category> categories_settings {
|
||||||
get;
|
get;
|
||||||
|
@@ -75,6 +75,11 @@
|
|||||||
"default": "none",
|
"default": "none",
|
||||||
"enum": ["background", "bottom", "top", "overlay", "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": {
|
"notification-2fa-action": {
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"description": "If each notification should display a 'COPY \"1234\"' action",
|
"description": "If each notification should display a 'COPY \"1234\"' action",
|
||||||
@@ -87,7 +92,7 @@
|
|||||||
},
|
},
|
||||||
"notification-icon-size": {
|
"notification-icon-size": {
|
||||||
"type": "integer",
|
"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,
|
"default": 64,
|
||||||
"minimum": 16
|
"minimum": 16
|
||||||
},
|
},
|
||||||
@@ -152,7 +157,7 @@
|
|||||||
},
|
},
|
||||||
"image-visibility": {
|
"image-visibility": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "An explanation about the purpose of this instance.",
|
"description": "The notification image visibility when no icon is available.",
|
||||||
"default": "when-available",
|
"default": "when-available",
|
||||||
"enum": ["always", "when-available", "never"]
|
"enum": ["always", "when-available", "never"]
|
||||||
},
|
},
|
||||||
@@ -397,8 +402,14 @@
|
|||||||
},
|
},
|
||||||
"image-radius": {
|
"image-radius": {
|
||||||
"type": "integer",
|
"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
|
"default": 12
|
||||||
|
},
|
||||||
|
"blur": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Appy the artwork as the MPRIS background and blur it",
|
||||||
|
"default": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -422,6 +433,22 @@
|
|||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "Command to be executed on click",
|
"description": "Command to be executed on click",
|
||||||
"default": ""
|
"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 class Constants {
|
||||||
public const string VERSION = @VERSION@;
|
public const string VERSION = @VERSION@;
|
||||||
public const string VERSIONNUM = @VERSION_NUM@;
|
public const string VERSIONNUM = @VERSION_NUM@;
|
||||||
|
public const uint ANIMATION_DURATION = 400;
|
||||||
}
|
}
|
||||||
|
@@ -20,89 +20,84 @@
|
|||||||
<property name="vexpand">True</property>
|
<property name="vexpand">True</property>
|
||||||
<property name="orientation">vertical</property>
|
<property name="orientation">vertical</property>
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkScrolledWindow" id="scrolled_window">
|
<object class="GtkBox" id="notifications_box">
|
||||||
<property name="visible">True</property>
|
<property name="visible">True</property>
|
||||||
<property name="can-focus">True</property>
|
<property name="can-focus">False</property>
|
||||||
<property name="hscrollbar-policy">never</property>
|
<property name="orientation">vertical</property>
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkViewport" id="viewport">
|
<object class="GtkStack" id="stack">
|
||||||
<property name="visible">True</property>
|
<property name="visible">True</property>
|
||||||
<property name="can-focus">False</property>
|
<property name="can-focus">False</property>
|
||||||
<property name="vexpand">True</property>
|
<property name="transition-type">crossfade</property>
|
||||||
<property name="shadow-type">none</property>
|
|
||||||
<child>
|
<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="visible">True</property>
|
||||||
<property name="can-focus">False</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>
|
<child>
|
||||||
<object class="GtkBox">
|
<object class="GtkImage">
|
||||||
<property name="visible">True</property>
|
<property name="visible">True</property>
|
||||||
<property name="can-focus">False</property>
|
<property name="can-focus">False</property>
|
||||||
<property name="halign">center</property>
|
<property name="pixel-size">96</property>
|
||||||
<property name="valign">center</property>
|
<property name="icon-name">preferences-system-notifications-symbolic</property>
|
||||||
<property name="hexpand">True</property>
|
<property name="use-fallback">True</property>
|
||||||
<property name="vexpand">True</property>
|
<property name="icon_size">0</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>
|
|
||||||
</object>
|
</object>
|
||||||
<packing>
|
<packing>
|
||||||
<property name="name">notifications-placeholder</property>
|
<property name="expand">False</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="position">0</property>
|
||||||
</packing>
|
</packing>
|
||||||
</child>
|
</child>
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkListBox" id="list_box">
|
<object class="GtkLabel">
|
||||||
<property name="visible">True</property>
|
<property name="visible">True</property>
|
||||||
<property name="can-focus">False</property>
|
<property name="can-focus">False</property>
|
||||||
<property name="valign">end</property>
|
<property name="label" translatable="yes">No Notifications</property>
|
||||||
<property name="selection-mode">none</property>
|
|
||||||
<property name="activate-on-single-click">False</property>
|
|
||||||
<style>
|
|
||||||
<class name="control-center-list"/>
|
|
||||||
</style>
|
|
||||||
</object>
|
</object>
|
||||||
<packing>
|
<packing>
|
||||||
<property name="name">notifications-list</property>
|
<property name="expand">False</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
<property name="position">1</property>
|
<property name="position">1</property>
|
||||||
</packing>
|
</packing>
|
||||||
</child>
|
</child>
|
||||||
|
<style>
|
||||||
|
<class name="control-center-list-placeholder"/>
|
||||||
|
</style>
|
||||||
</object>
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="name">notifications-placeholder</property>
|
||||||
|
<property name="position">1</property>
|
||||||
|
</packing>
|
||||||
</child>
|
</child>
|
||||||
</object>
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="position">0</property>
|
||||||
|
</packing>
|
||||||
</child>
|
</child>
|
||||||
</object>
|
</object>
|
||||||
<packing>
|
<packing>
|
||||||
<property name="expand">True</property>
|
<property name="expand">False</property>
|
||||||
<property name="fill">True</property>
|
<property name="fill">True</property>
|
||||||
<property name="position">0</property>
|
<property name="position">0</property>
|
||||||
</packing>
|
</packing>
|
||||||
|
@@ -1,18 +1,30 @@
|
|||||||
namespace SwayNotificationCenter {
|
namespace SwayNotificationCenter {
|
||||||
[GtkTemplate (ui = "/org/erikreider/sway-notification-center/controlCenter/controlCenter.ui")]
|
[GtkTemplate (ui = "/org/erikreider/sway-notification-center/controlCenter/controlCenter.ui")]
|
||||||
public class ControlCenter : Gtk.ApplicationWindow {
|
public class ControlCenter : Gtk.ApplicationWindow {
|
||||||
|
|
||||||
[GtkChild]
|
[GtkChild]
|
||||||
unowned Gtk.ScrolledWindow scrolled_window;
|
unowned Gtk.Box notifications_box;
|
||||||
[GtkChild]
|
|
||||||
unowned Gtk.Viewport viewport;
|
|
||||||
[GtkChild]
|
[GtkChild]
|
||||||
unowned Gtk.Stack stack;
|
unowned Gtk.Stack stack;
|
||||||
[GtkChild]
|
[GtkChild]
|
||||||
unowned Gtk.ListBox list_box;
|
unowned Gtk.ScrolledWindow scrolled_window;
|
||||||
|
FadedViewport viewport = new FadedViewport (20);
|
||||||
|
Gtk.ListBox list_box = new Gtk.ListBox ();
|
||||||
|
|
||||||
[GtkChild]
|
[GtkChild]
|
||||||
unowned Gtk.Box box;
|
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_NOTIFICATIONS_PAGE = "notifications-list";
|
||||||
const string STACK_PLACEHOLDER_PAGE = "notifications-placeholder";
|
const string STACK_PLACEHOLDER_PAGE = "notifications-placeholder";
|
||||||
|
|
||||||
@@ -23,9 +35,8 @@ namespace SwayNotificationCenter {
|
|||||||
private SwayncDaemon swaync_daemon;
|
private SwayncDaemon swaync_daemon;
|
||||||
private NotiDaemon noti_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 bool list_reverse = false;
|
||||||
private Gtk.Align list_align = Gtk.Align.START;
|
private Gtk.Align list_align = Gtk.Align.START;
|
||||||
|
|
||||||
@@ -38,6 +49,18 @@ namespace SwayNotificationCenter {
|
|||||||
|
|
||||||
this.swaync_daemon.reloading_css.connect (reload_notifications_style);
|
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 (swaync_daemon.use_layer_shell) {
|
||||||
if (!GtkLayerShell.is_supported ()) {
|
if (!GtkLayerShell.is_supported ()) {
|
||||||
stderr.printf ("GTKLAYERSHELL IS NOT SUPPORTED!\n");
|
stderr.printf ("GTKLAYERSHELL IS NOT SUPPORTED!\n");
|
||||||
@@ -54,8 +77,6 @@ namespace SwayNotificationCenter {
|
|||||||
GtkLayerShell.set_anchor (this, GtkLayerShell.Edge.BOTTOM, true);
|
GtkLayerShell.set_anchor (this, GtkLayerShell.Edge.BOTTOM, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
viewport.size_allocate.connect (size_alloc);
|
|
||||||
|
|
||||||
this.map.connect (() => {
|
this.map.connect (() => {
|
||||||
set_anchor ();
|
set_anchor ();
|
||||||
// Wait until the layer has attached
|
// Wait until the layer has attached
|
||||||
@@ -152,72 +173,10 @@ namespace SwayNotificationCenter {
|
|||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
|
||||||
this.key_press_event.connect ((w, event_key) => {
|
key_press_event.connect (key_press_event_cb);
|
||||||
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;
|
|
||||||
});
|
|
||||||
|
|
||||||
// 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 (() => {
|
list_box.add.connect (() => {
|
||||||
stack.set_visible_child_name (STACK_NOTIFICATIONS_PAGE);
|
stack.set_visible_child_name (STACK_NOTIFICATIONS_PAGE);
|
||||||
});
|
});
|
||||||
@@ -228,6 +187,171 @@ namespace SwayNotificationCenter {
|
|||||||
});
|
});
|
||||||
|
|
||||||
add_widgets ();
|
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 */
|
/** Adds all custom widgets. Removes previous widgets */
|
||||||
@@ -242,11 +366,11 @@ namespace SwayNotificationCenter {
|
|||||||
if (w.length == 0) w = DEFAULT_WIDGETS;
|
if (w.length == 0) w = DEFAULT_WIDGETS;
|
||||||
bool has_notification = false;
|
bool has_notification = false;
|
||||||
foreach (string key in w) {
|
foreach (string key in w) {
|
||||||
// Reposition the scrolled_window
|
// Reposition the notifications_box
|
||||||
if (key == "notifications") {
|
if (key == "notifications") {
|
||||||
has_notification = true;
|
has_notification = true;
|
||||||
uint pos = box.get_children ().length ();
|
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;
|
continue;
|
||||||
}
|
}
|
||||||
// Add the widget if it is valid
|
// Add the widget if it is valid
|
||||||
@@ -260,23 +384,22 @@ namespace SwayNotificationCenter {
|
|||||||
if (!has_notification) {
|
if (!has_notification) {
|
||||||
warning ("Notification widget not included in \"widgets\" config. Using default bottom position");
|
warning ("Notification widget not included in \"widgets\" config. Using default bottom position");
|
||||||
uint pos = box.get_children ().length ();
|
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 */
|
/** Resets the UI positions */
|
||||||
private void set_anchor () {
|
private void set_anchor () {
|
||||||
if (swaync_daemon.use_layer_shell) {
|
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
|
// Grabs the keyboard input until closed
|
||||||
bool keyboard_shortcuts = ConfigModel.instance.keyboard_shortcuts;
|
bool keyboard_shortcuts = ConfigModel.instance.keyboard_shortcuts;
|
||||||
#if HAVE_LATEST_GTK_LAYER_SHELL
|
|
||||||
var mode = keyboard_shortcuts ?
|
var mode = keyboard_shortcuts ?
|
||||||
GtkLayerShell.KeyboardMode.EXCLUSIVE :
|
GtkLayerShell.KeyboardMode.EXCLUSIVE :
|
||||||
GtkLayerShell.KeyboardMode.NONE;
|
GtkLayerShell.KeyboardMode.NONE;
|
||||||
GtkLayerShell.set_keyboard_mode (this, mode);
|
GtkLayerShell.set_keyboard_mode (this, mode);
|
||||||
#else
|
|
||||||
GtkLayerShell.set_keyboard_interactivity (this, keyboard_shortcuts);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// Set layer
|
// Set layer
|
||||||
GtkLayerShell.Layer layer;
|
GtkLayerShell.Layer layer;
|
||||||
@@ -351,28 +474,37 @@ namespace SwayNotificationCenter {
|
|||||||
box.set_valign (align_y);
|
box.set_valign (align_y);
|
||||||
|
|
||||||
list_box.set_valign (list_align);
|
list_box.set_valign (list_align);
|
||||||
list_box.set_sort_func ((w1, w2) => {
|
list_box.set_sort_func (list_box_sort_func);
|
||||||
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;
|
|
||||||
});
|
|
||||||
|
|
||||||
// Always set the size request in all events.
|
// Always set the size request in all events.
|
||||||
box.set_size_request (ConfigModel.instance.control_center_width,
|
box.set_size_request (ConfigModel.instance.control_center_width,
|
||||||
ConfigModel.instance.control_center_height);
|
ConfigModel.instance.control_center_height);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void size_alloc () {
|
/**
|
||||||
var adj = viewport.vadjustment;
|
* Returns < 0 if row1 should be before row2, 0 if they are equal
|
||||||
double upper = adj.get_upper ();
|
* and > 0 otherwise
|
||||||
if (last_upper < upper) {
|
*/
|
||||||
scroll_to_start (list_reverse);
|
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) {
|
private void scroll_to_start (bool reverse) {
|
||||||
@@ -384,20 +516,26 @@ namespace SwayNotificationCenter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public uint notification_count () {
|
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 () {
|
public void close_all_notifications () {
|
||||||
foreach (var w in list_box.get_children ()) {
|
foreach (var w in list_box.get_children ()) {
|
||||||
Notification noti = (Notification) w;
|
NotificationGroup group = (NotificationGroup) w;
|
||||||
if (noti != null) noti.close_notification (false);
|
if (group != null) group.close_all_notifications ();
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
swaync_daemon.subscribe_v2 (notification_count (),
|
swaync_daemon.subscribe_v2 (notification_count (),
|
||||||
swaync_daemon.get_dnd (),
|
swaync_daemon.get_dnd (),
|
||||||
get_visibility (),
|
get_visibility (),
|
||||||
swaync_daemon.inhibited);
|
swaync_daemon.inhibited);
|
||||||
} catch (Error e) {
|
} catch (Error e) {
|
||||||
stderr.printf (e.message + "\n");
|
stderr.printf (e.message + "\n");
|
||||||
}
|
}
|
||||||
@@ -407,11 +545,20 @@ namespace SwayNotificationCenter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void navigate_list (uint i) {
|
private void navigate_list (int i) {
|
||||||
var widget = list_box.get_children ().nth_data (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) {
|
if (widget != null) {
|
||||||
list_box.set_focus_child (widget);
|
|
||||||
widget.grab_focus ();
|
widget.grab_focus ();
|
||||||
|
list_box.set_focus_child (widget);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -424,20 +571,24 @@ namespace SwayNotificationCenter {
|
|||||||
if (this.visible) {
|
if (this.visible) {
|
||||||
// Focus the first notification
|
// Focus the first notification
|
||||||
list_position = list_reverse ?
|
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;
|
if (list_position == uint.MAX) list_position = 0;
|
||||||
|
|
||||||
list_box.grab_focus ();
|
list_box.grab_focus ();
|
||||||
navigate_list (list_position);
|
navigate_list (list_position);
|
||||||
foreach (var w in list_box.get_children ()) {
|
foreach (var w in list_box.get_children ()) {
|
||||||
var noti = (Notification) w;
|
var group = (NotificationGroup) w;
|
||||||
if (noti != null) noti.set_time ();
|
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 (),
|
swaync_daemon.subscribe_v2 (notification_count (),
|
||||||
noti_daemon.dnd,
|
noti_daemon.dnd,
|
||||||
this.visible,
|
this.visible,
|
||||||
swaync_daemon.inhibited);
|
swaync_daemon.inhibited);
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool toggle_visibility () {
|
public bool toggle_visibility () {
|
||||||
@@ -455,41 +606,114 @@ namespace SwayNotificationCenter {
|
|||||||
on_visibility_change ();
|
on_visibility_change ();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void close_notification (uint32 id, bool replaces = false) {
|
public void close_notification (uint32 id, bool dismiss) {
|
||||||
foreach (var w in list_box.get_children ()) {
|
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;
|
var noti = (Notification) w;
|
||||||
if (noti != null && noti.param.applied_id == id) {
|
if (noti != null && noti.param.applied_id == id) {
|
||||||
if (replaces) {
|
if (!dismiss) {
|
||||||
noti.remove_noti_timeout ();
|
noti.remove_noti_timeout ();
|
||||||
noti.destroy ();
|
noti.destroy ();
|
||||||
} else {
|
} else {
|
||||||
noti.close_notification (false);
|
noti.close_notification (false);
|
||||||
list_box.remove (w);
|
group.remove_notification (noti);
|
||||||
}
|
}
|
||||||
|
noti_groups_id.remove (id);
|
||||||
break;
|
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,
|
public void replace_notification (uint32 id, NotifyParams new_params) {
|
||||||
NotiDaemon noti_daemon) {
|
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,
|
var noti = new Notification.regular (param,
|
||||||
noti_daemon,
|
noti_daemon,
|
||||||
NotificationType.CONTROL_CENTER);
|
NotificationType.CONTROL_CENTER);
|
||||||
noti.grab_focus.connect ((w) => {
|
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) {
|
if (list_position != uint.MAX && list_position != i) {
|
||||||
list_position = i;
|
list_position = i;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
noti.set_time ();
|
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);
|
scroll_to_start (list_reverse);
|
||||||
try {
|
try {
|
||||||
swaync_daemon.subscribe_v2 (notification_count (),
|
swaync_daemon.subscribe_v2 (notification_count (),
|
||||||
swaync_daemon.get_dnd (),
|
swaync_daemon.get_dnd (),
|
||||||
get_visibility (),
|
get_visibility (),
|
||||||
swaync_daemon.inhibited);
|
swaync_daemon.inhibited);
|
||||||
} catch (Error e) {
|
} catch (Error e) {
|
||||||
stderr.printf (e.message + "\n");
|
stderr.printf (e.message + "\n");
|
||||||
}
|
}
|
||||||
@@ -506,8 +730,13 @@ namespace SwayNotificationCenter {
|
|||||||
/** Forces each notification EventBox to reload its style_context #27 */
|
/** Forces each notification EventBox to reload its style_context #27 */
|
||||||
private void reload_notifications_style () {
|
private void reload_notifications_style () {
|
||||||
foreach (var c in list_box.get_children ()) {
|
foreach (var c in list_box.get_children ()) {
|
||||||
Notification noti = (Notification) c;
|
NotificationGroup group = (NotificationGroup) c;
|
||||||
if (noti != null) noti.reload_style_context ();
|
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_draw_value (false);
|
||||||
slider.set_round_digits (0);
|
slider.set_round_digits (0);
|
||||||
slider.value_changed.connect (() => {
|
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 ();
|
slider.tooltip_text = ((int) slider.get_value ()).to_string ();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@@ -3,7 +3,7 @@ namespace SwayNotificationCenter.Widgets {
|
|||||||
|
|
||||||
[DBus (name = "org.freedesktop.login1.Session")]
|
[DBus (name = "org.freedesktop.login1.Session")]
|
||||||
interface Login1 : Object {
|
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;
|
string name, uint32 brightness) throws GLib.Error;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -77,14 +77,14 @@ namespace SwayNotificationCenter.Widgets {
|
|||||||
if (monitor != null) monitor.cancel ();
|
if (monitor != null) monitor.cancel ();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void set_brightness (float percent) {
|
public async void set_brightness (float percent) {
|
||||||
this.close ();
|
this.close ();
|
||||||
try {
|
try {
|
||||||
if (subsystem == "backlight") {
|
if (subsystem == "backlight") {
|
||||||
int actual = calc_actual (percent);
|
int actual = calc_actual (percent);
|
||||||
login1.set_brightness (subsystem, device, actual);
|
login1.set_brightness.begin (subsystem, device, actual);
|
||||||
} else {
|
} else {
|
||||||
login1.set_brightness (subsystem, device, (uint32) percent);
|
login1.set_brightness.begin (subsystem, device, (uint32) percent);
|
||||||
}
|
}
|
||||||
} catch (Error e) {
|
} catch (Error e) {
|
||||||
error ("Error %s\n", e.message);
|
error ("Error %s\n", e.message);
|
||||||
|
@@ -1,5 +1,3 @@
|
|||||||
using Posix;
|
|
||||||
|
|
||||||
namespace SwayNotificationCenter.Widgets {
|
namespace SwayNotificationCenter.Widgets {
|
||||||
public abstract class BaseWidget : Gtk.Box {
|
public abstract class BaseWidget : Gtk.Box {
|
||||||
public abstract string widget_name { get; }
|
public abstract string widget_name { get; }
|
||||||
@@ -16,6 +14,20 @@ namespace SwayNotificationCenter.Widgets {
|
|||||||
public unowned SwayncDaemon swaync_daemon;
|
public unowned SwayncDaemon swaync_daemon;
|
||||||
public unowned NotiDaemon noti_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) {
|
protected BaseWidget (string suffix, SwayncDaemon swaync_daemon, NotiDaemon noti_daemon) {
|
||||||
this.suffix = suffix;
|
this.suffix = suffix;
|
||||||
this.key = widget_name + (suffix.length > 0 ? "#%s".printf (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++) {
|
for (int i = 0; i < actions.get_length (); i++) {
|
||||||
string label = actions.get_object_element (i).get_string_member_with_default ("label", "label");
|
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 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 () {
|
res[i] = Action () {
|
||||||
label = label,
|
label = label,
|
||||||
command = command
|
command = command,
|
||||||
|
type = type,
|
||||||
|
update_command = update_command,
|
||||||
|
active = active
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void execute_command (string cmd) {
|
|
||||||
pid_t pid;
|
protected async void execute_command (string cmd, string[] env_additions = {}) {
|
||||||
int status;
|
string msg = "";
|
||||||
if ((pid = fork ()) < 0) {
|
yield Functions.execute_command (cmd, env_additions, out msg);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -10,6 +10,7 @@ namespace SwayNotificationCenter.Widgets {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Action[] actions;
|
Action[] actions;
|
||||||
|
List<ToggleButton> toggle_buttons;
|
||||||
|
|
||||||
public ButtonsGrid (string suffix, SwayncDaemon swaync_daemon, NotiDaemon noti_daemon) {
|
public ButtonsGrid (string suffix, SwayncDaemon swaync_daemon, NotiDaemon noti_daemon) {
|
||||||
base (suffix, swaync_daemon, noti_daemon);
|
base (suffix, swaync_daemon, noti_daemon);
|
||||||
@@ -26,14 +27,29 @@ namespace SwayNotificationCenter.Widgets {
|
|||||||
|
|
||||||
// add action to container
|
// add action to container
|
||||||
foreach (var act in actions) {
|
foreach (var act in actions) {
|
||||||
Gtk.Button b = new Gtk.Button.with_label (act.label);
|
switch (act.type) {
|
||||||
|
case ButtonType.TOGGLE:
|
||||||
b.clicked.connect (() => execute_command (act.command));
|
ToggleButton tb = new ToggleButton (act.label, act.command, act.update_command, act.active);
|
||||||
|
container.insert (tb, -1);
|
||||||
container.insert (b, -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 ();
|
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 {
|
public struct Action {
|
||||||
string ? label;
|
string ? label;
|
||||||
string ? command;
|
string ? command;
|
||||||
|
BaseWidget.ButtonType ? type;
|
||||||
|
string ? update_command;
|
||||||
|
bool ? active;
|
||||||
}
|
}
|
||||||
|
|
||||||
public class Menubar : BaseWidget {
|
public class Menubar : BaseWidget {
|
||||||
@@ -38,6 +41,7 @@ namespace SwayNotificationCenter.Widgets {
|
|||||||
Gtk.Box topbar_container;
|
Gtk.Box topbar_container;
|
||||||
|
|
||||||
List<ConfigObject ?> menu_objects;
|
List<ConfigObject ?> menu_objects;
|
||||||
|
List<ToggleButton> toggle_buttons;
|
||||||
|
|
||||||
public Menubar (string suffix, SwayncDaemon swaync_daemon, NotiDaemon noti_daemon) {
|
public Menubar (string suffix, SwayncDaemon swaync_daemon, NotiDaemon noti_daemon) {
|
||||||
base (suffix, swaync_daemon, 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);
|
if (obj.name != null) container.get_style_context ().add_class (obj.name);
|
||||||
|
|
||||||
foreach (Action a in obj.actions) {
|
foreach (Action a in obj.actions) {
|
||||||
Gtk.Button b = new Gtk.Button.with_label (a.label);
|
switch (a.type) {
|
||||||
|
case ButtonType.TOGGLE:
|
||||||
b.clicked.connect (() => execute_command (a.command));
|
ToggleButton tb = new ToggleButton (a.label, a.command, a.update_command, a.active);
|
||||||
|
container.add (tb);
|
||||||
container.add (b);
|
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) {
|
switch (obj.position) {
|
||||||
case Position.LEFT:
|
case Position.LEFT:
|
||||||
@@ -110,9 +121,18 @@ namespace SwayNotificationCenter.Widgets {
|
|||||||
});
|
});
|
||||||
|
|
||||||
foreach (var a in obj.actions) {
|
foreach (var a in obj.actions) {
|
||||||
Gtk.Button b = new Gtk.Button.with_label (a.label);
|
switch (a.type) {
|
||||||
b.clicked.connect (() => execute_command (a.command));
|
case ButtonType.TOGGLE:
|
||||||
menu.pack_start (b, true, true, 0);
|
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) {
|
switch (obj.position) {
|
||||||
@@ -210,6 +230,10 @@ namespace SwayNotificationCenter.Widgets {
|
|||||||
foreach (var obj in menu_objects) {
|
foreach (var obj in menu_objects) {
|
||||||
obj.revealer ?.set_reveal_child (false);
|
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 {
|
public struct Config {
|
||||||
int image_size;
|
int image_size;
|
||||||
int image_radius;
|
int image_radius;
|
||||||
|
bool blur;
|
||||||
}
|
}
|
||||||
|
|
||||||
public class Mpris : BaseWidget {
|
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.";
|
const string MPRIS_PREFIX = "org.mpris.MediaPlayer2.";
|
||||||
HashTable<string, MprisPlayer> players = new HashTable<string, MprisPlayer> (str_hash, str_equal);
|
HashTable<string, MprisPlayer> players = new HashTable<string, MprisPlayer> (str_hash, str_equal);
|
||||||
|
|
||||||
@@ -26,6 +29,7 @@ namespace SwayNotificationCenter.Widgets.Mpris {
|
|||||||
Config mpris_config = Config () {
|
Config mpris_config = Config () {
|
||||||
image_size = 96,
|
image_size = 96,
|
||||||
image_radius = 12,
|
image_radius = 12,
|
||||||
|
blur = true,
|
||||||
};
|
};
|
||||||
|
|
||||||
public Mpris (string suffix, SwayncDaemon swaync_daemon, NotiDaemon noti_daemon) {
|
public Mpris (string suffix, SwayncDaemon swaync_daemon, NotiDaemon noti_daemon) {
|
||||||
@@ -53,9 +57,8 @@ namespace SwayNotificationCenter.Widgets.Mpris {
|
|||||||
carousel = new Hdy.Carousel () {
|
carousel = new Hdy.Carousel () {
|
||||||
visible = true,
|
visible = true,
|
||||||
};
|
};
|
||||||
#if HAVE_LATEST_LIBHANDY
|
|
||||||
carousel.allow_scroll_wheel = true;
|
carousel.allow_scroll_wheel = true;
|
||||||
#endif
|
carousel.draw.connect (carousel_draw_cb);
|
||||||
carousel.page_changed.connect ((index) => {
|
carousel.page_changed.connect ((index) => {
|
||||||
GLib.List<weak Gtk.Widget> children = carousel.get_children ();
|
GLib.List<weak Gtk.Widget> children = carousel.get_children ();
|
||||||
int children_length = (int) children.length ();
|
int children_length = (int) children.length ();
|
||||||
@@ -91,6 +94,11 @@ namespace SwayNotificationCenter.Widgets.Mpris {
|
|||||||
// Clamp the radius
|
// Clamp the radius
|
||||||
mpris_config.image_radius = mpris_config.image_radius.clamp (
|
mpris_config.image_radius = mpris_config.image_radius.clamp (
|
||||||
0, (int) (mpris_config.image_size * 0.5));
|
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 ();
|
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.
|
* Forces the carousel to reload its style_context.
|
||||||
* Fixes carousel items not redrawing when window isn't visible.
|
* 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="icon-name">audio-x-generic-symbolic</property>
|
||||||
<property name="use-fallback">True</property>
|
<property name="use-fallback">True</property>
|
||||||
<property name="icon_size">0</property>
|
<property name="icon_size">0</property>
|
||||||
|
<style>
|
||||||
|
<class name="widget-mpris-album-art"/>
|
||||||
|
</style>
|
||||||
</object>
|
</object>
|
||||||
<packing>
|
<packing>
|
||||||
<property name="expand">False</property>
|
<property name="expand">False</property>
|
||||||
|
@@ -38,6 +38,9 @@ namespace SwayNotificationCenter.Widgets.Mpris {
|
|||||||
private string prev_art_url;
|
private string prev_art_url;
|
||||||
private DesktopAppInfo ? desktop_entry = null;
|
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;
|
private unowned Config mpris_config;
|
||||||
|
|
||||||
public MprisPlayer (MprisSource source, Config mpris_config) {
|
public MprisPlayer (MprisSource source, Config mpris_config) {
|
||||||
@@ -93,12 +96,84 @@ namespace SwayNotificationCenter.Widgets.Mpris {
|
|||||||
update_buttons (source.media_player.metadata);
|
update_buttons (source.media_player.metadata);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
album_art.set_pixel_size (mpris_config.image_size);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void before_destroy () {
|
public void before_destroy () {
|
||||||
source.properties_changed.disconnect (properties_changed);
|
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,
|
private void properties_changed (string iface,
|
||||||
HashTable<string, Variant> changed,
|
HashTable<string, Variant> changed,
|
||||||
string[] invalid) {
|
string[] invalid) {
|
||||||
@@ -246,7 +321,6 @@ namespace SwayNotificationCenter.Widgets.Mpris {
|
|||||||
|
|
||||||
int scale = get_style_context ().get_scale ();
|
int scale = get_style_context ().get_scale ();
|
||||||
|
|
||||||
Gdk.Pixbuf ? pixbuf = null;
|
|
||||||
// Cancel previous download, reset the state and download again
|
// Cancel previous download, reset the state and download again
|
||||||
album_art_cancellable.cancel ();
|
album_art_cancellable.cancel ();
|
||||||
album_art_cancellable.reset ();
|
album_art_cancellable.reset ();
|
||||||
@@ -255,34 +329,79 @@ namespace SwayNotificationCenter.Widgets.Mpris {
|
|||||||
InputStream stream = yield file.read_async (Priority.DEFAULT,
|
InputStream stream = yield file.read_async (Priority.DEFAULT,
|
||||||
album_art_cancellable);
|
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);
|
stream, album_art_cancellable);
|
||||||
} catch (Error e) {
|
} catch (Error e) {
|
||||||
debug ("Could not download album art for %s. Using fallback...",
|
debug ("Could not download album art for %s. Using fallback...",
|
||||||
source.media_player.identity);
|
source.media_player.identity);
|
||||||
|
this.album_art_pixbuf = null;
|
||||||
}
|
}
|
||||||
if (pixbuf != null) {
|
if (this.album_art_pixbuf != null) {
|
||||||
pixbuf = Functions.scale_round_pixbuf (pixbuf,
|
unowned Gtk.StyleContext style_ctx = album_art.get_style_context ();
|
||||||
mpris_config.image_size,
|
Value br_value =
|
||||||
mpris_config.image_size,
|
style_ctx.get_property (Gtk.STYLE_PROPERTY_BORDER_RADIUS,
|
||||||
scale,
|
style_ctx.get_state ());
|
||||||
mpris_config.image_radius);
|
int radius = 0;
|
||||||
album_art.set_from_pixbuf (pixbuf);
|
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);
|
album_art.get_style_context ().set_scale (1);
|
||||||
|
this.queue_draw ();
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.album_art_pixbuf = null;
|
||||||
|
this.blurred_cover_surface = null;
|
||||||
// Get the app icon
|
// Get the app icon
|
||||||
Icon ? icon = null;
|
Icon ? icon = null;
|
||||||
if (desktop_entry is DesktopAppInfo) {
|
if (desktop_entry is DesktopAppInfo) {
|
||||||
icon = desktop_entry.get_icon ();
|
icon = desktop_entry.get_icon ();
|
||||||
}
|
}
|
||||||
|
Gtk.IconInfo ? icon_info = null;
|
||||||
if (icon != 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 {
|
} else {
|
||||||
// Default icon
|
// Default icon
|
||||||
album_art.set_from_icon_name ("audio-x-generic-symbolic",
|
string icon_name = "audio-x-generic-symbolic";
|
||||||
mpris_config.image_size);
|
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 () {
|
public static void init () {
|
||||||
system_css_provider = new Gtk.CssProvider ();
|
system_css_provider = new Gtk.CssProvider ();
|
||||||
user_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,
|
public static void set_image_uri (owned string uri,
|
||||||
Gtk.Image img,
|
Gtk.Image img,
|
||||||
int icon_size,
|
int icon_size,
|
||||||
bool file_exists) {
|
int radius,
|
||||||
if ((path.length > 6 && path.slice (0, 7) == "file://") || file_exists) {
|
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 as a URI (file:// is the only URI schema supported right now)
|
||||||
try {
|
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 (
|
var pixbuf = new Gdk.Pixbuf.from_file_at_scale (
|
||||||
path,
|
uri,
|
||||||
icon_size * img.scale_factor,
|
icon_size * img.scale_factor,
|
||||||
icon_size * img.scale_factor,
|
icon_size * img.scale_factor,
|
||||||
true);
|
true);
|
||||||
var surface = Gdk.cairo_surface_create_from_pixbuf (
|
// Scale and round the image. Scales to fit the size
|
||||||
pixbuf,
|
var surface = scale_round_pixbuf (pixbuf,
|
||||||
img.scale_factor,
|
icon_size,
|
||||||
img.get_window ());
|
icon_size,
|
||||||
|
img.scale_factor,
|
||||||
|
radius);
|
||||||
img.set_from_surface (surface);
|
img.set_from_surface (surface);
|
||||||
return;
|
|
||||||
} catch (Error e) {
|
} catch (Error e) {
|
||||||
stderr.printf (e.message + "\n");
|
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);
|
// Try as icon name
|
||||||
} else {
|
if (img.storage_type == Gtk.ImageType.EMPTY) {
|
||||||
img.set_from_icon_name (
|
img.set_from_icon_name (uri, Gtk.IconSize.INVALID);
|
||||||
"image-missing",
|
|
||||||
Notification.icon_size);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
// Rebuild and scale the image
|
||||||
var pixbuf = new Gdk.Pixbuf.with_unowned_data (data.data,
|
var pixbuf = new Gdk.Pixbuf.with_unowned_data (data.data,
|
||||||
Gdk.Colorspace.RGB,
|
Gdk.Colorspace.RGB,
|
||||||
@@ -54,14 +65,11 @@ namespace SwayNotificationCenter {
|
|||||||
data.rowstride,
|
data.rowstride,
|
||||||
null);
|
null);
|
||||||
|
|
||||||
pixbuf = pixbuf.scale_simple (
|
var surface = scale_round_pixbuf (pixbuf,
|
||||||
icon_size * img.scale_factor,
|
icon_size,
|
||||||
icon_size * img.scale_factor,
|
icon_size,
|
||||||
Gdk.InterpType.BILINEAR);
|
img.scale_factor,
|
||||||
var surface = Gdk.cairo_surface_create_from_pixbuf (
|
radius);
|
||||||
pixbuf,
|
|
||||||
img.scale_factor,
|
|
||||||
img.get_window ());
|
|
||||||
img.set_from_surface (surface);
|
img.set_from_surface (surface);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -211,12 +219,15 @@ namespace SwayNotificationCenter {
|
|||||||
return type;
|
return type;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Scales the pixbuf to fit the given dimensions */
|
/** Roundes the Cairo Surface to the given radii */
|
||||||
public static Gdk.Pixbuf scale_round_pixbuf (Gdk.Pixbuf pixbuf,
|
public static Cairo.Surface round_surface (Cairo.Surface base_surf,
|
||||||
int buffer_width,
|
int buffer_width,
|
||||||
int buffer_height,
|
int buffer_height,
|
||||||
int img_scale,
|
int img_scale,
|
||||||
int radius) {
|
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,
|
Cairo.Surface surface = new Cairo.ImageSurface (Cairo.Format.ARGB32,
|
||||||
buffer_width,
|
buffer_width,
|
||||||
buffer_height);
|
buffer_height);
|
||||||
@@ -230,12 +241,31 @@ namespace SwayNotificationCenter {
|
|||||||
cr.arc (radius, buffer_height - radius, radius, 90 * DEGREES, 180 * DEGREES);
|
cr.arc (radius, buffer_height - radius, radius, 90 * DEGREES, 180 * DEGREES);
|
||||||
cr.arc (radius, radius, radius, 180 * DEGREES, 270 * DEGREES);
|
cr.arc (radius, radius, radius, 180 * DEGREES, 270 * DEGREES);
|
||||||
cr.close_path ();
|
cr.close_path ();
|
||||||
cr.set_source_rgb (0, 0, 0);
|
cr.set_source_rgba (0, 0, 0, 0);
|
||||||
cr.clip ();
|
cr.clip ();
|
||||||
cr.paint ();
|
cr.paint ();
|
||||||
|
|
||||||
cr.save ();
|
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,
|
img_scale,
|
||||||
null);
|
null);
|
||||||
int width = pixbuf.width / img_scale;
|
int width = pixbuf.width / img_scale;
|
||||||
@@ -245,23 +275,41 @@ namespace SwayNotificationCenter {
|
|||||||
if (window_ratio > bg_ratio) { // Taller wallpaper than monitor
|
if (window_ratio > bg_ratio) { // Taller wallpaper than monitor
|
||||||
double scale = (double) buffer_width / width;
|
double scale = (double) buffer_width / width;
|
||||||
if (scale * height < buffer_height) {
|
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 {
|
} 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
|
} else { // Wider wallpaper than monitor
|
||||||
double scale = (double) buffer_height / height;
|
double scale = (double) buffer_height / height;
|
||||||
if (scale * width < buffer_width) {
|
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 {
|
} 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.paint ();
|
||||||
cr.restore ();
|
cr.restore ();
|
||||||
|
|
||||||
scale_surf.finish ();
|
base_surf.finish ();
|
||||||
return Gdk.pixbuf_get_from_surface (surface, 0, 0, buffer_width, buffer_height);
|
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,
|
private static void draw_scale_tall (int buffer_width,
|
||||||
@@ -299,5 +347,68 @@ namespace SwayNotificationCenter {
|
|||||||
}
|
}
|
||||||
return result;
|
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
|
# Helpers
|
||||||
'controlCenter/widgets/baseWidget.vala',
|
'controlCenter/widgets/baseWidget.vala',
|
||||||
'controlCenter/widgets/factory.vala',
|
'controlCenter/widgets/factory.vala',
|
||||||
|
'controlCenter/widgets/shared/toggleButton.vala',
|
||||||
# Widget: Title
|
# Widget: Title
|
||||||
'controlCenter/widgets/title/title.vala',
|
'controlCenter/widgets/title/title.vala',
|
||||||
# Widget: Dnd
|
# Widget: Dnd
|
||||||
@@ -49,13 +50,16 @@ widget_sources = [
|
|||||||
|
|
||||||
app_sources = [
|
app_sources = [
|
||||||
'main.vala',
|
'main.vala',
|
||||||
|
'animation/animation.vala',
|
||||||
'orderedHashTable/orderedHashTable.vala',
|
'orderedHashTable/orderedHashTable.vala',
|
||||||
'configModel/configModel.vala',
|
'configModel/configModel.vala',
|
||||||
'swayncDaemon/swayncDaemon.vala',
|
'swayncDaemon/swayncDaemon.vala',
|
||||||
'notiDaemon/notiDaemon.vala',
|
'notiDaemon/notiDaemon.vala',
|
||||||
'notiModel/notiModel.vala',
|
'notiModel/notiModel.vala',
|
||||||
|
'fadedViewport/fadedViewport.vala',
|
||||||
'notificationWindow/notificationWindow.vala',
|
'notificationWindow/notificationWindow.vala',
|
||||||
'notification/notification.vala',
|
'notification/notification.vala',
|
||||||
|
'notificationGroup/notificationGroup.vala',
|
||||||
'controlCenter/controlCenter.vala',
|
'controlCenter/controlCenter.vala',
|
||||||
widget_sources,
|
widget_sources,
|
||||||
'blankWindow/blankWindow.vala',
|
'blankWindow/blankWindow.vala',
|
||||||
@@ -63,13 +67,19 @@ app_sources = [
|
|||||||
constants,
|
constants,
|
||||||
]
|
]
|
||||||
|
|
||||||
|
assert(meson.get_compiler('vala').version() >= '0.56')
|
||||||
|
|
||||||
app_deps = [
|
app_deps = [
|
||||||
dependency('gio-2.0', version: '>= 2.50'),
|
dependency('gio-2.0', version: '>= 2.50'),
|
||||||
dependency('gio-unix-2.0', version: '>= 2.50'),
|
dependency('gio-unix-2.0', version: '>= 2.50'),
|
||||||
dependency('gtk+-3.0', version: '>= 3.22'),
|
dependency('gtk+-3.0', version: '>= 3.22'),
|
||||||
dependency('json-glib-1.0', version: '>= 1.0'),
|
dependency('json-glib-1.0', version: '>= 1.0'),
|
||||||
dependency('libhandy-1', version: '>= 1.2.3'),
|
dependency('libhandy-1', version: '>= 1.8.0'),
|
||||||
meson.get_compiler('c').find_library('gtk-layer-shell'),
|
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('c').find_library('m', required : true),
|
||||||
meson.get_compiler('vala').find_library('posix'),
|
meson.get_compiler('vala').find_library('posix'),
|
||||||
dependency('gee-0.8'),
|
dependency('gee-0.8'),
|
||||||
@@ -97,36 +107,18 @@ if get_option('pulse-audio')
|
|||||||
]
|
]
|
||||||
endif
|
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 = [
|
args = [
|
||||||
'--target-glib=2.50',
|
'--target-glib=2.50',
|
||||||
'--pkg=GtkLayerShell-0.1',
|
'--pkg=GtkLayerShell-0.1',
|
||||||
]
|
]
|
||||||
|
|
||||||
sysconfdir = get_option('sysconfdir')
|
app_resources += gnome.compile_resources('sway_notification_center-resources',
|
||||||
gnome = import('gnome')
|
|
||||||
|
|
||||||
app_sources += gnome.compile_resources('sway_notification_center-resources',
|
|
||||||
'sway_notification_center.gresource.xml',
|
'sway_notification_center.gresource.xml',
|
||||||
c_name: 'sway_notification_center'
|
c_name: 'sway_notification_center'
|
||||||
)
|
)
|
||||||
|
|
||||||
executable('swaync',
|
executable('swaync',
|
||||||
app_sources,
|
[ app_sources, app_resources ],
|
||||||
vala_args: args,
|
vala_args: args,
|
||||||
dependencies: app_deps,
|
dependencies: app_deps,
|
||||||
install: true,
|
install: true,
|
||||||
@@ -139,8 +131,6 @@ executable('swaync-client',
|
|||||||
install: true,
|
install: true,
|
||||||
)
|
)
|
||||||
|
|
||||||
config_path = join_paths(sysconfdir, 'xdg/swaync')
|
|
||||||
|
|
||||||
config_data = configuration_data()
|
config_data = configuration_data()
|
||||||
config_data.set_quoted('JSONPATH', join_paths('/', config_path, 'configSchema.json'))
|
config_data.set_quoted('JSONPATH', join_paths('/', config_path, 'configSchema.json'))
|
||||||
config_json = configure_file(
|
config_json = configure_file(
|
||||||
@@ -149,6 +139,5 @@ config_json = configure_file(
|
|||||||
configuration : config_data
|
configuration : config_data
|
||||||
)
|
)
|
||||||
|
|
||||||
install_data('style.css', install_dir : config_path)
|
|
||||||
install_data(config_json, install_dir : config_path)
|
install_data(config_json, install_dir : config_path)
|
||||||
install_data('configSchema.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 */
|
/** Method to close notification and send DISMISSED signal */
|
||||||
public void manually_close_notification (uint32 id, bool timeout)
|
public void manually_close_notification (uint32 id, bool timeout)
|
||||||
throws DBusError, IOError {
|
throws DBusError, IOError {
|
||||||
NotificationWindow.instance.close_notification (id, false);
|
NotificationWindow.instance.close_notification (id, true);
|
||||||
if (!timeout) {
|
if (!timeout) {
|
||||||
control_center.close_notification (id);
|
control_center.close_notification (id, true);
|
||||||
NotificationClosed (id, ClosedReasons.DISMISSED);
|
NotificationClosed (id, ClosedReasons.DISMISSED);
|
||||||
|
|
||||||
swaync_daemon.subscribe_v2 (control_center.notification_count (),
|
swaync_daemon.subscribe_v2 (control_center.notification_count (),
|
||||||
@@ -170,11 +170,10 @@ namespace SwayNotificationCenter {
|
|||||||
|
|
||||||
debug ("Notification: %s\n", param.to_string ());
|
debug ("Notification: %s\n", param.to_string ());
|
||||||
|
|
||||||
// Replace notification logic
|
// Get the notification id to replace
|
||||||
|
uint32 replace_notification = 0;
|
||||||
if (id == replaces_id) {
|
if (id == replaces_id) {
|
||||||
param.replaces = true;
|
replace_notification = id;
|
||||||
NotificationWindow.instance.close_notification (id, true);
|
|
||||||
control_center.close_notification (id, true);
|
|
||||||
} else if (param.synchronous != null
|
} else if (param.synchronous != null
|
||||||
&& param.synchronous.length > 0) {
|
&& param.synchronous.length > 0) {
|
||||||
// Tries replacing without replaces_id instead
|
// Tries replacing without replaces_id instead
|
||||||
@@ -182,29 +181,47 @@ namespace SwayNotificationCenter {
|
|||||||
// if there is any notification to replace
|
// if there is any notification to replace
|
||||||
if (synchronous_ids.lookup_extended (
|
if (synchronous_ids.lookup_extended (
|
||||||
param.synchronous, null, out r_id)) {
|
param.synchronous, null, out r_id)) {
|
||||||
param.replaces = true;
|
replace_notification = r_id;
|
||||||
// Close the notification
|
|
||||||
NotificationWindow.instance.close_notification (r_id, true);
|
|
||||||
control_center.close_notification (r_id, true);
|
|
||||||
}
|
}
|
||||||
synchronous_ids.set (param.synchronous, id);
|
synchronous_ids.set (param.synchronous, id);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only show popup notification if it is ENABLED or TRANSIENT
|
bool show_notification = state == NotificationStatusEnum.ENABLED
|
||||||
if ((state == NotificationStatusEnum.ENABLED || state == NotificationStatusEnum.TRANSIENT)
|
|| state == NotificationStatusEnum.TRANSIENT;
|
||||||
&& !control_center.get_visibility ()) {
|
// Don't show the notification window if the control center is open
|
||||||
// Also check if urgency is Critical and not inhibited and dnd
|
if (control_center.get_visibility ()) {
|
||||||
if (param.urgency == UrgencyLevels.CRITICAL ||
|
show_notification = false;
|
||||||
(!dnd && !swaync_daemon.inhibited
|
|
||||||
&& param.urgency != UrgencyLevels.CRITICAL)) {
|
|
||||||
NotificationWindow.instance.add_notification (param, this);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
// Only add notification to CC if it isn't IGNORED and not transient/TRANSIENT
|
||||||
if (state != NotificationStatusEnum.IGNORED
|
if (state != NotificationStatusEnum.IGNORED
|
||||||
&& state != NotificationStatusEnum.TRANSIENT
|
&& state != NotificationStatusEnum.TRANSIENT
|
||||||
&& !param.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
|
#if WANT_SCRIPTING
|
||||||
@@ -292,8 +309,8 @@ namespace SwayNotificationCenter {
|
|||||||
*/
|
*/
|
||||||
[DBus (name = "CloseNotification")]
|
[DBus (name = "CloseNotification")]
|
||||||
public void close_notification (uint32 id) throws DBusError, IOError {
|
public void close_notification (uint32 id) throws DBusError, IOError {
|
||||||
NotificationWindow.instance.close_notification (id, false);
|
NotificationWindow.instance.close_notification (id, true);
|
||||||
control_center.close_notification (id);
|
control_center.close_notification (id, true);
|
||||||
NotificationClosed (id, ClosedReasons.CLOSED_BY_CLOSENOTIFICATION);
|
NotificationClosed (id, ClosedReasons.CLOSED_BY_CLOSENOTIFICATION);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -110,10 +110,16 @@ namespace SwayNotificationCenter {
|
|||||||
/** Disables scripting for notification */
|
/** Disables scripting for notification */
|
||||||
public bool swaync_no_script { get; set; }
|
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; }
|
public Array<Action> actions { get; set; }
|
||||||
|
|
||||||
/** If the notification replaces another */
|
public DesktopAppInfo ? desktop_app_info = null;
|
||||||
public bool replaces { get; set; }
|
|
||||||
|
public string name_id;
|
||||||
|
|
||||||
|
public string display_name;
|
||||||
|
|
||||||
public NotifyParams (uint32 applied_id,
|
public NotifyParams (uint32 applied_id,
|
||||||
string app_name,
|
string app_name,
|
||||||
@@ -134,12 +140,37 @@ namespace SwayNotificationCenter {
|
|||||||
this.expire_timeout = expire_timeout;
|
this.expire_timeout = expire_timeout;
|
||||||
this.time = (int64) (get_real_time () * 0.000001);
|
this.time = (int64) (get_real_time () * 0.000001);
|
||||||
|
|
||||||
this.replaces = false;
|
|
||||||
this.has_synch = false;
|
this.has_synch = false;
|
||||||
|
|
||||||
parse_hints ();
|
parse_hints ();
|
||||||
|
|
||||||
parse_actions (actions);
|
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 () {
|
private void parse_hints () {
|
||||||
@@ -151,6 +182,11 @@ namespace SwayNotificationCenter {
|
|||||||
swaync_no_script = hint_value.get_boolean ();
|
swaync_no_script = hint_value.get_boolean ();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case "SWAYNC_BYPASS_DND":
|
||||||
|
if (hint_value.is_of_type (VariantType.BOOLEAN)) {
|
||||||
|
swaync_bypass_dnd = hint_value.get_boolean ();
|
||||||
|
}
|
||||||
|
break;
|
||||||
case "value":
|
case "value":
|
||||||
if (hint_value.is_of_type (VariantType.INT32)) {
|
if (hint_value.is_of_type (VariantType.INT32)) {
|
||||||
this.has_synch = true;
|
this.has_synch = true;
|
||||||
|
@@ -60,15 +60,37 @@
|
|||||||
<property name="visible">True</property>
|
<property name="visible">True</property>
|
||||||
<property name="can-focus">False</property>
|
<property name="can-focus">False</property>
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkImage" id="img">
|
<object class="GtkOverlay">
|
||||||
<property name="visible">True</property>
|
<property name="visible">True</property>
|
||||||
<property name="can-focus">False</property>
|
<property name="can-focus">False</property>
|
||||||
|
<property name="halign">center</property>
|
||||||
<property name="valign">center</property>
|
<property name="valign">center</property>
|
||||||
<property name="margin-end">12</property>
|
<child>
|
||||||
<property name="icon_size">6</property>
|
<object class="GtkImage" id="img">
|
||||||
<style>
|
<property name="visible">True</property>
|
||||||
<class name="image"/>
|
<property name="can-focus">False</property>
|
||||||
</style>
|
<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>
|
</object>
|
||||||
<packing>
|
<packing>
|
||||||
<property name="expand">False</property>
|
<property name="expand">False</property>
|
||||||
@@ -81,7 +103,6 @@
|
|||||||
<property name="visible">True</property>
|
<property name="visible">True</property>
|
||||||
<property name="can-focus">False</property>
|
<property name="can-focus">False</property>
|
||||||
<property name="valign">center</property>
|
<property name="valign">center</property>
|
||||||
<property name="margin-end">14</property>
|
|
||||||
<property name="hexpand">True</property>
|
<property name="hexpand">True</property>
|
||||||
<property name="vexpand">True</property>
|
<property name="vexpand">True</property>
|
||||||
<property name="orientation">vertical</property>
|
<property name="orientation">vertical</property>
|
||||||
@@ -153,6 +174,9 @@
|
|||||||
<property name="position">1</property>
|
<property name="position">1</property>
|
||||||
</packing>
|
</packing>
|
||||||
</child>
|
</child>
|
||||||
|
<style>
|
||||||
|
<class name="text-box"/>
|
||||||
|
</style>
|
||||||
</object>
|
</object>
|
||||||
<packing>
|
<packing>
|
||||||
<property name="expand">True</property>
|
<property name="expand">True</property>
|
||||||
@@ -281,7 +305,7 @@
|
|||||||
<object class="GtkImage">
|
<object class="GtkImage">
|
||||||
<property name="visible">True</property>
|
<property name="visible">True</property>
|
||||||
<property name="can-focus">False</property>
|
<property name="can-focus">False</property>
|
||||||
<property name="icon-name">window-close-symbolic</property>
|
<property name="icon-name">swaync-close-symbolic</property>
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
<style>
|
<style>
|
||||||
|
@@ -30,7 +30,7 @@ namespace SwayNotificationCenter {
|
|||||||
[GtkChild]
|
[GtkChild]
|
||||||
unowned Gtk.Button close_button;
|
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]
|
[GtkChild]
|
||||||
unowned Gtk.Label summary;
|
unowned Gtk.Label summary;
|
||||||
@@ -41,6 +41,8 @@ namespace SwayNotificationCenter {
|
|||||||
[GtkChild]
|
[GtkChild]
|
||||||
unowned Gtk.Image img;
|
unowned Gtk.Image img;
|
||||||
[GtkChild]
|
[GtkChild]
|
||||||
|
unowned Gtk.Image img_app_icon;
|
||||||
|
[GtkChild]
|
||||||
unowned Gtk.Image body_image;
|
unowned Gtk.Image body_image;
|
||||||
|
|
||||||
// Inline Reply
|
// Inline Reply
|
||||||
@@ -70,8 +72,8 @@ namespace SwayNotificationCenter {
|
|||||||
|
|
||||||
public bool is_timed { get; construct; default = false; }
|
public bool is_timed { get; construct; default = false; }
|
||||||
|
|
||||||
public NotifyParams param { get; construct; }
|
public NotifyParams param { get; private set; }
|
||||||
public NotiDaemon noti_daemon { get; construct; }
|
public unowned NotiDaemon noti_daemon { get; construct; }
|
||||||
|
|
||||||
public NotificationType notification_type {
|
public NotificationType notification_type {
|
||||||
get;
|
get;
|
||||||
@@ -112,8 +114,9 @@ namespace SwayNotificationCenter {
|
|||||||
NotiDaemon noti_daemon,
|
NotiDaemon noti_daemon,
|
||||||
NotificationType notification_type) {
|
NotificationType notification_type) {
|
||||||
Object (noti_daemon: noti_daemon,
|
Object (noti_daemon: noti_daemon,
|
||||||
param: param,
|
|
||||||
notification_type: notification_type);
|
notification_type: notification_type);
|
||||||
|
this.param = param;
|
||||||
|
build_noti ();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Show a timed notification */
|
/** Show a timed notification */
|
||||||
@@ -124,7 +127,6 @@ namespace SwayNotificationCenter {
|
|||||||
uint timeout_low,
|
uint timeout_low,
|
||||||
uint timeout_critical) {
|
uint timeout_critical) {
|
||||||
Object (noti_daemon: noti_daemon,
|
Object (noti_daemon: noti_daemon,
|
||||||
param: param,
|
|
||||||
notification_type: notification_type,
|
notification_type: notification_type,
|
||||||
is_timed: true,
|
is_timed: true,
|
||||||
timeout_delay: timeout,
|
timeout_delay: timeout,
|
||||||
@@ -132,11 +134,13 @@ namespace SwayNotificationCenter {
|
|||||||
timeout_critical_delay: timeout_critical,
|
timeout_critical_delay: timeout_critical,
|
||||||
number_of_body_lines: 5
|
number_of_body_lines: 5
|
||||||
);
|
);
|
||||||
|
this.param = param;
|
||||||
|
build_noti ();
|
||||||
}
|
}
|
||||||
|
|
||||||
construct {
|
construct {
|
||||||
try {
|
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);
|
RegexCompileFlags.MULTILINE);
|
||||||
string joined_tags = string.joinv ("|", TAGS);
|
string joined_tags = string.joinv ("|", TAGS);
|
||||||
tag_regex = new Regex ("<(/?(?:%s))>".printf (joined_tags));
|
tag_regex = new Regex ("<(/?(?:%s))>".printf (joined_tags));
|
||||||
@@ -196,12 +200,85 @@ namespace SwayNotificationCenter {
|
|||||||
});
|
});
|
||||||
|
|
||||||
this.transition_time = ConfigModel.instance.transition_time;
|
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 ();
|
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 () {
|
private void default_action_update_state () {
|
||||||
@@ -235,50 +312,8 @@ namespace SwayNotificationCenter {
|
|||||||
this.summary.set_text (param.summary ?? param.app_name);
|
this.summary.set_text (param.summary ?? param.app_name);
|
||||||
this.summary.set_ellipsize (Pango.EllipsizeMode.END);
|
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_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.revealer.set_transition_duration (this.transition_time);
|
||||||
|
|
||||||
this.carousel.set_animation_duration (this.transition_time);
|
this.carousel.set_animation_duration (this.transition_time);
|
||||||
@@ -295,20 +330,9 @@ namespace SwayNotificationCenter {
|
|||||||
this.carousel_empty_widget_index = 0;
|
this.carousel_empty_widget_index = 0;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
this.carousel.page_changed.connect ((_, i) => {
|
// Reset state
|
||||||
if (i != this.carousel_empty_widget_index) return;
|
this.carousel.scroll_to (event_box);
|
||||||
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
|
|
||||||
this.carousel.allow_scroll_wheel = false;
|
this.carousel.allow_scroll_wheel = false;
|
||||||
#endif
|
|
||||||
|
|
||||||
if (this.progress_bar.visible = param.has_synch) {
|
if (this.progress_bar.visible = param.has_synch) {
|
||||||
this.progress_bar.set_fraction (param.value * 0.01);
|
this.progress_bar.set_fraction (param.value * 0.01);
|
||||||
@@ -322,13 +346,16 @@ namespace SwayNotificationCenter {
|
|||||||
|
|
||||||
this.show ();
|
this.show ();
|
||||||
|
|
||||||
if (param.replaces) {
|
Timeout.add (0, () => {
|
||||||
this.revealer.set_reveal_child (true);
|
this.revealer.set_reveal_child (true);
|
||||||
} else {
|
return Source.REMOVE;
|
||||||
Timeout.add (0, () => {
|
});
|
||||||
this.revealer.set_reveal_child (true);
|
|
||||||
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);
|
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
|
// Removes all image tags and adds them to an array
|
||||||
if (text.length > 0) {
|
if (text.length > 0) {
|
||||||
try {
|
try {
|
||||||
@@ -374,22 +404,29 @@ namespace SwayNotificationCenter {
|
|||||||
|
|
||||||
// Markup
|
// Markup
|
||||||
try {
|
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;
|
Pango.AttrList ? attr = null;
|
||||||
string ? buf = 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);
|
this.body.set_text (buf);
|
||||||
if (attr != null) this.body.set_attributes (attr);
|
if (attr != null) this.body.set_attributes (attr);
|
||||||
@@ -421,6 +458,7 @@ namespace SwayNotificationCenter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void click_alt_action (uint index) {
|
public void click_alt_action (uint index) {
|
||||||
|
if (alt_actions_box == null) return;
|
||||||
List<weak Gtk.Widget> ? children = alt_actions_box.get_children ();
|
List<weak Gtk.Widget> ? children = alt_actions_box.get_children ();
|
||||||
uint length = children.length ();
|
uint length = children.length ();
|
||||||
if (length == 0 || index >= length) return;
|
if (length == 0 || index >= length) return;
|
||||||
@@ -452,6 +490,11 @@ namespace SwayNotificationCenter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void set_style_urgency () {
|
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) {
|
switch (param.urgency) {
|
||||||
case UrgencyLevels.LOW:
|
case UrgencyLevels.LOW:
|
||||||
base_box.get_style_context ().add_class ("low");
|
base_box.get_style_context ().add_class ("low");
|
||||||
@@ -467,6 +510,8 @@ namespace SwayNotificationCenter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void set_inline_reply () {
|
private void set_inline_reply () {
|
||||||
|
// Reset state
|
||||||
|
inline_reply_box.hide ();
|
||||||
// Only show inline replies in popup notifications if the compositor
|
// Only show inline replies in popup notifications if the compositor
|
||||||
// supports ON_DEMAND layer shell keyboard interactivity
|
// supports ON_DEMAND layer shell keyboard interactivity
|
||||||
if (!ConfigModel.instance.notification_inline_replies
|
if (!ConfigModel.instance.notification_inline_replies
|
||||||
@@ -494,32 +539,23 @@ namespace SwayNotificationCenter {
|
|||||||
},
|
},
|
||||||
null);
|
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.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 () {
|
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
|
// Check for security codes
|
||||||
string ? code = parse_body_codes ();
|
string ? code = parse_body_codes ();
|
||||||
if (param.actions.length > 0 || code != null) {
|
if (param.actions.length > 0 || code != null) {
|
||||||
var viewport = new Gtk.Viewport (null, null);
|
var viewport = new Gtk.Viewport (null, null);
|
||||||
var scroll = new Gtk.ScrolledWindow (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_homogeneous (true);
|
||||||
alt_actions_box.set_layout (Gtk.ButtonBoxStyle.EXPAND);
|
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 () {
|
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;
|
var image_visibility = ConfigModel.instance.image_visibility;
|
||||||
if (image_visibility == ImageVisibility.NEVER) {
|
if (image_visibility == ImageVisibility.NEVER) {
|
||||||
img.set_visible (false);
|
img.set_visible (false);
|
||||||
|
img_app_icon.set_visible (false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
img.set_pixel_size (notification_icon_size);
|
img.set_pixel_size (notification_icon_size);
|
||||||
img.height_request = notification_icon_size;
|
img.height_request = notification_icon_size;
|
||||||
img.width_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 ();
|
param.image_path ?? "").query_exists ();
|
||||||
var app_icon_exists = File.new_for_path (
|
if (param.image_path != null && !img_path_exists) {
|
||||||
param.app_icon ?? "").query_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) {
|
if (param.image_data.is_initialized) {
|
||||||
Functions.set_image_data (param.image_data, img,
|
Functions.set_image_data (param.image_data, img,
|
||||||
notification_icon_size);
|
notification_icon_size, radius);
|
||||||
} else if (param.image_path != null &&
|
} else if (param.image_path != null &&
|
||||||
param.image_path != "" &&
|
param.image_path != "" &&
|
||||||
img_path_exists) {
|
img_path_exists) {
|
||||||
Functions.set_image_path (param.image_path, img,
|
Functions.set_image_uri (param.image_path, img,
|
||||||
notification_icon_size,
|
notification_icon_size,
|
||||||
|
radius,
|
||||||
img_path_exists);
|
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) {
|
} else if (param.icon_data.is_initialized) {
|
||||||
Functions.set_image_data (param.icon_data, img,
|
Functions.set_image_data (param.icon_data, img,
|
||||||
notification_icon_size);
|
notification_icon_size, radius);
|
||||||
} else {
|
}
|
||||||
|
|
||||||
|
if (img.storage_type == Gtk.ImageType.EMPTY) {
|
||||||
// Get the app icon
|
// Get the app icon
|
||||||
Icon ? icon = null;
|
if (app_icon_uri != null) {
|
||||||
if (param.desktop_entry != null) {
|
Functions.set_image_uri (app_icon_uri, img,
|
||||||
string entry = param.desktop_entry;
|
notification_icon_size,
|
||||||
entry = entry.replace (".desktop", "");
|
radius,
|
||||||
DesktopAppInfo entry_info = new DesktopAppInfo (
|
app_icon_exists);
|
||||||
"%s.desktop".printf (entry));
|
} else if (app_icon_name != null) {
|
||||||
// Checks if the .desktop file actually exists or not
|
img.set_from_gicon (app_icon_name, icon_size);
|
||||||
if (entry_info is DesktopAppInfo) {
|
|
||||||
icon = entry_info.get_icon ();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (icon != null) {
|
|
||||||
img.set_from_gicon (icon, icon_size);
|
|
||||||
} else if (image_visibility == ImageVisibility.ALWAYS) {
|
} else if (image_visibility == ImageVisibility.ALWAYS) {
|
||||||
// Default icon
|
// Default icon
|
||||||
img.set_from_icon_name ("image-missing", icon_size);
|
img.set_from_icon_name ("image-missing", icon_size);
|
||||||
} else {
|
} else {
|
||||||
img.set_visible (false);
|
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) {
|
public void change_visibility (bool value) {
|
||||||
if (!value) {
|
if (!value) {
|
||||||
close_all_notifications ();
|
close_all_notifications ();
|
||||||
|
close ();
|
||||||
} else {
|
} else {
|
||||||
this.set_anchor ();
|
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
|
// Remove notification and its destruction timeout
|
||||||
if (noti != null) {
|
if (noti != null) {
|
||||||
#if HAVE_LATEST_GTK_LAYER_SHELL
|
|
||||||
if (noti.has_inline_reply) {
|
if (noti.has_inline_reply) {
|
||||||
inline_reply_notifications.remove (noti.param.applied_id);
|
inline_reply_notifications.remove (noti.param.applied_id);
|
||||||
if (inline_reply_notifications.size == 0
|
if (inline_reply_notifications.size == 0
|
||||||
@@ -183,12 +183,11 @@ namespace SwayNotificationCenter {
|
|||||||
this, GtkLayerShell.KeyboardMode.NONE);
|
this, GtkLayerShell.KeyboardMode.NONE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
noti.remove_noti_timeout ();
|
noti.remove_noti_timeout ();
|
||||||
noti.destroy ();
|
noti.destroy ();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!replaces
|
if (dismiss
|
||||||
&& (!get_realized ()
|
&& (!get_realized ()
|
||||||
|| !get_mapped ()
|
|| !get_mapped ()
|
||||||
|| !(get_child () is Gtk.Widget)
|
|| !(get_child () is Gtk.Widget)
|
||||||
@@ -198,15 +197,13 @@ namespace SwayNotificationCenter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void add_notification (NotifyParams param,
|
public void add_notification (NotifyParams param) {
|
||||||
NotiDaemon noti_daemon) {
|
|
||||||
var noti = new Notification.timed (param,
|
var noti = new Notification.timed (param,
|
||||||
noti_daemon,
|
swaync_daemon.noti_daemon,
|
||||||
NotificationType.POPUP,
|
NotificationType.POPUP,
|
||||||
ConfigModel.instance.timeout,
|
ConfigModel.instance.timeout,
|
||||||
ConfigModel.instance.timeout_low,
|
ConfigModel.instance.timeout_low,
|
||||||
ConfigModel.instance.timeout_critical);
|
ConfigModel.instance.timeout_critical);
|
||||||
#if HAVE_LATEST_GTK_LAYER_SHELL
|
|
||||||
if (noti.has_inline_reply) {
|
if (noti.has_inline_reply) {
|
||||||
inline_reply_notifications.add (param.applied_id);
|
inline_reply_notifications.add (param.applied_id);
|
||||||
|
|
||||||
@@ -216,7 +213,6 @@ namespace SwayNotificationCenter {
|
|||||||
this, GtkLayerShell.KeyboardMode.ON_DEMAND);
|
this, GtkLayerShell.KeyboardMode.ON_DEMAND);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
|
|
||||||
if (list_reverse) {
|
if (list_reverse) {
|
||||||
box.pack_start (noti);
|
box.pack_start (noti);
|
||||||
@@ -234,16 +230,31 @@ namespace SwayNotificationCenter {
|
|||||||
scroll_to_start (list_reverse);
|
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 ()) {
|
foreach (var w in box.get_children ()) {
|
||||||
var noti = (Notification) w;
|
var noti = (Notification) w;
|
||||||
if (noti != null && noti.param.applied_id == id) {
|
if (noti != null && noti.param.applied_id == id) {
|
||||||
remove_notification (noti, replaces);
|
remove_notification (noti, dismiss);
|
||||||
break;
|
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 () {
|
public uint32 ? get_latest_notification () {
|
||||||
List<weak Gtk.Widget> children = box.get_children ();
|
List<weak Gtk.Widget> children = box.get_children ();
|
||||||
if (children.is_empty ()) return null;
|
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` */
|
/** Closes a specific notification with the `id` */
|
||||||
public void close_notification (uint32 id) throws DBusError, IOError {
|
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