Unverified Commit 3d719a82 authored by Erik Reider's avatar Erik Reider Committed by GitHub

Ported to GTK 4 (#539)

* Ported to GTK 4 * Updated README dependencies * Updated GitHub actions * Added blueprint-compiler to README dependencies * Fixed expanded_group not being set to null when group only contains a single notification * Added a overlay close button for grouped notifications, just like regular notifications * Fixed control center kb shortcuts not always working * Fixed Notification sizing in Control Center When adding a large notification with a body image and actions, and add a seperate non-grouped notification, the first notification would take up more space than needed * Simplified Notification Group offset calculation * Limit the control center height to the monitors height * Fixed vala-lint issues * Fixed MPRIS background not using album art gicon * Show --skip-system-css in --help print * Fixed CC adaptive height being short with fit-to-screen enabled * Fixed grouped notifications not notifying CC that it's not the expanded one * Fixed notification groups focus being buggy and not visually visible * Fixed notification focus resetting to the top when removing the last notification
parent 5a6ae71d
......@@ -21,7 +21,7 @@ jobs:
- name: Install packages
run: |
pacman-key --init
pacman -Syu --noconfirm
pacman -Sy --noconfirm
- name: Add builduser
run: |
......@@ -34,13 +34,23 @@ jobs:
- name: Check out sources
uses: actions/checkout@v3
with:
fetch-depth: 0
path: swaync
- name: Test PKGBUILD
run: |
build_dir="/tmp/PKGBUILD-git/"
cd swaync
build_dir="/tmp/PKGBUILD-git"
cd build-scripts
mkdir $build_dir
cp ./PKGBUILD-git $build_dir/PKGBUILD
mkdir $build_dir/src
cp -r ../ $build_dir/src/swaync
cd $build_dir
sudo chown builduser $build_dir
sudo -H -u builduser bash -c 'makepkg -s --noconfirm'
sudo chown -R builduser $build_dir
# Use the locally checked-out branch instead of downloading the source
sudo -H -u builduser bash -c "makepkg -s --noconfirm --noextract SRCDEST=$build_dir"
......@@ -15,11 +15,11 @@ on:
jobs:
ubuntu-build:
container: ubuntu:24.10
container: ubuntu:25.04
runs-on: ubuntu-latest
env:
DEBIAN_FRONTEND: noninteractive
PACKAGES: meson libwayland-dev libgtk-3-dev gobject-introspection libgirepository1.0-dev valac libjson-glib-dev libhandy-1-dev libgtk-layer-shell-dev scdoc libgee-0.8-dev libpulse-dev sassc libgranite-dev
PACKAGES: meson libwayland-dev libgtk-4-dev gobject-introspection libgirepository1.0-dev valac libjson-glib-dev libgtk4-layer-shell-dev scdoc libgee-0.8-dev libpulse-dev sassc libgranite-7-dev blueprint-compiler libadwaita-1-dev
steps:
- name: Install packages
run: |
......
SwayNotificationCenter
======================
# SwayNotificationCenter
[![Check PKGBUILD builds for Arch.](https://github.com/ErikReider/SwayNotificationCenter/actions/workflows/PKGBUILD-buildd.yml/badge.svg)](https://github.com/ErikReider/SwayNotificationCenter/actions/workflows/PKGBUILD-buildd.yml)
[![Check build for Fedora.](https://github.com/ErikReider/SwayNotificationCenter/actions/workflows/fedora-build.yml/badge.svg)](https://github.com/ErikReider/SwayNotificationCenter/actions/workflows/fedora-build.yml)
......@@ -21,8 +20,7 @@ https://github.com/ErikReider/SwayNotificationCenter/assets/35975961/93ff072f-e6
![Screenshot of panel](./assets/panel.png)
Table of Contents
=================
## Table of Contents
* [Want to show off your sick config?](#want-to-show-off-your-sick-config)
* [Features](#features)
......@@ -49,6 +47,7 @@ Table of Contents
* [Disable scripting](#disable-scripting)
* [i3status-rs Example](#i3status-rs-example)
* [Waybar Example](#waybar-example)
* [Debugging Environment Variables](#debugging-environment-variables)
## Want to show off your sick config?
......@@ -154,7 +153,6 @@ Lunar and later:
sudo apt install sway-notification-center
```
### Debian
Bookworm and later:
......@@ -166,6 +164,7 @@ sudo apt install sway-notification-center
### Guix
The simplest way is to install it to user's profile:
```zsh
guix install swaynotificationcenter
```
......@@ -173,7 +172,8 @@ guix install swaynotificationcenter
But we recommend to use [Guix Home](https://guix.gnu.org/manual/devel/en/html_node/Home-Configuration.html) to manage packages and their configurations declaratively.
### rde
```
```scm
(use-modules (rde features wm))
;; Include the following code into the list of your rde features:
......@@ -186,19 +186,20 @@ But we recommend to use [Guix Home](https://guix.gnu.org/manual/devel/en/html_no
- `vala >= 0.56`
- `meson`
- `blueprint-compiler`
- `git`
- `scdoc`
- `sassc`
- `gtk3`
- `gtk-layer-shell`
- `gtk4`
- `gtk4-layer-shell`
- `dbus`
- `glib2`
- `gobject-introspection`
- `libgee`
- `json-glib`
- `libhandy`
- `libadwaita`
- `gvfs`
- `granite`
- `granite7`
##### Optional Dependencies
......@@ -446,3 +447,15 @@ Alternatively, the number of notifications can be shown by adding `{}` anywhere
// ...
},
```
## Debugging Environment Variables
- `G_MESSAGES_DEBUG=all`: Displays all of the debug messages.
- `GTK_DEBUG=interactive`: Opens the GTK Inspector.
- `G_ENABLE_DIAGNOSTIC=1`: If set to a non-zero value, this environment variable
enables diagnostic messages, like deprecation messages for GObject properties
and signals.
- `G_DEBUG=fatal_criticals` or `G_DEBUG=fatal_warnings`: Causes GLib to abort
the program at the first call to g_warning() or g_critical().
More can be read [here](https://www.manpagez.com/html/glib/glib-2.56.0/glib-running.php)
......@@ -2,7 +2,7 @@
# Maintainer: Erik Reider <erik.reider@protonmail.com>
pkgname=swaync-git
_pkgname=swaync
pkgver=r558.8d1b813
pkgver=r560.a935000
pkgrel=1
pkgdesc="A simple notification daemon with a GTK panel for checking previous notifications like other DEs"
url="https://github.com/ErikReider/SwayNotificationCenter"
......@@ -12,7 +12,7 @@ arch=(
'armv7h' # ARM v7 hardfloat
)
license=('GPL3')
depends=("gtk3" "gtk-layer-shell" "dbus" "glib2" "gobject-introspection" "libgee" "json-glib" "libhandy" "libpulse" "gvfs" "libnotify" "granite")
depends=("gtk3" "gtk4-layer-shell" "dbus" "glib2" "gobject-introspection" "libgee" "json-glib" "libpulse" "gvfs" "libnotify" "granite7" "blueprint-compiler" "libadwaita")
conflicts=("swaync" "swaync-client")
provides=("swaync" "swaync-client" "notification-daemon")
makedepends=("vala>=0.56" meson git scdoc sassc)
......@@ -30,12 +30,15 @@ prepare() {
}
build() {
arch-meson $_pkgname build -Dscripting=true
# Very important to be in the directory! Otherwise somehow breaks linking to the .ui files
cd $_pkgname
arch-meson build -Dscripting=true
ninja -C build
}
package() {
cd $_pkgname
DESTDIR="$pkgdir/" ninja -C build install
install -Dm644 "$_pkgname/COPYING" -t "$pkgdir/usr/share/licenses/$pkgname"
install -Dm644 "$_pkgname/README.md" -t "$pkgdir/usr/share/doc/$pkgname"
install -Dm644 "COPYING" -t "$pkgdir/usr/share/licenses/$pkgname"
install -Dm644 "README.md" -t "$pkgdir/usr/share/doc/$pkgname"
}
......@@ -14,23 +14,24 @@ URL: https://github.com/ErikReider/SwayNotificationCenter
VCS: {{{ git_repo_vcs }}}
Source: {{{ git_repo_pack }}}
BuildRequires: meson >= 0.51.0
BuildRequires: meson >= 1.5.1
BuildRequires: vala >= 0.56
BuildRequires: scdoc
BuildRequires: pkgconfig(gtk+-3.0) >= 3.22
BuildRequires: pkgconfig(gtk-layer-shell-0) >= 0.1
BuildRequires: pkgconfig(gtk4) >= 4.16
BuildRequires: pkgconfig(gtk4-layer-shell-0) >= 1.0.3
BuildRequires: pkgconfig(json-glib-1.0) >= 1.0
BuildRequires: pkgconfig(libhandy-1) >= 1.4.0
BuildRequires: pkgconfig(libadwaita-1) >= 1.6.5
BuildRequires: pkgconfig(glib-2.0) >= 2.50
BuildRequires: pkgconfig(gobject-introspection-1.0) >= 1.68
BuildRequires: pkgconfig(gee-0.8) >= 0.20
BuildRequires: pkgconfig(bash-completion)
BuildRequires: pkgconfig(fish)
BuildRequires: pkgconfig(libpulse)
BuildRequires: pkgconfig(granite)
BuildRequires: pkgconfig(granite-7)
BuildRequires: systemd-devel
BuildRequires: systemd
BuildRequires: sassc
BuildRequires: blueprint-compiler >= 0.16
BuildRequires: granite-devel
Requires: gvfs
......
......@@ -13,23 +13,24 @@ URL: https://github.com/ErikReider/SwayNotificationCenter
VCS: {{{ git_repo_vcs }}}
Source: {{{ git_repo_pack }}}
BuildRequires: meson >= 0.51.0
BuildRequires: meson >= 1.5.1
BuildRequires: vala >= 0.56
BuildRequires: scdoc
BuildRequires: pkgconfig(gtk+-3.0) >= 3.22
BuildRequires: pkgconfig(gtk-layer-shell-0) >= 0.1
BuildRequires: pkgconfig(gtk4) >= 4.16
BuildRequires: pkgconfig(gtk4-layer-shell-0) >= 1.0.3
BuildRequires: pkgconfig(json-glib-1.0) >= 1.0
BuildRequires: pkgconfig(libhandy-1) >= 1.4.0
BuildRequires: pkgconfig(libadwaita-1) >= 1.6.5
BuildRequires: pkgconfig(glib-2.0) >= 2.50
BuildRequires: pkgconfig(gobject-introspection-1.0) >= 1.68
BuildRequires: pkgconfig(gee-0.8) >= 0.20
BuildRequires: pkgconfig(bash-completion)
BuildRequires: pkgconfig(fish)
BuildRequires: pkgconfig(libpulse)
BuildRequires: pkgconfig(granite)
BuildRequires: pkgconfig(granite-7)
BuildRequires: systemd-devel
BuildRequires: systemd
BuildRequires: sassc
BuildRequires: blueprint-compiler >= 0.16
BuildRequires: granite-devel
Requires: gvfs
......
app_resources += gnome.compile_resources('icon-resources',
'swaync_icons.gresource.xml',
c_name: 'sway_notification_center_icons'
)
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" height="16px" viewBox="0 0 16 16" width="16px"><path d="m 3.292969 6.707031 l 4 4 c 0.390625 0.390625 1.023437 0.390625 1.414062 0 l 4 -4 c 0.390625 -0.390625 0.390625 -1.023437 0 -1.414062 s -1.023437 -0.390625 -1.414062 0 l -3.292969 3.292969 l -3.292969 -3.292969 c -0.390625 -0.390625 -1.023437 -0.390625 -1.414062 0 s -0.390625 1.023437 0 1.414062 z m 0 0" fill="#222222" fill-rule="evenodd"/></svg>
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" height="16px" viewBox="0 0 16 16" width="16px"><path d="m 7.292969 5.292969 l -4 4 c -0.390625 0.390625 -0.390625 1.023437 0 1.414062 s 1.023437 0.390625 1.414062 0 l 3.292969 -3.292969 l 3.292969 3.292969 c 0.390625 0.390625 1.023437 0.390625 1.414062 0 s 0.390625 -1.023437 0 -1.414062 l -4 -4 c -0.390625 -0.390625 -1.023437 -0.390625 -1.414062 0 z m 0 0" fill="#222222" fill-rule="evenodd"/></svg>
<?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,8 +2,6 @@ install_data('org.erikreider.swaync.gschema.xml',
install_dir: join_paths(get_option('datadir'), 'glib-2.0/schemas')
)
subdir('icons')
compile_schemas = find_program('glib-compile-schemas', required: false)
if compile_schemas.found()
test('Validate schema file', compile_schemas,
......@@ -34,3 +32,21 @@ style_css = custom_target(
)
message(style_css.full_path())
blueprints = custom_target('blueprints',
input: files(run_command('find', '.', '-name', '*.blp').stdout().strip().split('\n')),
output: '.',
command: [
find_program('blueprint-compiler', required: true),
'batch-compile',
'@OUTPUT@', '@CURRENT_SOURCE_DIR@', '@INPUT@'
],
build_always_stale: true,
build_by_default: true,
)
app_resources += gnome.compile_resources('sway_notification_center-resources',
'sway_notification_center.gresource.xml',
dependencies: [ blueprints ],
c_name: 'sway_notification_center'
)
.widget-backlight {
background-color: #{"@noti-bg"};
padding: 8px;
margin: 8px;
border-radius: 12px;
padding: $margin;
margin: $margin;
border-radius: $border-radius;
}
.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;
}
padding: $margin;
margin: $margin;
border-radius: $border-radius;
.widget-buttons-grid > flowbox > flowboxchild > button.toggle:checked {
& flowboxchild > button {
border-radius: $border-radius;
&.toggle:checked {
/* style given to the active toggle button */
}
}
.widget-buttons-grid > flowbox > flowboxchild > button:hover {
&:hover {
}
}
}
.widget-dnd {
color: #{"@text-color"};
margin: 8px;
label {
color: var(--text-color);
margin: $margin;
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;
}
switch {
border-radius: $border-radius;
margin: $margin;
&:checked {
}
slider {
border-radius: $border-radius;
}
}
}
.widget-inhibitors {
margin: 8px;
& > label {
margin: $margin;
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"};
}
& > button {
margin: $margin;
border-radius: $border-radius;
}
}
.widget-label {
margin: 8px;
}
.widget-label > label {
margin: $margin;
& > label {
font-size: 1.1rem;
}
}
.widget-menubar > box > .menu-button-bar > button {
border: none;
background: transparent;
}
.widget-menubar {
& > .menu-button-bar {
/* The left button container */
& > .start {
margin-left: $margin;
}
/* The right button container */
& > .end {
margin-right: $margin;
}
/* The left and right button container */
& > .widget-menubar-container {
button {
border-radius: $border-radius;
margin: 0 $margin / 2;
}
}
}
/* The revealer buttons */
& > revealer {
margin-top: $margin;
/* .AnyName { Name defined in config after #
background-color: @noti-bg;
button {
border-radius: $border-radius;
margin: $margin;
margin-top: 0;
}
}
/* .AnyName { Name defined in config after #
background-color: rgba(var(--noti-bg), 1.0);
padding: 8px;
margin: 8px;
border-radius: 12px;
}
}
.AnyName>button {
.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;
.AnyName>button:hover {
background-color: var(--noti-bg-hover);
} */
}
@define-color mpris-album-art-overlay rgba(0, 0, 0, 0.55);
@define-color mpris-button-hover rgba(0, 0, 0, 0.50);
:root {
--mpris-album-art-overlay: rgba(0, 0, 0, 0.55);
--mpris-button-hover: rgba(0, 0, 0, 0.5);
}
$mpris-shadow: 0px 0px 10px rgba(0, 0, 0, 0.75);
.widget-mpris {
margin: $margin;
/* 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;
.mpris-background {
filter: blur(10px);
}
.mpris-overlay {
padding: 16px;
background-color: var(--mpris-album-art-overlay);
button:hover {
/* The media player buttons (play, pause, next, etc...) */
background: #{"@noti-bg-hover"};
background: var(--noti-bg-hover);
}
.widget-mpris-album-art {
border-radius: $border-radius;
box-shadow: $mpris-shadow;
-gtk-icon-size: 96px;
}
.widget-mpris-title {
font-weight: bold;
......@@ -33,7 +43,8 @@ $mpris-shadow: 0px 0px 10px rgba(0, 0, 0, 0.75);
& > box > button {
/* Change player control buttons */
&:hover {
background-color: #{"@mpris-button-hover"};
background-color: var(--mpris-button-hover);
}
}
}
}
......
.widget-title {
color: #{"@text-color"};
margin: 8px;
& > label {
margin: $margin;
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"};
}
& > button {
margin: $margin;
border-radius: $border-radius;
}
}
.widget-volume {
background-color: #{"@noti-bg"};
padding: 8px;
margin: 8px;
border-radius: 12px;
padding: $margin;
margin: $margin;
border-radius: $border-radius;
}
.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;
background-color: var(--noti-bg-alt);
padding: $margin/2 $margin $margin $margin;
margin: 0px $margin $margin $margin;
border-radius: $border-radius;
}
<?xml version="1.0" encoding="UTF-8"?>
<gresources>
<gresource prefix="/org/erikreider/swaync">
<file preprocess="xml-stripblanks">icons/scalable/actions/swaync-collapse-symbolic.svg</file>
<file preprocess="xml-stripblanks">icons/scalable/actions/swaync-close-symbolic.svg</file>
<file preprocess="xml-stripblanks">icons/scalable/actions/swaync-up-small-symbolic.svg</file>
<file preprocess="xml-stripblanks">icons/scalable/actions/swaync-down-small-symbolic.svg</file>
<file preprocess="xml-stripblanks">ui/notification_window.ui</file>
<file preprocess="xml-stripblanks">ui/notification.ui</file>
<file preprocess="xml-stripblanks">ui/control_center.ui</file>
<file preprocess="xml-stripblanks">ui/mpris_player.ui</file>
</gresource>
</gresources>
using Gtk 4.0;
template $SwayNotificationCenterControlCenter: ApplicationWindow {
name: "control-center-window";
styles [
"blank-window",
]
ScrolledWindow window {
hscrollbar-policy: never;
styles [
"control-center",
]
child: Viewport {
child: $IterBox box {
orientation: vertical;
vexpand: true;
Box notifications_box {
orientation: vertical;
Stack stack {
hhomogeneous: true;
vhomogeneous: true;
transition-type: crossfade;
transition-duration: 200;
vexpand: true;
StackPage {
name: "notifications-list";
child: ScrolledWindow scrolled_window {
hscrollbar-policy: never;
has-frame: false;
child: Viewport viewport {
vexpand: true;
child: ListBox list_box {
valign: fill;
styles [
"control-center-list",
]
};
};
};
}
StackPage {
name: "notifications-placeholder";
child: Box {
halign: center;
valign: center;
hexpand: true;
vexpand: true;
orientation: vertical;
spacing: 12;
styles [
"control-center-list-placeholder",
]
Image {
pixel-size: 96;
icon-name: "preferences-system-notifications-symbolic";
use-fallback: true;
}
Label text_empty_label {
label: "No Notifications";
}
};
}
}
}
};
};
}
}
using Gtk 4.0;
template $SwayNotificationCenterWidgetsMprisMprisPlayer: $Underlay {
overflow: hidden;
valign: start;
hexpand: true;
underlay_child: Picture background_picture {
content-fit: cover;
styles [
"mpris-background",
]
};
child: Box {
orientation: vertical;
styles [
"mpris-overlay",
]
Box {
spacing: 12;
Image album_art {
icon-name: "audio-x-generic-symbolic";
use-fallback: true;
pixel-size: 96;
overflow: hidden;
styles [
"widget-mpris-album-art",
]
}
Box {
orientation: vertical;
spacing: 4;
hexpand: true;
valign: center;
Label title {
justify: left;
ellipsize: end;
wrap-mode: word_char;
xalign: 0;
yalign: 0;
styles [
"widget-mpris-title",
]
}
Label sub_title {
justify: left;
ellipsize: end;
wrap-mode: word_char;
xalign: 0;
yalign: 0;
styles [
"widget-mpris-subtitle",
]
}
}
}
Box {
spacing: 6;
halign: center;
Button button_shuffle {
has-frame: false;
icon-name: "media-playlist-shuffle-symbolic";
styles [
"circular",
"image-button",
"flat",
]
}
Button button_prev {
has-frame: false;
icon-name: "media-seek-backward-symbolic";
styles [
"circular",
"image-button",
"flat",
]
}
Button button_play_pause {
has-frame: false;
icon-name: "media-playback-pause-symbolic";
styles [
"circular",
"image-button",
"flat",
]
}
Button button_next {
has-frame: false;
icon-name: "media-seek-forward-symbolic";
styles [
"circular",
"image-button",
"flat",
]
}
Button button_repeat {
has-frame: false;
icon-name: "media-playlist-repeat-symbolic";
styles [
"circular",
"image-button",
"flat",
]
}
}
};
}
using Gtk 4.0;
template $SwayNotificationCenterNotification: Box {
hexpand: true;
styles [
"notification-row",
]
Revealer revealer {
transition-type: crossfade;
reveal-child: false;
hexpand: true;
child: $DismissibleWidget dismissible_widget {
halign: fill;
valign: fill;
child: Overlay base_widget {
hexpand: true;
styles [
"notification-background",
]
$IterBox base_box {
orientation: vertical;
overflow: hidden;
styles [
"notification",
]
Box default_action {
orientation: vertical;
styles [
"notification-default-action",
]
Box {
orientation: vertical;
spacing: 8;
styles [
"notification-content",
]
Box {
// Box with images/icons
Overlay {
halign: center;
valign: center;
child: Image img {
// pixel-size: 48;
valign: center;
overflow: hidden;
styles [
"image",
]
};
[overlay]
Image img_app_icon {
// pixel-size: 48;
halign: end;
valign: end;
overflow: hidden;
styles [
"app-icon",
]
}
}
// Body, summary, and time
Box {
hexpand: true;
vexpand: true;
valign: center;
orientation: vertical;
styles [
"text-box",
]
Box {
orientation: horizontal;
valign: center;
Label summary {
justify: left;
ellipsize: end;
wrap: false;
xalign: 0;
hexpand: true;
styles [
"summary",
]
}
Label time {
justify: left;
ellipsize: none;
valign: start;
styles [
"time",
]
}
}
Label body {
ellipsize: end;
justify: left;
wrap-mode: word_char;
natural-wrap-mode: word;
lines: 1;
xalign: 0;
yalign: 0;
hexpand: true;
styles [
"body",
]
}
}
}
ProgressBar progress_bar {
orientation: horizontal;
}
Picture body_image {
halign: center;
overflow: hidden;
}
Box inline_reply_box {
styles [
"inline-reply",
]
Entry inline_reply_entry {
input-purpose: free_form;
input-hints: spellcheck | emoji;
hexpand: true;
styles [
"inline-reply-entry",
]
}
Button inline_reply_button {
styles [
"inline-reply-button",
]
}
}
}
}
FlowBox alt_actions_box {
orientation: horizontal;
homogeneous: true;
overflow: hidden;
selection-mode: none;
activate-on-single-click: true;
styles [
"notification-alt-actions",
]
}
}
[overlay]
$SwayNotificationCenterNotificationCloseButton close_button {}
};
};
}
}
using Gtk 4.0;
template $SwayNotificationCenterNotificationWindow: Gtk.ApplicationWindow {
styles [
"floating-notifications",
]
Gtk.ScrolledWindow scrolled_window {
propagate-natural-height: true;
hscrollbar-policy: never;
vscrollbar-policy: automatic;
has-frame: false;
Gtk.Viewport viewport {
vexpand: true;
$IterBox box {
orientation: vertical;
}
}
}
}
......@@ -19,6 +19,9 @@ swaync - A simple notification daemon with a GTK gui for notifications and the c
*-s, --style* <CSS file>
Use a custom Stylesheet file
*--skip-system-css*
Skip trying to parse the packaged Stylesheet file. Useful for CSS debugging
*-c, --config* <Config file>
Use a custom config file
......
......@@ -330,11 +330,6 @@ config file to be able to detect config errors
optional: true ++
default: 96 ++
description: The size of the album art. ++
image-radius: ++
type: integer ++
optional: true ++
default: 12 ++
description: The border radius of the album art. ++
blacklist: ++
type: array ++
optional: true ++
......@@ -615,7 +610,6 @@ config file to be able to detect config errors
},
"mpris": {
"image-size": 96,
"image-radius": 12,
"blacklist": ["playerctld"],
"autohide": true
},
......
project('sway-notificaton-center', ['c', 'vala'],
version: '0.11.0',
meson_version: '>= 0.59.0',
meson_version: '>= 0.60.0',
default_options: [ 'warning_level=2' ],
)
......
#include <gdk/gdk.h>
#include <gdk/gdkwayland.h>
#include <glib.h>
#include <stdio.h>
#include <wayland-client.h>
/** GDK doesn't provide a vapi file for GDK Wayland... */
#define PRINT_ERROR \
g_error("Gdk Display isn't a Wayland display! Only Wayland is supported")
struct wl_display *get_wl_display() {
GdkDisplay *display = gdk_display_get_default();
if (GDK_IS_WAYLAND_DISPLAY(display)) {
return gdk_wayland_display_get_wl_display(display);
}
PRINT_ERROR;
return NULL;
}
struct wl_surface *get_wl_surface(GdkWindow *window) {
if (GDK_IS_WAYLAND_WINDOW(window)) {
return gdk_wayland_window_get_wl_surface(window);
}
PRINT_ERROR;
return NULL;
}
extern Wl.Display * get_wl_display ();
extern Wl.Surface * get_wl_surface (Gdk.Window window);
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);
}
}
}
namespace SwayNotificationCenter {
public class BlankWindow : Gtk.Window {
public unowned Gdk.Display display { get; private set; }
public class BlankWindow : Gtk.ApplicationWindow {
public unowned Gdk.Monitor monitor { get; private set; }
unowned SwayncDaemon daemon;
Gtk.Button button;
private Gtk.GestureClick blank_window_gesture;
private bool blank_window_down = false;
private bool blank_window_in = false;
public BlankWindow (Gdk.Display disp,
Gdk.Monitor mon,
SwayncDaemon dae) {
display = disp;
monitor = mon;
daemon = dae;
public BlankWindow (Gdk.Monitor monitor) {
this.monitor = monitor;
// Use button click event instead of Window button_press_event due
// to Gtk layer shell bug. This would grab focus instead of ControlCenter
button = new Gtk.Button () {
expand = true,
opacity = 0,
relief = Gtk.ReliefStyle.NONE,
visible = true,
};
button.clicked.connect (() => {
blank_window_gesture = new Gtk.GestureClick ();
((Gtk.Widget) this).add_controller (blank_window_gesture);
blank_window_gesture.touch_only = false;
blank_window_gesture.exclusive = true;
blank_window_gesture.button = Gdk.BUTTON_PRIMARY;
blank_window_gesture.propagation_phase = Gtk.PropagationPhase.BUBBLE;
blank_window_gesture.pressed.connect ((n_press, x, y) => {
blank_window_in = true;
blank_window_down = true;
});
blank_window_gesture.released.connect ((n_press, x, y) => {
// Emit released
if (!blank_window_down) return;
blank_window_down = false;
if (blank_window_in) {
try {
daemon.set_visibility (false);
swaync_daemon.set_visibility (false);
} catch (Error e) {
stderr.printf ("BlankWindow Click Error: %s\n", e.message);
stderr.printf ("ControlCenter BlankWindow Click Error: %s\n",
e.message);
}
}
if (blank_window_gesture.get_current_sequence () == null) {
blank_window_in = false;
}
});
add (button);
blank_window_gesture.update.connect ((gesture, sequence) => {
Gtk.GestureSingle gesture_single = (Gtk.GestureSingle) gesture;
if (sequence != gesture_single.get_current_sequence ()) return;
// Calculate if the clicked coords intersect other monitors
double x, y;
gesture.get_point (sequence, out x, out y);
Graphene.Point click_point = Graphene.Point ()
.init ((float) x, (float) y);
Graphene.Rect ? bounds = null;
this.compute_bounds (this, out bounds);
if (bounds != null && bounds.contains_point (click_point)) {
blank_window_in = false;
}
});
blank_window_gesture.cancel.connect (() => {
blank_window_down = false;
});
if (!GtkLayerShell.is_supported ()) {
stderr.printf ("GTKLAYERSHELL IS NOT SUPPORTED!\n");
......@@ -48,25 +72,22 @@ namespace SwayNotificationCenter {
GtkLayerShell.set_exclusive_zone (this, -1);
GtkLayerShell.Layer layer;
switch (ConfigModel.instance.control_center_layer) {
case Layer.BACKGROUND:
layer = GtkLayerShell.Layer.BACKGROUND;
break;
case Layer.BOTTOM:
layer = GtkLayerShell.Layer.BOTTOM;
break;
case Layer.TOP:
layer = GtkLayerShell.Layer.TOP;
break;
default:
case Layer.OVERLAY:
layer = GtkLayerShell.Layer.OVERLAY;
break;
GtkLayerShell.set_layer (
this, ConfigModel.instance.control_center_layer.to_layer ());
add_css_class ("blank-window");
}
GtkLayerShell.set_layer (this, layer);
get_style_context ().add_class ("blank-window");
protected override void snapshot (Gtk.Snapshot snapshot) {
// HACK: Fixes fully transparent windows not being mapped
Gdk.RGBA color = Gdk.RGBA () {
red = 0,
green = 0,
blue = 0,
alpha = 0,
};
snapshot.append_color (color, Graphene.Rect.zero ());
base.snapshot (snapshot);
}
}
}
......@@ -75,7 +75,6 @@
},
"mpris": {
"image-size": 96,
"image-radius": 12,
"blacklist": [],
"autohide": false
},
......
......@@ -38,6 +38,20 @@ namespace SwayNotificationCenter {
return "overlay";
}
}
public GtkLayerShell.Layer to_layer () {
switch (this) {
case BACKGROUND:
return GtkLayerShell.Layer.BACKGROUND;
case BOTTOM:
return GtkLayerShell.Layer.BOTTOM;
case TOP:
return GtkLayerShell.Layer.TOP;
default:
case OVERLAY:
return GtkLayerShell.Layer.OVERLAY;
}
}
}
public enum CssPriority {
......@@ -75,8 +89,7 @@ namespace SwayNotificationCenter {
public string ? category { get; set; default = null; }
private const RegexCompileFlags REGEX_COMPILE_OPTIONS =
RegexCompileFlags.MULTILINE
| RegexCompileFlags.JAVASCRIPT_COMPAT;
RegexCompileFlags.MULTILINE;
private const RegexMatchFlags REGEX_MATCH_FLAGS = RegexMatchFlags.NOTEMPTY;
......
......@@ -145,7 +145,7 @@
},
"control-center-height": {
"type": "integer",
"description": "Height of the control center in pixels. Ignored when 'fit-to-screen' is set to 'true'",
"description": "Height of the control center in pixels. Ignored when 'fit-to-screen' is set to 'true'. Also limited to the height of the monitor, unless 'layer-shell-cover-screen' is set to false.",
"default": 600,
"minimum": 300
},
......@@ -409,17 +409,6 @@
"type": "integer",
"description": "The size of the album art",
"default": 96
},
"image-radius": {
"type": "integer",
"description": "The border radius of the album art. Will be overriden by setting the border-radius in the style.css for the \".widget-mpris-album-art\" class",
"default": 12
},
"blur": {
"type": "boolean",
"description": "Appy the artwork as the MPRIS background and blur it",
"default": true
}
}
},
......
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.40.0 -->
<interface>
<requires lib="gtk+" version="3.24"/>
<template class="SwayNotificationCenterControlCenter" parent="GtkApplicationWindow">
<property name="name">control-center-window</property>
<property name="can-focus">False</property>
<property name="resizable">False</property>
<property name="skip-taskbar-hint">True</property>
<property name="skip-pager-hint">True</property>
<property name="urgency-hint">True</property>
<property name="decorated">False</property>
<property name="deletable">False</property>
<property name="gravity">north-east</property>
<property name="show-menubar">False</property>
<child>
<object class="GtkBox" id="box">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="vexpand">True</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkBox" id="notifications_box">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkStack" id="stack">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="transition-type">crossfade</property>
<child>
<object class="GtkScrolledWindow" id="scrolled_window">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="hscrollbar-policy">never</property>
<child>
<placeholder/>
</child>
</object>
<packing>
<property name="name">notifications-list</property>
</packing>
</child>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="halign">center</property>
<property name="valign">center</property>
<property name="hexpand">True</property>
<property name="vexpand">True</property>
<property name="orientation">vertical</property>
<property name="spacing">12</property>
<child>
<object class="GtkImage">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="pixel-size">96</property>
<property name="icon-name">preferences-system-notifications-symbolic</property>
<property name="use-fallback">True</property>
<property name="icon_size">0</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="text_empty_label">
<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>
<packing>
<property name="name">notifications-placeholder</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<style>
<class name="control-center"/>
</style>
</object>
</child>
<style>
<class name="blank-window"/>
</style>
</template>
</interface>
......@@ -51,15 +51,14 @@ namespace SwayNotificationCenter.Widgets {
slider.set_draw_value (false);
slider.set_round_digits (0);
slider.set_hexpand (true);
slider.value_changed.connect (() => {
this.client.set_brightness.begin ((float) slider.get_value ());
slider.tooltip_text = ((int) slider.get_value ()).to_string ();
});
add (label_widget);
pack_start (slider, true, true, 0);
show_all ();
append (label_widget);
append (slider);
}
public override void on_cc_visibility_change (bool val) {
......
......@@ -79,16 +79,12 @@ namespace SwayNotificationCenter.Widgets {
public async void set_brightness (float percent) {
this.close ();
try {
if (subsystem == "backlight") {
int actual = calc_actual (percent);
login1.set_brightness.begin (subsystem, device, actual);
} else {
login1.set_brightness.begin (subsystem, device, (uint32) percent);
}
} catch (Error e) {
error ("Error %s\n", e.message);
}
connect_monitor ();
}
......
......@@ -34,8 +34,8 @@ namespace SwayNotificationCenter.Widgets {
this.swaync_daemon = swaync_daemon;
this.noti_daemon = noti_daemon;
get_style_context ().add_class (css_class_name);
if (suffix.length > 0) get_style_context ().add_class (suffix);
add_css_class (css_class_name);
if (suffix.length > 0) add_css_class (suffix);
}
protected Json.Object ? get_config (Gtk.Widget widget) {
......
......@@ -23,7 +23,8 @@ namespace SwayNotificationCenter.Widgets {
Gtk.FlowBox container = new Gtk.FlowBox ();
container.set_selection_mode (Gtk.SelectionMode.NONE);
pack_start (container, true, true, 0);
container.set_hexpand (true);
append (container);
// add action to container
foreach (var act in actions) {
......@@ -40,8 +41,6 @@ namespace SwayNotificationCenter.Widgets {
break;
}
}
show_all ();
}
public override void on_cc_visibility_change (bool value) {
......
......@@ -24,7 +24,9 @@ namespace SwayNotificationCenter.Widgets {
// Title
title_widget = new Gtk.Label (title);
add (title_widget);
title_widget.set_hexpand (true);
title_widget.set_halign (Gtk.Align.START);
append (title_widget);
// Dnd button
dnd_button = new Gtk.Switch () {
......@@ -40,10 +42,8 @@ namespace SwayNotificationCenter.Widgets {
dnd_button.set_can_focus (false);
dnd_button.valign = Gtk.Align.CENTER;
// Backwards compatible towards older CSS stylesheets
dnd_button.get_style_context ().add_class ("control-center-dnd");
pack_end (dnd_button, false);
show_all ();
dnd_button.add_css_class ("control-center-dnd");
append (dnd_button);
}
private bool state_set (Gtk.Widget widget, bool state) {
......
......@@ -42,8 +42,10 @@ namespace SwayNotificationCenter.Widgets {
}
title_widget = new Gtk.Label (title);
title_widget.set_halign (Gtk.Align.START);
title_widget.set_hexpand (true);
title_widget.show ();
add (title_widget);
append (title_widget);
if (has_clear_all_button) {
clear_all_button = new Gtk.Button.with_label (button_text);
......@@ -57,7 +59,7 @@ namespace SwayNotificationCenter.Widgets {
clear_all_button.set_can_focus (false);
clear_all_button.valign = Gtk.Align.CENTER;
clear_all_button.show ();
pack_end (clear_all_button, false);
append (clear_all_button);
}
hide ();
......
......@@ -29,18 +29,17 @@ namespace SwayNotificationCenter.Widgets {
label_widget.set_text (text);
label_widget.set_ellipsize (Pango.EllipsizeMode.END);
label_widget.set_line_wrap (true);
label_widget.set_wrap (true);
label_widget.set_lines (max_lines);
// Without this and pack_start fill, the label would expand to
// the monitors full width... GTK bug!...
label_widget.set_max_width_chars (0);
label_widget.set_line_wrap_mode (Pango.WrapMode.WORD_CHAR);
label_widget.set_wrap_mode (Pango.WrapMode.WORD_CHAR);
label_widget.set_justify (Gtk.Justification.LEFT);
label_widget.set_alignment (0, 0);
label_widget.set_xalign (0.0f);
label_widget.set_yalign (0.0f);
pack_start (label_widget, true, true, 0);
show_all ();
append (label_widget);
}
}
}
......@@ -37,35 +37,48 @@ namespace SwayNotificationCenter.Widgets {
}
}
Gtk.Box menus_container;
Gtk.Box topbar_container;
Gtk.Box left_container;
Gtk.Box right_container;
List<ConfigObject ?> menu_objects;
List<ToggleButton> toggle_buttons;
public Menubar (string suffix, SwayncDaemon swaync_daemon, NotiDaemon noti_daemon) {
base (suffix, swaync_daemon, noti_daemon);
set_orientation (Gtk.Orientation.VERTICAL);
set_hexpand (true);
Json.Object ? config = get_config (this);
if (config != null) {
parse_config_objects (config);
}
menus_container = new Gtk.Box (Gtk.Orientation.VERTICAL, 0);
topbar_container = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 0);
topbar_container.get_style_context ().add_class ("menu-button-bar");
menus_container.add (topbar_container);
Gtk.Box topbar_container = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 0);
topbar_container.add_css_class ("menu-button-bar");
append (topbar_container);
left_container = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 0) {
overflow = Gtk.Overflow.HIDDEN,
hexpand = true,
halign = Gtk.Align.START,
};
left_container.add_css_class ("widget-menubar-container");
left_container.add_css_class ("start");
topbar_container.append (left_container);
right_container = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 0) {
overflow = Gtk.Overflow.HIDDEN,
hexpand = true,
halign = Gtk.Align.END,
};
right_container.add_css_class ("widget-menubar-container");
right_container.add_css_class ("end");
topbar_container.append (right_container);
for (int i = 0; i < menu_objects.length (); i++) {
unowned ConfigObject ? obj = menu_objects.nth_data (i);
add_menu (ref obj);
}
pack_start (menus_container, true, true, 0);
show_all ();
foreach (var obj in menu_objects) {
obj.revealer ?.set_reveal_child (false);
}
......@@ -75,43 +88,48 @@ namespace SwayNotificationCenter.Widgets {
switch (obj.type) {
case MenuType.BUTTONS:
Gtk.Box container = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 0);
if (obj.name != null) container.get_style_context ().add_class (obj.name);
if (obj.name != null) container.add_css_class (obj.name);
foreach (Action a in obj.actions) {
switch (a.type) {
case ButtonType.TOGGLE:
ToggleButton tb = new ToggleButton (a.label, a.command, a.update_command, a.active);
container.add (tb);
container.append (tb);
toggle_buttons.append (tb);
break;
default:
Gtk.Button b = new Gtk.Button.with_label (a.label);
b.clicked.connect (() => execute_command.begin (a.command));
container.add (b);
container.append (b);
break;
}
}
switch (obj.position) {
case Position.LEFT:
topbar_container.pack_start (container, false, false, 0);
left_container.append (container);
break;
case Position.RIGHT:
topbar_container.pack_end (container, false, false, 0);
right_container.append (container);
break;
}
break;
case MenuType.MENU:
Gtk.Button show_button = new Gtk.Button.with_label (obj.label);
Gtk.ToggleButton show_button = new Gtk.ToggleButton.with_label (obj.label);
Gtk.Box menu = new Gtk.Box (Gtk.Orientation.VERTICAL, 0);
if (obj.name != null) menu.get_style_context ().add_class (obj.name);
if (obj.name != null) menu.add_css_class (obj.name);
Gtk.Revealer r = new Gtk.Revealer ();
r.add (menu);
r.set_child (menu);
r.set_transition_duration (obj.animation_duration);
r.set_transition_type (obj.animation_type);
obj.revealer = r;
// Make sure that the toggle buttons state is always synced
// with the revealers visibility.
r.bind_property ("child-revealed",
show_button, "active", BindingFlags.SYNC_CREATE, null, null);
show_button.clicked.connect (() => {
bool visible = !r.get_reveal_child ();
foreach (var o in menu_objects) {
......@@ -124,27 +142,29 @@ namespace SwayNotificationCenter.Widgets {
switch (a.type) {
case ButtonType.TOGGLE:
ToggleButton tb = new ToggleButton (a.label, a.command, a.update_command, a.active);
menu.pack_start (tb, true, true, 0);
tb.set_hexpand (true);
menu.append (tb);
toggle_buttons.append (tb);
break;
default:
Gtk.Button b = new Gtk.Button.with_label (a.label);
b.set_hexpand (true);
b.clicked.connect (() => execute_command.begin (a.command));
menu.pack_start (b, true, true, 0);
menu.append (b);
break;
}
}
switch (obj.position) {
case Position.RIGHT:
topbar_container.pack_end (show_button, false, false, 0);
right_container.append (show_button);
break;
case Position.LEFT:
topbar_container.pack_start (show_button, false, false, 0);
left_container.append (show_button);
break;
}
menus_container.add (r);
append (r);
break;
}
}
......
namespace SwayNotificationCenter.Widgets.Mpris {
public struct Config {
int image_size;
int image_radius;
bool blur;
bool autohide;
string[] blacklist;
}
......@@ -24,15 +22,13 @@ namespace SwayNotificationCenter.Widgets.Mpris {
Gtk.Button button_prev;
Gtk.Button button_next;
Gtk.Box carousel_box;
Hdy.Carousel carousel;
Hdy.CarouselIndicatorDots carousel_dots;
Adw.Carousel carousel;
Adw.CarouselIndicatorDots carousel_dots;
// Default config values
Config mpris_config = Config () {
image_size = 96,
image_radius = 12,
blur = true,
autohide = false
autohide = false,
};
public Mpris (string suffix, SwayncDaemon swaync_daemon, NotiDaemon noti_daemon) {
......@@ -45,43 +41,41 @@ namespace SwayNotificationCenter.Widgets.Mpris {
visible = true,
};
button_prev = new Gtk.Button.from_icon_name ("go-previous", Gtk.IconSize.BUTTON) {
relief = Gtk.ReliefStyle.NONE,
button_prev = new Gtk.Button.from_icon_name ("go-previous") {
has_frame = false,
visible = false,
};
button_prev.clicked.connect (() => change_carousel_position (-1));
button_next = new Gtk.Button.from_icon_name ("go-next", Gtk.IconSize.BUTTON) {
relief = Gtk.ReliefStyle.NONE,
button_next = new Gtk.Button.from_icon_name ("go-next") {
has_frame = false,
visible = false,
};
button_next.clicked.connect (() => change_carousel_position (1));
carousel = new Hdy.Carousel () {
carousel = new Adw.Carousel () {
visible = true,
};
carousel.allow_scroll_wheel = true;
carousel.page_changed.connect ((index) => {
GLib.List<weak Gtk.Widget> children = carousel.get_children ();
int children_length = (int) children.length ();
if (children_length <= 1) {
if (carousel.n_pages <= 1) {
button_prev.sensitive = false;
button_next.sensitive = false;
return;
}
button_prev.sensitive = index > 0;
button_next.sensitive = index < children_length - 1;
button_next.sensitive = index < carousel.n_pages - 1;
});
carousel_box.add (button_prev);
carousel_box.add (carousel);
carousel_box.add (button_next);
add (carousel_box);
carousel_box.append (button_prev);
carousel_box.append (carousel);
carousel_box.append (button_next);
append (carousel_box);
carousel_dots = new Hdy.CarouselIndicatorDots ();
carousel_dots = new Adw.CarouselIndicatorDots ();
carousel_dots.set_carousel (carousel);
carousel_dots.show ();
add (carousel_dots);
append (carousel_dots);
// Config
Json.Object ? config = get_config (this);
......@@ -90,18 +84,6 @@ namespace SwayNotificationCenter.Widgets.Mpris {
int? image_size = get_prop<int> (config, "image-size");
if (image_size != null) mpris_config.image_size = image_size;
// Get image-border-radius
int? image_radius = get_prop<int> (config, "image-radius");
if (image_radius != null) mpris_config.image_radius = image_radius;
// Clamp the radius
mpris_config.image_radius = mpris_config.image_radius.clamp (
0, (int) (mpris_config.image_size * 0.5));
// Get blur
bool blur_found;
bool? blur = get_prop<bool> (config, "blur", out blur_found);
if (blur_found) mpris_config.blur = blur;
Json.Array ? blacklist = get_prop_array (config, "blacklist");
if (blacklist != null) {
mpris_config.blacklist = new string[blacklist.get_length ()];
......@@ -128,19 +110,6 @@ namespace SwayNotificationCenter.Widgets.Mpris {
}
}
/**
* Forces the carousel to reload its style_context.
* Fixes carousel items not redrawing when window isn't visible.
* Probably related to: https://gitlab.gnome.org/GNOME/libhandy/-/issues/363
*/
public override void on_cc_visibility_change (bool value) {
if (!value) return;
carousel.get_style_context ().changed ();
foreach (var child in carousel.get_children ()) {
child.get_style_context ().changed ();
}
}
private void setup_mpris () throws Error {
dbus_iface = Bus.get_proxy_sync (BusType.SESSION,
"org.freedesktop.DBus",
......@@ -175,7 +144,6 @@ namespace SwayNotificationCenter.Widgets.Mpris {
return false;
}
private bool check_player_metadata_empty (string name) {
MprisPlayer ? player = players.lookup (name);
if (player == null) return true;
......@@ -188,19 +156,21 @@ namespace SwayNotificationCenter.Widgets.Mpris {
}
private bool check_carousel_has_player (MprisPlayer player) {
return carousel.get_children ().find (player) != null;
return player != null && player.parent == carousel;
}
private void add_player_to_carousel (string name) {
MprisPlayer ? player = players.lookup (name);
if (player == null || check_carousel_has_player (player)) return;
carousel.prepend (player);
uint children_length = carousel.get_children ().length ();
if (children_length > 1) {
// HACK: The carousel doesn't focus the prepended player when not mapped.
carousel.append (player);
carousel.reorder (player, 0);
if (carousel.n_pages > 1) {
button_prev.show ();
button_next.show ();
// Scroll to the new player
carousel.scroll_to (player);
carousel.scroll_to (player, false);
}
if (!visible) show ();
......@@ -208,7 +178,7 @@ namespace SwayNotificationCenter.Widgets.Mpris {
private void add_player (string name, MprisSource source) {
MprisPlayer player = new MprisPlayer (source, mpris_config);
player.get_style_context ().add_class ("%s-player".printf (css_class_name));
player.add_css_class ("%s-player".printf (css_class_name));
players.set (name, player);
if (mpris_config.autohide) {
......@@ -229,10 +199,11 @@ namespace SwayNotificationCenter.Widgets.Mpris {
MprisPlayer ? player = players.lookup (name);
if (player == null || !check_carousel_has_player (player)) return;
carousel.remove (player);
uint children_length = carousel.get_children ().length ();
if (children_length == 0) hide ();
uint children_length = carousel.n_pages;
if (children_length == 0) {
hide ();
}
if (children_length <= 1) {
button_prev.hide ();
button_next.hide ();
......@@ -248,17 +219,15 @@ namespace SwayNotificationCenter.Widgets.Mpris {
remove_player_from_carousel (name);
player.before_destroy ();
player.destroy ();
players.remove (name);
}
private void change_carousel_position (int delta) {
GLib.List<weak Gtk.Widget> children = carousel.get_children ();
int children_length = (int) children.length ();
uint children_length = carousel.n_pages;
if (children_length == 0) return;
int position = ((int) carousel.position + delta).clamp (
0, children_length - 1);
carousel.scroll_to (children.nth_data (position));
uint position = ((uint) carousel.position + delta)
.clamp (0, (children_length - 1));
carousel.scroll_to (carousel.get_nth_page (position), true);
}
private bool is_blacklisted (string name) {
......@@ -266,7 +235,7 @@ namespace SwayNotificationCenter.Widgets.Mpris {
if (blacklistedPattern == null || blacklistedPattern.length == 0) {
continue;
}
if (GLib.Regex.match_simple (blacklistedPattern, name, GLib.RegexCompileFlags.JAVASCRIPT_COMPAT, 0)) {
if (GLib.Regex.match_simple (blacklistedPattern, name, RegexCompileFlags.DEFAULT, 0)) {
message ("\"%s\" is blacklisted", name);
return true;
}
......
......@@ -10,6 +10,8 @@ namespace SwayNotificationCenter.Widgets {
this.update_command = update_command;
this.label = label;
this.set_has_frame (true);
if (active) {
this.active = true;
}
......
......@@ -33,7 +33,9 @@ namespace SwayNotificationCenter.Widgets {
}
title_widget = new Gtk.Label (title);
add (title_widget);
title_widget.set_hexpand (true);
title_widget.set_halign (Gtk.Align.START);
append (title_widget);
if (has_clear_all_button) {
clear_all_button = new Gtk.Button.with_label (button_text);
......@@ -44,14 +46,19 @@ namespace SwayNotificationCenter.Widgets {
error ("Error: %s\n", e.message);
}
});
if (noti_daemon.control_center != null) {
clear_all_button.set_sensitive (
noti_daemon.control_center.notification_count () > 0);
}
swaync_daemon.subscribe_v2.connect ((count) => {
clear_all_button.set_sensitive (count > 0);
});
clear_all_button.set_can_focus (false);
clear_all_button.valign = Gtk.Align.CENTER;
// Backwards compatible towards older CSS stylesheets
clear_all_button.get_style_context ().add_class ("control-center-clear-all");
pack_end (clear_all_button, false);
clear_all_button.add_css_class ("control-center-clear-all");
append (clear_all_button);
}
show_all ();
}
}
}
......@@ -19,24 +19,27 @@ namespace SwayNotificationCenter.Widgets {
this.show_per_app_icon = show_per_app_icon;
this.show_per_app_label = show_per_app_label;
set_activatable (false);
update (sink_input);
scale.draw_value = false;
scale.set_hexpand (true);
icon.pixel_size = icon_size;
container = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 0);
if (show_per_app_icon) {
container.add (icon);
container.append (icon);
}
if (show_per_app_label) {
container.add (label);
container.append (label);
}
container.pack_start (scale);
container.append (scale);
add (container);
set_child (container);
scale.value_changed.connect (() => {
client.set_sink_input_volume (sink_input, (float) scale.get_value ());
......@@ -49,9 +52,7 @@ namespace SwayNotificationCenter.Widgets {
if (show_per_app_icon) {
icon.set_from_icon_name (
sink_input.application_icon_name ?? "application-x-executable",
Gtk.IconSize.DIALOG
);
sink_input.application_icon_name ?? "application-x-executable");
}
if (show_per_app_label) {
......@@ -60,8 +61,6 @@ namespace SwayNotificationCenter.Widgets {
scale.set_value (sink_input.volume);
scale.tooltip_text = ((int) scale.get_value ()).to_string ();
this.show_all ();
}
}
}
......@@ -10,15 +10,16 @@ namespace SwayNotificationCenter.Widgets {
Gtk.Label label_widget = new Gtk.Label (null);
Gtk.Scale slider = new Gtk.Scale.with_range (Gtk.Orientation.HORIZONTAL, 0, 100, 1);
// Per app volume controll
// Per app volume control
Gtk.ListBox levels_listbox;
Gtk.Button reveal_button;
IterListBoxController list_box_controller;
Gtk.ToggleButton reveal_button;
Gtk.Revealer revealer;
Gtk.Label no_sink_inputs_label;
string empty_label = "No active sink input";
string expand_label = "⇧";
string collapse_label = "⇩";
string ? expand_label = null;
string ? collapse_label = null;
int icon_size = 24;
Gtk.RevealerTransitionType revealer_type = Gtk.RevealerTransitionType.SLIDE_DOWN;
......@@ -98,49 +99,65 @@ namespace SwayNotificationCenter.Widgets {
this.orientation = Gtk.Orientation.VERTICAL;
slider.draw_value = false;
slider.set_hexpand (true);
main_volume_slider_container.add (label_widget);
main_volume_slider_container.pack_start (slider, true, true, 0);
add (main_volume_slider_container);
main_volume_slider_container.append (label_widget);
main_volume_slider_container.append (slider);
append (main_volume_slider_container);
if (show_per_app) {
reveal_button = new Gtk.Button.with_label (expand_label);
revealer = new Gtk.Revealer ();
revealer.transition_type = revealer_type;
revealer.transition_duration = revealer_duration;
levels_listbox = new Gtk.ListBox ();
levels_listbox.get_style_context ().add_class ("per-app-volume");
revealer.add (levels_listbox);
levels_listbox.add_css_class ("per-app-volume");
levels_listbox.set_activate_on_single_click (true);
levels_listbox.set_selection_mode (Gtk.SelectionMode.NONE);
revealer.set_child (levels_listbox);
list_box_controller = new IterListBoxController (levels_listbox);
if (this.client.active_sinks.size == 0) {
no_sink_inputs_label = new Gtk.Label (empty_label);
levels_listbox.add (no_sink_inputs_label);
list_box_controller.append (no_sink_inputs_label);
}
foreach (var item in this.client.active_sinks.values) {
levels_listbox.add (new SinkInputRow (item, client,
icon_size, show_per_app_icon, show_per_app_label));
SinkInputRow row = new SinkInputRow (item, client,
icon_size, show_per_app_icon,
show_per_app_label);
list_box_controller.append (row);
}
this.client.change_active_sink.connect (active_sink_change);
this.client.new_active_sink.connect (active_sink_added);
this.client.remove_active_sink.connect (active_sink_removed);
reveal_button.clicked.connect (() => {
bool show = revealer.reveal_child;
revealer.set_reveal_child (!show);
if (show) {
reveal_button.label = expand_label;
} else {
reveal_button.label = collapse_label;
}
reveal_button = new Gtk.ToggleButton ();
set_button_icon ();
reveal_button.toggled.connect (() => {
set_button_icon ();
revealer.set_reveal_child (reveal_button.active);
});
main_volume_slider_container.pack_end (reveal_button, false, false, 0);
add (revealer);
main_volume_slider_container.append (reveal_button);
append (revealer);
}
}
show_all ();
private void set_button_icon () {
if (!reveal_button.active) {
if (expand_label == null) {
reveal_button.set_icon_name ("swaync-up-small-symbolic");
} else {
reveal_button.set_label (expand_label);
}
} else {
if (collapse_label == null) {
reveal_button.set_icon_name ("swaync-down-small-symbolic");
} else {
reveal_button.set_label (collapse_label);
}
}
}
public override void on_cc_visibility_change (bool val) {
......@@ -160,8 +177,10 @@ namespace SwayNotificationCenter.Widgets {
}
private void active_sink_change (PulseSinkInput sink) {
foreach (var row in levels_listbox.get_children ()) {
if (row == null) continue;
foreach (unowned Gtk.Widget row in list_box_controller.get_children ()) {
if (!(row is SinkInputRow)) {
continue;
}
var s = (SinkInputRow) row;
if (s.sink_input.cmp (sink)) {
s.update (sink);
......@@ -173,25 +192,28 @@ namespace SwayNotificationCenter.Widgets {
private void active_sink_added (PulseSinkInput sink) {
// one element added -> remove the empty label
if (this.client.active_sinks.size == 1) {
var label = levels_listbox.get_children ().first ().data;
levels_listbox.remove ((Gtk.Widget) label);
var label = levels_listbox.get_first_child ();
list_box_controller.remove ((Gtk.Widget) label);
}
levels_listbox.add (new SinkInputRow (sink, client, icon_size, show_per_app_icon, show_per_app_label));
show_all ();
SinkInputRow row = new SinkInputRow (sink, client,
icon_size, show_per_app_icon,
show_per_app_label);
list_box_controller.append (row);
}
private void active_sink_removed (PulseSinkInput sink) {
foreach (var row in levels_listbox.get_children ()) {
if (row == null) continue;
foreach (unowned Gtk.Widget row in list_box_controller.get_children ()) {
if (!(row is SinkInputRow)) {
continue;
}
var s = (SinkInputRow) row;
if (s.sink_input.cmp (sink)) {
levels_listbox.remove (row);
list_box_controller.remove (row);
break;
}
}
if (levels_listbox.get_children ().length () == 0) {
levels_listbox.add (no_sink_inputs_label);
show_all ();
if (levels_listbox.get_first_child () == null) {
list_box_controller.append (no_sink_inputs_label);
}
}
}
......
public enum SwipeDirection {
SWIPE_LEFT, SWIPE_RIGHT;
}
public class DismissibleWidget : Gtk.Widget, Adw.Swipeable {
protected unowned Gtk.Widget _child = null;
public unowned Gtk.Widget child {
get {
return _child;
}
set {
_child = value;
if (_child != null) {
_child.unparent ();
_child.set_parent (this);
}
}
}
public Gtk.Orientation orientation {
get;
private set;
default = Gtk.Orientation.HORIZONTAL;
}
// Animation
Adw.SpringAnimation animation;
Adw.AnimationTarget target;
// Swipe Gesture
Adw.SwipeTracker swipe_tracker;
bool transition_running = false;
bool gesture_active = false;
double child_offset = 0;
double swipe_progress = 0.0;
SwipeDirection swipe_direction = SwipeDirection.SWIPE_RIGHT;
construct {
swipe_tracker = new Adw.SwipeTracker (this);
swipe_tracker.set_orientation (orientation);
swipe_tracker.set_reversed (true);
swipe_tracker.set_upper_overshoot (true);
swipe_tracker.set_lower_overshoot (true);
swipe_tracker.set_allow_long_swipes (true);
swipe_tracker.set_enabled (true);
swipe_tracker.set_allow_mouse_drag (true);
swipe_tracker.prepare.connect (swipe_prepare_cb);
swipe_tracker.update_swipe.connect (swipe_update_swipe_cb);
swipe_tracker.end_swipe.connect (swipe_end_swipe_cb);
double[] snap_dir = get_snap_points ();
target = new Adw.CallbackAnimationTarget (animate_value_cb);
animation = new Adw.SpringAnimation (this, snap_dir[0], snap_dir[1],
new Adw.SpringParams (1, 0.5, 500),
target);
animation.set_clamp (true);
animation.done.connect (animation_done_cb);
}
public signal void dismissed ();
public void set_can_dismiss (bool state) {
swipe_tracker.set_enabled (state);
}
/*
* Overrides
*/
protected override Gtk.SizeRequestMode get_request_mode () {
return Gtk.SizeRequestMode.HEIGHT_FOR_WIDTH;
}
protected override void measure (Gtk.Orientation orientation, int for_size,
out int minimum, out int natural,
out int minimum_baseline, out int natural_baseline) {
minimum = 0;
natural = 0;
minimum_baseline = -1;
natural_baseline = -1;
int child_min, child_nat;
if (!child.visible) {
return;
}
child.measure (orientation, for_size,
out child_min, out child_nat, null, null);
minimum = int.max (minimum, child_min);
natural = int.max (natural, child_nat);
}
protected override void size_allocate (int width, int height, int baseline) {
if (!child.visible) {
return;
}
int child_width, child_height;
int min = 0, nat = 0;
if (orientation == Gtk.Orientation.HORIZONTAL) {
child.measure (orientation,
height, out min, out nat, null, null);
} else {
child.measure (orientation,
width, out min, out nat, null, null);
}
child_width = width;
child_height = height;
double x = 0;
if (get_direction () == Gtk.TextDirection.RTL) {
x -= ((width * swipe_progress) - (width - child_width) / 2.0)
+ (width * child_offset * 2);
} else {
x -= - ((width * swipe_progress) - (width - child_width) / 2.0)
- (width * child_offset * 2);
}
Gsk.Transform transform = new Gsk.Transform ()
.translate (Graphene.Point ().init ((float) x, 0));
child.allocate (child_width, child_height, baseline, transform);
}
protected override void snapshot (Gtk.Snapshot snapshot) {
snapshot.save ();
snapshot.push_opacity (1 - swipe_progress.abs ());
snapshot_child (child, snapshot);
snapshot.pop ();
snapshot.restore ();
}
/*
* Callbacks
*/
private void animate_value_cb (double value) {
set_position (value);
}
private void animation_done_cb () {
transition_running = false;
if (swipe_progress != 0) {
dismissed ();
}
}
private void swipe_prepare_cb (Adw.NavigationDirection direction) {
gesture_active = true;
if (transition_running) {
animation.pause ();
} else {
transition_running = true;
}
}
private void swipe_update_swipe_cb (double distance) {
set_position (distance);
}
private void swipe_end_swipe_cb (double velocity, double to) {
if (!gesture_active) {
return;
}
animation.set_value_from (swipe_progress);
animation.set_value_to (to);
animation.set_initial_velocity (velocity);
animation.play ();
gesture_active = false;
}
/*
* Methods
*/
private void set_position (double value) {
this.swipe_progress = value;
queue_allocate ();
}
public void set_gesture_direction (SwipeDirection swipe_direction) {
this.swipe_direction = swipe_direction;
// Reset the position
set_position (0.0);
}
/*
* Swipe gesture
*/
/** Gets the progress this will snap back to after the gesture is canceled. */
public double get_cancel_progress () {
return 0;
}
/** Gets the swipe distance of this. */
public double get_distance () {
return get_width ();
}
/** Gets the current progress of this. */
public double get_progress () {
if (!transition_running) {
return 0;
}
return this.swipe_progress;
}
/** Gets the snap points of this. */
public double[] get_snap_points () {
switch (swipe_direction) {
case SwipeDirection.SWIPE_LEFT:
return new double[] { -1, 0 };
default:
case SwipeDirection.SWIPE_RIGHT:
return new double[] { 0, 1 };
}
}
/**
* Gets the area this can start a swipe from for the given direction
* and gesture type.
*/
public Gdk.Rectangle get_swipe_area (Adw.NavigationDirection direction,
bool is_drag) {
Gtk.Allocation alloc = Gtk.Allocation ();
Graphene.Rect bounds;
this.compute_bounds (this, out bounds);
alloc.width = (int) bounds.size.width;
alloc.height = (int) bounds.size.height;
alloc.x = (int) bounds.origin.x;
alloc.y = (int) bounds.origin.y;
return alloc;
}
}
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;
}
}
public class IterBox : Gtk.Box {
public uint length { get; private set; default = 0; }
private List<Gtk.Widget> children = new List<Gtk.Widget> ();
public IterBox (Gtk.Orientation orientation, int spacing) {
Object (orientation: orientation, spacing: spacing);
set_name ("iterbox");
}
private void on_add (Gtk.Widget child) {
length++;
child.destroy.connect (() => {
children.remove (child);
});
}
public List<weak Gtk.Widget> get_children () {
return children.copy ();
}
public new void append (Gtk.Widget child) {
children.append (child);
base.append (children.last ().data);
on_add (child);
}
public new void prepend (Gtk.Widget child) {
children.prepend (child);
base.prepend (children.first ().data);
on_add (child);
}
public new void remove (Gtk.Widget child) {
children.remove (child);
base.remove (child);
length--;
}
}
public class IterListBoxController : Object {
public uint length { get; private set; default = 0; }
private List<Gtk.Widget> children = new List<Gtk.Widget> ();
public unowned Gtk.ListBox list_box {
get;
private set;
}
public IterListBoxController (Gtk.ListBox list_box) {
this.list_box = list_box;
}
private void on_add (Gtk.Widget child) {
length++;
child.destroy.connect (() => {
children.remove (child);
});
}
public List<weak Gtk.Widget> get_children () {
return children.copy ();
}
public void append (Gtk.Widget child) {
children.append (child);
list_box.append (children.last ().data);
on_add (child);
}
public void prepend (Gtk.Widget child) {
children.prepend (child);
list_box.prepend (children.first ().data);
on_add (child);
}
public void insert (Gtk.Widget child, int position) {
children.insert (child, position);
list_box.insert (children.nth (position).data, position);
on_add (child);
}
public void remove (Gtk.Widget child) {
children.remove (child);
list_box.remove (child);
length--;
}
}
namespace SwayNotificationCenter {
static SwayncDaemon swaync_daemon;
// Args
static string ? style_path;
static string ? config_path;
static uint layer_shell_protocol_version = 3;
// Dev args
static bool skip_packaged_css = false;
static Settings self_settings;
public void main (string[] args) {
Gtk.init (ref args);
Hdy.init ();
Gtk.init ();
Adw.init ();
Functions.init ();
self_settings = new Settings ("org.erikreider.swaync");
......@@ -22,6 +23,9 @@ namespace SwayNotificationCenter {
case "--style":
style_path = args[++i];
break;
case "--skip-system-css":
skip_packaged_css = true;
break;
case "-c":
case "--config":
config_path = args[++i];
......@@ -39,13 +43,13 @@ namespace SwayNotificationCenter {
}
}
var app = new Gtk.Application ("org.erikreider.swaync",
ApplicationFlags.DEFAULT_FLAGS);
app.hold ();
app.activate.connect (() => {
ConfigModel.init (config_path);
Functions.load_css (style_path);
if (ConfigModel.instance.layer_shell) {
layer_shell_protocol_version = GtkLayerShell.get_protocol_version ();
}
swaync_daemon = new SwayncDaemon ();
Bus.own_name (BusType.SESSION, "org.erikreider.swaync.cc",
BusNameOwnerFlags.NONE,
......@@ -57,7 +61,10 @@ namespace SwayNotificationCenter {
Process.exit (1);
});
Gtk.main ();
app.add_window (swaync_daemon.noti_daemon.control_center);
});
app.run ();
}
void on_cc_bus_aquired (DBusConnection conn) {
......@@ -78,5 +85,6 @@ namespace SwayNotificationCenter {
print ("Options:\n");
print ("\t -s, --style \t\t Use a custom Stylesheet file\n");
print ("\t -c, --config \t\t Use a custom config file\n");
print ("\t --skip-system-css \t Skip trying to parse the packaged Stylesheet file. Useful for CSS debugging\n");
}
}
......@@ -50,22 +50,22 @@ widget_sources = [
app_sources = [
'main.vala',
'animation/animation.vala',
'orderedHashTable/orderedHashTable.vala',
'iterHelpers/iterBox.vala',
'iterHelpers/iterListBoxController.vala',
'underlay/underlay.vala',
'configModel/configModel.vala',
'swayncDaemon/swayncDaemon.vala',
'notiDaemon/notiDaemon.vala',
'notiModel/notiModel.vala',
'fadedViewport/fadedViewport.vala',
'notificationWindow/notificationWindow.vala',
'dismissableWidget/dismissableWidget.vala',
'notification/notification.vala',
'notificationGroup/notificationGroup.vala',
'controlCenter/controlCenter.vala',
widget_sources,
'blankWindow/blankWindow.vala',
'functions.vala',
'WaylandUtils/WaylandUtils.c',
'WaylandUtils/WaylandUtils.vala',
'xdg_activation.vala',
constants,
]
......@@ -73,20 +73,18 @@ app_sources = [
assert(vala.version() >= '0.56')
app_deps = [
dependency('gtk4-layer-shell-0', version: '>= 1.0.3'),
dependency('gio-2.0', version: '>= 2.50'),
dependency('gio-unix-2.0', version: '>= 2.50'),
dependency('gtk+-3.0', version: '>= 3.22'),
dependency('gtk4', version: '>= 4.16.13'),
dependency('gtk4-wayland', version: '>= 4.16.5'),
dependency('json-glib-1.0', version: '>= 1.0'),
dependency('libhandy-1', version: '>= 1.8.0'),
dependency('granite', version: '>= 6.2.0'),
dependency('gtk-layer-shell-0',
fallback: ['gtk-layer-shell-0', 'gtk-layer-shell'],
version: '>= 0.8.0'
),
dependency('libadwaita-1', version: '>= 1.6.1'),
dependency('granite-7', version: '>= 7.5.0'),
cc.find_library('m', required : true),
vala.find_library('posix'),
dependency('gee-0.8'),
dependency('wayland-client'),
dependency('gee-0.8', version: '>=0.20.6'),
dependency('wayland-client', version: '>=1.23.0'),
protocol_dep,
]
......@@ -113,15 +111,9 @@ if get_option('pulse-audio')
endif
args = [
'--target-glib=2.50',
'--pkg=GtkLayerShell-0.1',
'--target-glib=2.82',
]
app_resources += gnome.compile_resources('sway_notification_center-resources',
'sway_notification_center.gresource.xml',
c_name: 'sway_notification_center'
)
executable('swaync',
[ app_sources, app_resources ],
vala_args: args,
......
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.38.2 -->
<interface>
<requires lib="gtk+" version="3.24"/>
<template class="SwayNotificationCenterNotificationWindow" parent="GtkApplicationWindow">
<property name="can-focus">False</property>
<property name="role">NotificationWindow</property>
<property name="resizable">False</property>
<property name="type-hint">notification</property>
<property name="skip-taskbar-hint">True</property>
<property name="skip-pager-hint">True</property>
<property name="urgency-hint">True</property>
<property name="decorated">False</property>
<property name="deletable">False</property>
<property name="gravity">north-east</property>
<property name="show-menubar">False</property>
<child>
<object class="GtkScrolledWindow" id="scrolled_window">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="hscrollbar-policy">never</property>
<property name="window-placement">top-right</property>
<property name="propagate-natural-height">True</property>
<child>
<object class="GtkViewport" id="viewport">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="vexpand">True</property>
<property name="shadow-type">none</property>
<child>
<object class="GtkBox" id="box">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="orientation">vertical</property>
<child>
<placeholder/>
</child>
</object>
</child>
</object>
</child>
</object>
</child>
<style>
<class name="floating-notifications"/>
</style>
</template>
</interface>
<?xml version="1.0" encoding="UTF-8"?>
<gresources>
<gresource prefix="/org/erikreider/sway-notification-center">
<file>notificationWindow/notificationWindow.ui</file>
<file>notification/notification.ui</file>
<file>controlCenter/controlCenter.ui</file>
<file>controlCenter/widgets/mpris/mpris_player.ui</file>
</gresource>
</gresources>
......@@ -18,17 +18,18 @@ namespace SwayNotificationCenter {
private Array<BlankWindow> blank_windows = new Array<BlankWindow> ();
private unowned Gdk.Display ? display = Gdk.Display.get_default ();
[DBus (visible = false)]
public signal void reloading_css ();
private unowned GLib.ListModel ? monitors = null;
// Only set on swaync start due to some limitations of GtkLayerShell
[DBus (visible = false)]
public bool use_layer_shell { get; private set; }
[DBus (visible = false)]
public bool has_layer_on_demand { get; private set; }
public SwayncDaemon () {
// Init noti_daemon
this.use_layer_shell = ConfigModel.instance.layer_shell;
this.has_layer_on_demand = use_layer_shell && GtkLayerShell.get_protocol_version () >= 4;
this.noti_daemon = new NotiDaemon (this);
this.xdg_activation = new XdgActivationHelper ();
Bus.own_name (BusType.SESSION, "org.freedesktop.Notifications",
......@@ -67,27 +68,13 @@ namespace SwayNotificationCenter {
/// Blank windows
if (display == null) return;
init_blank_windows (false);
display.closed.connect ((is_error) => {
clear_blank_windows ();
if (is_error) stderr.printf ("Display closed due to error!\n");
});
display.opened.connect ((d) => {
bool visibility = noti_daemon.control_center.get_visibility ();
init_blank_windows (visibility);
});
display.monitor_added.connect ((d, m) => {
bool visibility = noti_daemon.control_center.get_visibility ();
add_blank_window (d, m, visibility);
});
display.monitor_removed.connect ((monitor) => {
monitors = display.get_monitors ();
monitors.items_changed.connect (() => {
close_blank_windows ();
bool visibility = noti_daemon.control_center.get_visibility ();
init_blank_windows (visibility);
});
init_blank_windows (false);
}
private void on_noti_bus_aquired (DBusConnection conn) {
......@@ -100,25 +87,20 @@ namespace SwayNotificationCenter {
}
}
private void add_blank_window (Gdk.Display display,
Gdk.Monitor monitor,
bool visible) {
var win = new BlankWindow (display, monitor, this);
win.set_visible (visible);
blank_windows.append_val (win);
}
private void init_blank_windows (bool visible) {
clear_blank_windows ();
close_blank_windows ();
// Add a window to all monitors
for (int i = 0; i < display.get_n_monitors (); i++) {
unowned Gdk.Monitor ? monitor = display.get_monitor (i);
if (monitor == null) continue;
add_blank_window (display, monitor, visible);
for (int i = 0; i < monitors.get_n_items (); i++) {
Object ? obj = monitors.get_item (i);
if (obj == null || !(obj is Gdk.Monitor)) continue;
BlankWindow win = new BlankWindow ((Gdk.Monitor) obj);
win.set_visible (visible);
blank_windows.append_val (win);
}
}
private void clear_blank_windows () {
private void close_blank_windows () {
while (blank_windows.length > 0) {
uint i = blank_windows.length - 1;
unowned BlankWindow ? win = blank_windows.index (i);
......@@ -128,12 +110,14 @@ namespace SwayNotificationCenter {
}
[DBus (visible = false)]
public void show_blank_windows (Gdk.Monitor ? monitor) {
public void show_blank_windows (Gdk.Monitor ? ref_monitor) {
if (!use_layer_shell || !ConfigModel.instance.layer_shell_cover_screen) {
return;
}
foreach (unowned BlankWindow win in blank_windows.data) {
if (win.monitor != monitor) win.show ();
if (win.monitor != ref_monitor) {
win.show ();
}
}
}
......@@ -174,7 +158,6 @@ namespace SwayNotificationCenter {
/** Reloads the CSS file */
public bool reload_css () throws Error {
bool result = Functions.load_css (style_path);
if (result) reloading_css ();
return result;
}
......
public class Underlay : Gtk.Widget {
protected unowned Gtk.Widget _child = null;
public unowned Gtk.Widget child {
get {
return _child;
}
set {
_child = value;
set_children ();
}
}
protected unowned Gtk.Widget _underlay_child = null;
public unowned Gtk.Widget underlay_child {
get {
return _underlay_child;
}
set {
_underlay_child = value;
set_children ();
}
}
private void set_children () {
if (_underlay_child != null) {
_underlay_child.unparent ();
_underlay_child.insert_after (this, null);
}
if (_child != null) {
_child.unparent ();
_child.insert_after (this, _underlay_child);
}
}
/*
* Overrides
*/
protected override Gtk.SizeRequestMode get_request_mode () {
return Gtk.SizeRequestMode.HEIGHT_FOR_WIDTH;
}
protected override void measure (Gtk.Orientation orientation, int for_size,
out int minimum, out int natural,
out int minimum_baseline, out int natural_baseline) {
minimum = 0;
natural = 0;
minimum_baseline = -1;
natural_baseline = -1;
int child_min, child_nat;
if (!child.visible) {
return;
}
child.measure (orientation, for_size,
out child_min, out child_nat, null, null);
minimum = int.max (minimum, child_min);
natural = int.max (natural, child_nat);
}
protected override void size_allocate (int width, int height, int baseline) {
if (child != null && child.should_layout ()) {
child.allocate (width, height, baseline, null);
}
if (underlay_child != null && underlay_child.should_layout ()) {
underlay_child.allocate (width, height, baseline, null);
}
}
}
......@@ -8,7 +8,7 @@ public class XdgActivationHelper : Object {
private Activation * xdg_activation = null;
public XdgActivationHelper () {
unowned Wl.Display wl_display = get_wl_display ();
unowned Wl.Display wl_display = Functions.get_wl_display ();
var wl_registry = wl_display.get_registry ();
wl_registry.add_listener (registry_listener, this);
......@@ -48,13 +48,13 @@ public class XdgActivationHelper : Object {
return null;
}
unowned Wl.Display wl_display = get_wl_display ();
unowned Gdk.Window ? window = widget.get_window ();
if (window == null) {
unowned Wl.Display wl_display = Functions.get_wl_display ();
unowned Gtk.Root ? root = widget.get_root ();
if (root == null) {
warning ("GDK Window is null");
return null;
}
unowned Wl.Surface wl_surface = get_wl_surface (window);
unowned Wl.Surface wl_surface = Functions.get_wl_surface (root.get_surface ());
Value token_value = Value (typeof (string));
token_value.set_string (null);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment