Compare commits
1 Commits
master
...
feat/works
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3fad8c6a16 |
645
Cargo.lock
generated
645
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
42
Cargo.toml
42
Cargo.toml
@@ -63,10 +63,10 @@ workspaces = ["futures-util"]
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
# core
|
# core
|
||||||
gtk = "0.18.1"
|
gtk = "0.17.0"
|
||||||
gtk-layer-shell = "0.8.0"
|
gtk-layer-shell = "0.6.0"
|
||||||
glib = "0.18.5"
|
glib = "0.17.10"
|
||||||
tokio = { version = "1.35.1", features = [
|
tokio = { version = "1.32.0", features = [
|
||||||
"macros",
|
"macros",
|
||||||
"rt-multi-thread",
|
"rt-multi-thread",
|
||||||
"time",
|
"time",
|
||||||
@@ -75,38 +75,38 @@ tokio = { version = "1.35.1", features = [
|
|||||||
"io-util",
|
"io-util",
|
||||||
"net",
|
"net",
|
||||||
] }
|
] }
|
||||||
tracing = "0.1.40"
|
tracing = "0.1.37"
|
||||||
tracing-subscriber = { version = "0.3.17", features = ["env-filter"] }
|
tracing-subscriber = { version = "0.3.17", features = ["env-filter"] }
|
||||||
tracing-error = "0.2.0"
|
tracing-error = "0.2.0"
|
||||||
tracing-appender = "0.2.3"
|
tracing-appender = "0.2.2"
|
||||||
strip-ansi-escapes = "0.2.0"
|
strip-ansi-escapes = "0.2.0"
|
||||||
color-eyre = "0.6.2"
|
color-eyre = "0.6.2"
|
||||||
serde = { version = "1.0.193", features = ["derive"] }
|
serde = { version = "1.0.188", features = ["derive"] }
|
||||||
indexmap = "2.1.0"
|
indexmap = "2.0.0"
|
||||||
dirs = "5.0.1"
|
dirs = "5.0.1"
|
||||||
walkdir = "2.4.0"
|
walkdir = "2.4.0"
|
||||||
notify = { version = "6.1.1", default-features = false }
|
notify = { version = "6.1.1", default-features = false }
|
||||||
wayland-client = "0.31.1"
|
wayland-client = "0.30.2"
|
||||||
wayland-protocols = { version = "0.31.0", features = ["unstable", "client"] }
|
wayland-protocols = { version = "0.30.1", features = ["unstable", "client"] }
|
||||||
wayland-protocols-wlr = { version = "0.2.0", features = ["client"] }
|
wayland-protocols-wlr = { version = "0.1.0", features = ["client"] }
|
||||||
smithay-client-toolkit = { version = "0.18.0", default-features = false, features = [
|
smithay-client-toolkit = { version = "0.17.0", default-features = false, features = [
|
||||||
"calloop",
|
"calloop",
|
||||||
] }
|
] }
|
||||||
universal-config = { version = "0.4.3", default_features = false }
|
universal-config = { version = "0.4.3", default_features = false }
|
||||||
ctrlc = "3.4.2"
|
ctrlc = "3.4.1"
|
||||||
|
|
||||||
lazy_static = "1.4.0"
|
lazy_static = "1.4.0"
|
||||||
async_once = "0.2.6"
|
async_once = "0.2.6"
|
||||||
cfg-if = "1.0.0"
|
cfg-if = "1.0.0"
|
||||||
|
|
||||||
# cli
|
# cli
|
||||||
clap = { version = "4.4.12", optional = true, features = ["derive"] }
|
clap = { version = "4.4.4", optional = true, features = ["derive"] }
|
||||||
|
|
||||||
# ipc
|
# ipc
|
||||||
serde_json = { version = "1.0.109", optional = true }
|
serde_json = { version = "1.0.107", optional = true }
|
||||||
|
|
||||||
# http
|
# http
|
||||||
reqwest = { version = "0.11.23", optional = true }
|
reqwest = { version = "0.11.20", optional = true }
|
||||||
|
|
||||||
# clipboard
|
# clipboard
|
||||||
nix = { version = "0.27.1", optional = true, features = ["event"] }
|
nix = { version = "0.27.1", optional = true, features = ["event"] }
|
||||||
@@ -115,26 +115,26 @@ nix = { version = "0.27.1", optional = true, features = ["event"] }
|
|||||||
chrono = { version = "0.4.31", optional = true, features = ["unstable-locales"] }
|
chrono = { version = "0.4.31", optional = true, features = ["unstable-locales"] }
|
||||||
|
|
||||||
# music
|
# music
|
||||||
mpd_client = { version = "1.3.0", optional = true }
|
mpd_client = { version = "1.2.0", optional = true }
|
||||||
mpris = { version = "2.0.1", optional = true }
|
mpris = { version = "2.0.1", optional = true }
|
||||||
|
|
||||||
# sys_info
|
# sys_info
|
||||||
sysinfo = { version = "0.29.11", optional = true }
|
sysinfo = { version = "0.29.10", optional = true }
|
||||||
|
|
||||||
# tray
|
# tray
|
||||||
system-tray = { version = "0.1.4", optional = true }
|
system-tray = { version = "0.1.4", optional = true }
|
||||||
|
|
||||||
# upower
|
# upower
|
||||||
upower_dbus = { version = "0.3.2", optional = true }
|
upower_dbus = { version = "0.3.2", optional = true }
|
||||||
futures-lite = { version = "2.1.0", optional = true }
|
futures-lite = { version = "1.12.0", optional = true }
|
||||||
zbus = { version = "3.14.1", optional = true }
|
zbus = { version = "3.14.1", optional = true }
|
||||||
|
|
||||||
# workspaces
|
# workspaces
|
||||||
swayipc-async = { version = "2.0.1", optional = true }
|
swayipc-async = { version = "2.0.1", optional = true }
|
||||||
hyprland = { version = "0.3.12", features = ["silent"], optional = true }
|
hyprland = { version = "0.3.12", features = ["silent"], optional = true }
|
||||||
futures-util = { version = "0.3.30", optional = true }
|
futures-util = { version = "0.3.21", optional = true }
|
||||||
|
|
||||||
# shared
|
# shared
|
||||||
regex = { version = "1.10.2", default-features = false, features = [
|
regex = { version = "1.9.5", default-features = false, features = [
|
||||||
"std",
|
"std",
|
||||||
], optional = true } # music, sys_info
|
], optional = true } # music, sys_info
|
||||||
@@ -18,8 +18,6 @@ You also need rust; only the latest stable version is supported.
|
|||||||
|
|
||||||
```shell
|
```shell
|
||||||
pacman -S gtk3 gtk-layer-shell
|
pacman -S gtk3 gtk-layer-shell
|
||||||
# for http support
|
|
||||||
pacman -S openssl
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Ubuntu/Debian
|
### Ubuntu/Debian
|
||||||
@@ -33,9 +31,7 @@ apt install libssl-dev
|
|||||||
### Fedora
|
### Fedora
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
dnf install gtk3-devel gtk-layer-shell-devel
|
dnf install gtk3 gtk-layer-shell
|
||||||
# for http support
|
|
||||||
dnf install openssl-devel
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|||||||
@@ -95,11 +95,7 @@ Create a map/object called `monitors` inside the top-level object.
|
|||||||
Each of the map's keys should be an output name,
|
Each of the map's keys should be an output name,
|
||||||
and each value should be an object containing the bar config.
|
and each value should be an object containing the bar config.
|
||||||
|
|
||||||
You can still define a top-level "default" config to use for unspecified monitors.
|
To find your output names, run `wayland-info | grep wl_output -A1`.
|
||||||
Alternatively, leave the top-level `start`, `center` and `end` keys null to hide bars on unspecified monitors.
|
|
||||||
|
|
||||||
> [!TIP]
|
|
||||||
> To find your output names, run `wayland-info | grep wl_output -A1`.
|
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
<summary>JSON</summary>
|
<summary>JSON</summary>
|
||||||
@@ -271,24 +267,22 @@ Check [here](config) for an example config file for a fully configured bar in ea
|
|||||||
|
|
||||||
The following table lists each of the top-level bar config options:
|
The following table lists each of the top-level bar config options:
|
||||||
|
|
||||||
| Name | Type | Default | Description |
|
| Name | Type | Default | Description |
|
||||||
|--------------------|----------------------------------------|--------------------------------------|----------------------------------------------------------------------------------------------------------------------------|
|
|--------------------|----------------------------------------|-----------|-----------------------------------------------------------------------------------------------------------------|
|
||||||
| `name` | `string` | `bar-<n>` | A unique identifier for the bar, used for controlling it over IPC. If not set, uses a generated integer suffix. |
|
| `name` | `string` | `bar-<n>` | A unique identifier for the bar, used for controlling it over IPC. If not set, uses a generated integer suffix. |
|
||||||
| `position` | `top` or `bottom` or `left` or `right` | `bottom` | The bar's position on screen. |
|
| `position` | `top` or `bottom` or `left` or `right` | `bottom` | The bar's position on screen. |
|
||||||
| `anchor_to_edges` | `boolean` | `false` | Whether to anchor the bar to the edges of the screen. Setting to false centres the bar. |
|
| `anchor_to_edges` | `boolean` | `false` | Whether to anchor the bar to the edges of the screen. Setting to false centres the bar. |
|
||||||
| `height` | `integer` | `42` | The bar's height in pixels. |
|
| `height` | `integer` | `42` | The bar's height in pixels. |
|
||||||
| `popup_gap` | `integer` | `5` | The gap between the bar and popup window. |
|
| `popup_gap` | `integer` | `5` | The gap between the bar and popup window. |
|
||||||
| `margin.top` | `integer` | `0` | The margin on the top of the bar |
|
| `margin.top` | `integer` | `0` | The margin on the top of the bar |
|
||||||
| `margin.bottom` | `integer` | `0` | The margin on the bottom of the bar |
|
| `margin.bottom` | `integer` | `0` | The margin on the bottom of the bar |
|
||||||
| `margin.left` | `integer` | `0` | The margin on the left of the bar |
|
| `margin.left` | `integer` | `0` | The margin on the left of the bar |
|
||||||
| `margin.right` | `integer` | `0` | The margin on the right of the bar |
|
| `margin.right` | `integer` | `0` | The margin on the right of the bar |
|
||||||
| `icon_theme` | `string` | `null` | Name of the GTK icon theme to use. Leave blank to use default. |
|
| `icon_theme` | `string` | `null` | Name of the GTK icon theme to use. Leave blank to use default. |
|
||||||
| `ironvar_defaults` | `Map<string, string>` | `{}` | Map of [ironvar](ironvars) keys against their default values. |
|
| `ironvar_defaults` | `Map<string, string>` | `{}` | Map of [ironvar](ironvars) keys against their default values. |
|
||||||
| `start_hidden` | `boolean` | `false`, or `true` if `autohide` set | Whether the bar should be hidden when the application starts. Enabled by default when `autohide` is set. |
|
| `start` | `Module[]` | `[]` | Array of left or top modules. |
|
||||||
| `autohide` | `integer` | `null` | The duration in milliseconds before the bar is hidden after the cursor leaves. Leave unset to disable auto-hide behaviour. |
|
| `center` | `Module[]` | `[]` | Array of center modules. |
|
||||||
| `start` | `Module[]` | `[]` | Array of left or top modules. |
|
| `end` | `Module[]` | `[]` | Array of right or bottom modules. |
|
||||||
| `center` | `Module[]` | `[]` | Array of center modules. |
|
|
||||||
| `end` | `Module[]` | `[]` | Array of right or bottom modules. |
|
|
||||||
|
|
||||||
### 3.2 Module-level options
|
### 3.2 Module-level options
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
In some configuration locations, Ironbar supports dynamic values,
|
In some configuration locations, Ironbar supports dynamic values,
|
||||||
meaning you can inject content into the bar from an external source.
|
meaning you can inject content into the bar from an external source.
|
||||||
|
|
||||||
Currently two dynamic content sources are supported - [scripts](scripts) (via shorthand syntax) and [ironvars](ironvars).
|
Currently two dynamic content sources are supported - scripts and ironvars.
|
||||||
|
|
||||||
## Dynamic String
|
## Dynamic String
|
||||||
|
|
||||||
|
|||||||
@@ -22,22 +22,15 @@ Information on styling individual modules can be found on their pages in the sid
|
|||||||
| `.widget` | Any widget. |
|
| `.widget` | Any widget. |
|
||||||
| `.popup` | Any popup box. |
|
| `.popup` | Any popup box. |
|
||||||
|
|
||||||
Every Ironbar widget can be selected using a `kebab-case` class name matching its name.
|
Every widget can be selected using a `kebab-case` class name matching its name.
|
||||||
You can also target popups by prefixing `popup-` to the name. For example, you can use `.clock` and `.popup-clock` respectively.
|
You can also target popups by prefixing `popup-` to the name. For example, you can use `.clock` and `.popup-clock` respectively.
|
||||||
|
|
||||||
Setting the `name` option on a widget allows you to target that specific instance using `#name`.
|
Setting the `name` option on a widget allows you to target that specific instance using `#name`.
|
||||||
You can also add additional classes to re-use styles. In both cases, `popup-` is automatically prefixed to the popup (`#popup-name` or `.popup-my-class`).
|
You can also add additional classes to re-use styles. In both cases, `popup-` is automatically prefixed to the popup (`#popup-name` or `.popup-my-class`).
|
||||||
|
|
||||||
You can also target all GTK widgets of a certain type directly using their name. For example, `label` will select all labels, and `button:hover` will select the hover state on *all* buttons.
|
You can also target all GTK widgets of a certain type directly using their name. For example, `button:hover` will select the hover state on *all* buttons.
|
||||||
These names are all lower case with no separator, so `MenuBar` -> `menubar`.
|
These names are all lower case with no separator, so `MenuBar` -> `menubar`.
|
||||||
|
|
||||||
> [!NOTE]
|
|
||||||
> If an entry takes no effect you might have to use a more specific selector.
|
|
||||||
> For example, attempting to set text size on `.popup-clipboard .item` will likely have no effect.
|
|
||||||
> Instead, you can target the more specific `.popup-clipboard .item label`.
|
|
||||||
|
|
||||||
Running `ironbar inspect` can be used to find out how to address an element.
|
|
||||||
|
|
||||||
GTK CSS does not support custom properties, but it does have its own custom `@define-color` syntax which you can use for re-using colours:
|
GTK CSS does not support custom properties, but it does have its own custom `@define-color` syntax which you can use for re-using colours:
|
||||||
|
|
||||||
```css
|
```css
|
||||||
|
|||||||
@@ -21,12 +21,12 @@ in MPRIS mode, the widget will listen to all players and automatically detect/di
|
|||||||
| `truncate.max_length` | `integer` | `null` | The maximum number of characters before truncating. Leave blank to let GTK automatically handle. |
|
| `truncate.max_length` | `integer` | `null` | The maximum number of characters before truncating. Leave blank to let GTK automatically handle. |
|
||||||
| `icons.play` | `string` or [image](images) | `` | Icon to show when playing. |
|
| `icons.play` | `string` or [image](images) | `` | Icon to show when playing. |
|
||||||
| `icons.pause` | `string` or [image](images) | `` | Icon to show when paused. |
|
| `icons.pause` | `string` or [image](images) | `` | Icon to show when paused. |
|
||||||
| `icons.prev` | `string` or [image](images) | `` | Icon to show on previous button. |
|
| `icons.prev` | `string` or [image](images) | `玲` | Icon to show on previous button. |
|
||||||
| `icons.next` | `string` or [image](images) | `` | Icon to show on next button. |
|
| `icons.next` | `string` or [image](images) | `怜` | Icon to show on next button. |
|
||||||
| `icons.volume` | `string` or [image](images) | `` | Icon to show under popup volume slider. |
|
| `icons.volume` | `string` or [image](images) | `墳` | Icon to show under popup volume slider. |
|
||||||
| `icons.track` | `string` or [image](images) | `` | Icon to show next to track title. |
|
| `icons.track` | `string` or [image](images) | `` | Icon to show next to track title. |
|
||||||
| `icons.album` | `string` or [image](images) | `` | Icon to show next to album name. |
|
| `icons.album` | `string` or [image](images) | `` | Icon to show next to album name. |
|
||||||
| `icons.artist` | `string` or [image](images) | `` | Icon to show next to artist name. |
|
| `icons.artist` | `string` or [image](images) | `ﴁ` | Icon to show next to artist name. |
|
||||||
| `show_status_icon` | `boolean` | `true` | Whether to show the play/pause icon on the widget. |
|
| `show_status_icon` | `boolean` | `true` | Whether to show the play/pause icon on the widget. |
|
||||||
| `icon_size` | `integer` | `32` | Size to render icon at (image icons only). |
|
| `icon_size` | `integer` | `32` | Size to render icon at (image icons only). |
|
||||||
| `cover_image_size` | `integer` | `128` | Size to render album art image at inside popup. |
|
| `cover_image_size` | `integer` | `128` | Size to render album art image at inside popup. |
|
||||||
|
|||||||
@@ -28,13 +28,13 @@ Pango markup is supported.
|
|||||||
"end": [
|
"end": [
|
||||||
{
|
{
|
||||||
"format": [
|
"format": [
|
||||||
" {cpu_percent}% | {temp_c:k10temp-Tccd1}°C",
|
" {cpu_percent}% | {temp_c:k10temp_Tccd1}°C",
|
||||||
" {memory_used} / {memory_total} GB ({memory_percent}%)",
|
" {memory_used} / {memory_total} GB ({memory_percent}%)",
|
||||||
"| {swap_used} / {swap_total} GB ({swap_percent}%)",
|
"| {swap_used} / {swap_total} GB ({swap_percent}%)",
|
||||||
" {disk_used:/} / {disk_total:/} GB ({disk_percent:/}%)",
|
" {disk_used:/} / {disk_total:/} GB ({disk_percent:/}%)",
|
||||||
" {net_down:enp39s0} / {net_up:enp39s0} Mbps",
|
"李 {net_down:enp39s0} / {net_up:enp39s0} Mbps",
|
||||||
" {load_average:1} | {load_average:5} | {load_average:15}",
|
"猪 {load_average:1} | {load_average:5} | {load_average:15}",
|
||||||
" {uptime}"
|
" {uptime}"
|
||||||
],
|
],
|
||||||
"interval": {
|
"interval": {
|
||||||
"cpu": 1,
|
"cpu": 1,
|
||||||
@@ -58,13 +58,13 @@ Pango markup is supported.
|
|||||||
[[end]]
|
[[end]]
|
||||||
type = 'sys_info'
|
type = 'sys_info'
|
||||||
format = [
|
format = [
|
||||||
' {cpu_percent}% | {temp_c:k10temp-Tccd1}°C',
|
' {cpu_percent}% | {temp_c:k10temp_Tccd1}°C',
|
||||||
' {memory_used} / {memory_total} GB ({memory_percent}%)',
|
' {memory_used} / {memory_total} GB ({memory_percent}%)',
|
||||||
'| {swap_used} / {swap_total} GB ({swap_percent}%)',
|
'| {swap_used} / {swap_total} GB ({swap_percent}%)',
|
||||||
' {disk_used:/} / {disk_total:/} GB ({disk_percent:/}%)',
|
' {disk_used:/} / {disk_total:/} GB ({disk_percent:/}%)',
|
||||||
' {net_down:enp39s0} / {net_up:enp39s0} Mbps',
|
'李 {net_down:enp39s0} / {net_up:enp39s0} Mbps',
|
||||||
' {load_average:1} | {load_average:5} | {load_average:15}',
|
'猪 {load_average:1} | {load_average:5} | {load_average:15}',
|
||||||
' {uptime}',
|
' {uptime}',
|
||||||
]
|
]
|
||||||
|
|
||||||
[end.interval]
|
[end.interval]
|
||||||
@@ -85,13 +85,13 @@ temps = 5
|
|||||||
```yaml
|
```yaml
|
||||||
end:
|
end:
|
||||||
- format:
|
- format:
|
||||||
- ' {cpu_percent}% | {temp_c:k10temp-Tccd1}°C'
|
- ' {cpu_percent}% | {temp_c:k10temp_Tccd1}°C'
|
||||||
- ' {memory_used} / {memory_total} GB ({memory_percent}%)'
|
- ' {memory_used} / {memory_total} GB ({memory_percent}%)'
|
||||||
- '| {swap_used} / {swap_total} GB ({swap_percent}%)'
|
- '| {swap_used} / {swap_total} GB ({swap_percent}%)'
|
||||||
- ' {disk_used:/} / {disk_total:/} GB ({disk_percent:/}%)'
|
- ' {disk_used:/} / {disk_total:/} GB ({disk_percent:/}%)'
|
||||||
- ' {net_down:enp39s0} / {net_up:enp39s0} Mbps'
|
- '李 {net_down:enp39s0} / {net_up:enp39s0} Mbps'
|
||||||
- ' {load_average:1} | {load_average:5} | {load_average:15}'
|
- '猪 {load_average:1} | {load_average:5} | {load_average:15}'
|
||||||
- ' {uptime}'
|
- ' {uptime}'
|
||||||
interval:
|
interval:
|
||||||
cpu: 1
|
cpu: 1
|
||||||
disks: 300
|
disks: 300
|
||||||
@@ -119,13 +119,13 @@ end:
|
|||||||
interval.networks = 3
|
interval.networks = 3
|
||||||
|
|
||||||
format = [
|
format = [
|
||||||
" {cpu_percent}% | {temp_c:k10temp-Tccd1}°C"
|
" {cpu_percent}% | {temp_c:k10temp_Tccd1}°C"
|
||||||
" {memory_used} / {memory_total} GB ({memory_percent}%)"
|
" {memory_used} / {memory_total} GB ({memory_percent}%)"
|
||||||
"| {swap_used} / {swap_total} GB ({swap_percent}%)"
|
"| {swap_used} / {swap_total} GB ({swap_percent}%)"
|
||||||
" {disk_used:/} / {disk_total:/} GB ({disk_percent:/}%)"
|
" {disk_used:/} / {disk_total:/} GB ({disk_percent:/}%)"
|
||||||
" {net_down:enp39s0} / {net_up:enp39s0} Mbps"
|
"李 {net_down:enp39s0} / {net_up:enp39s0} Mbps"
|
||||||
" {load_average:1} | {load_average:5} | {load_average:15}"
|
"猪 {load_average:1} | {load_average:5} | {load_average:15}"
|
||||||
" {uptime}"
|
" {uptime}"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -168,7 +168,7 @@ The following tokens can be used in the `format` configuration option:
|
|||||||
| `{load_average:15}` | 15-minute load average. |
|
| `{load_average:15}` | 15-minute load average. |
|
||||||
| `{uptime}` | System uptime formatted as `HH:mm`. |
|
| `{uptime}` | System uptime formatted as `HH:mm`. |
|
||||||
|
|
||||||
For Intel CPUs, you can typically use `coretemp-Package-id-0` for the temperature sensor. For AMD, you can use `k10temp-Tccd1`.
|
For Intel CPUs, you can typically use `coretemp-Package-id-0` for the temperature sensor. For AMD, you can use `k10temp_Tccd1`.
|
||||||
|
|
||||||
## Styling
|
## Styling
|
||||||
|
|
||||||
|
|||||||
@@ -46,10 +46,10 @@ let {
|
|||||||
" {cpu_percent}% | {temp_c:k10temp_Tccd1}°C"
|
" {cpu_percent}% | {temp_c:k10temp_Tccd1}°C"
|
||||||
" {memory_used} / {memory_total} GB ({memory_percent}%)"
|
" {memory_used} / {memory_total} GB ({memory_percent}%)"
|
||||||
"| {swap_used} / {swap_total} GB ({swap_percent}%)"
|
"| {swap_used} / {swap_total} GB ({swap_percent}%)"
|
||||||
" {disk_used:/} / {disk_total:/} GB ({disk_percent:/}%)"
|
" {disk_used:/} / {disk_total:/} GB ({disk_percent:/}%)"
|
||||||
" {net_down:enp39s0} / {net_up:enp39s0} Mbps"
|
"李 {net_down:enp39s0} / {net_up:enp39s0} Mbps"
|
||||||
" {load_average:1} | {load_average:5} | {load_average:15}"
|
"猪 {load_average:1} | {load_average:5} | {load_average:15}"
|
||||||
" {uptime}"
|
" {uptime}"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -29,10 +29,10 @@
|
|||||||
" {cpu_percent}% | {temp_c:k10temp_Tccd1}°C",
|
" {cpu_percent}% | {temp_c:k10temp_Tccd1}°C",
|
||||||
" {memory_used} / {memory_total} GB ({memory_percent}%)",
|
" {memory_used} / {memory_total} GB ({memory_percent}%)",
|
||||||
"| {swap_used} / {swap_total} GB ({swap_percent}%)",
|
"| {swap_used} / {swap_total} GB ({swap_percent}%)",
|
||||||
" {disk_used:/} / {disk_total:/} GB ({disk_percent:/}%)",
|
" {disk_used:/} / {disk_total:/} GB ({disk_percent:/}%)",
|
||||||
" {net_down:enp39s0} / {net_up:enp39s0} Mbps",
|
"李 {net_down:enp39s0} / {net_up:enp39s0} Mbps",
|
||||||
" {load_average:1} | {load_average:5} | {load_average:15}",
|
"猪 {load_average:1} | {load_average:5} | {load_average:15}",
|
||||||
" {uptime}"
|
" {uptime}"
|
||||||
],
|
],
|
||||||
"interval": {
|
"interval": {
|
||||||
"cpu": 1,
|
"cpu": 1,
|
||||||
|
|||||||
@@ -30,10 +30,10 @@ format = [
|
|||||||
" {cpu_percent}% | {temp_c:k10temp_Tccd1}°C",
|
" {cpu_percent}% | {temp_c:k10temp_Tccd1}°C",
|
||||||
" {memory_used} / {memory_total} GB ({memory_percent}%)",
|
" {memory_used} / {memory_total} GB ({memory_percent}%)",
|
||||||
"| {swap_used} / {swap_total} GB ({swap_percent}%)",
|
"| {swap_used} / {swap_total} GB ({swap_percent}%)",
|
||||||
" {disk_used:/} / {disk_total:/} GB ({disk_percent:/}%)",
|
" {disk_used:/} / {disk_total:/} GB ({disk_percent:/}%)",
|
||||||
" {net_down:enp39s0} / {net_up:enp39s0} Mbps",
|
"李 {net_down:enp39s0} / {net_up:enp39s0} Mbps",
|
||||||
" {load_average:1} | {load_average:5} | {load_average:15}",
|
"猪 {load_average:1} | {load_average:5} | {load_average:15}",
|
||||||
" {uptime}",
|
" {uptime}",
|
||||||
]
|
]
|
||||||
type = "sys_info"
|
type = "sys_info"
|
||||||
|
|
||||||
|
|||||||
@@ -19,10 +19,10 @@ end:
|
|||||||
- {cpu_percent}% | {temp_c:k10temp_Tccd1}°C
|
- {cpu_percent}% | {temp_c:k10temp_Tccd1}°C
|
||||||
- {memory_used} / {memory_total} GB ({memory_percent}%)
|
- {memory_used} / {memory_total} GB ({memory_percent}%)
|
||||||
- '| {swap_used} / {swap_total} GB ({swap_percent}%)'
|
- '| {swap_used} / {swap_total} GB ({swap_percent}%)'
|
||||||
- {disk_used:/} / {disk_total:/} GB ({disk_percent:/}%)
|
- {disk_used:/} / {disk_total:/} GB ({disk_percent:/}%)
|
||||||
- {net_down:enp39s0} / {net_up:enp39s0} Mbps
|
- 李 {net_down:enp39s0} / {net_up:enp39s0} Mbps
|
||||||
- {load_average:1} | {load_average:5} | {load_average:15}
|
- 猪 {load_average:1} | {load_average:5} | {load_average:15}
|
||||||
- {uptime}
|
- {uptime}
|
||||||
interval:
|
interval:
|
||||||
cpu: 1
|
cpu: 1
|
||||||
disks: 300
|
disks: 300
|
||||||
|
|||||||
113
flake.lock
generated
113
flake.lock
generated
@@ -2,16 +2,19 @@
|
|||||||
"nodes": {
|
"nodes": {
|
||||||
"crane": {
|
"crane": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
|
"flake-compat": "flake-compat",
|
||||||
|
"flake-utils": "flake-utils",
|
||||||
"nixpkgs": [
|
"nixpkgs": [
|
||||||
"nixpkgs"
|
"nixpkgs"
|
||||||
]
|
],
|
||||||
|
"rust-overlay": "rust-overlay"
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1703439018,
|
"lastModified": 1693439040,
|
||||||
"narHash": "sha256-VT+06ft/x3eMZ1MJxWzQP3zXFGcrxGo5VR2rB7t88hs=",
|
"narHash": "sha256-t2nOxBcP0Q/XJt6Ild4v0hJ49OSl9F3nE1cdIT4xsDg=",
|
||||||
"owner": "ipetkov",
|
"owner": "ipetkov",
|
||||||
"repo": "crane",
|
"repo": "crane",
|
||||||
"rev": "afdcd41180e3dfe4dac46b5ee396e3b12ccc967a",
|
"rev": "174604795d316b75777e28185c3a4918bc69b399",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -20,10 +23,44 @@
|
|||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"flake-compat": {
|
||||||
|
"flake": false,
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1673956053,
|
||||||
|
"narHash": "sha256-4gtG9iQuiKITOjNQQeQIpoIB6b16fm+504Ch3sNKLd8=",
|
||||||
|
"owner": "edolstra",
|
||||||
|
"repo": "flake-compat",
|
||||||
|
"rev": "35bb57c0c8d8b62bbfd284272c928ceb64ddbde9",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "edolstra",
|
||||||
|
"repo": "flake-compat",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
"flake-utils": {
|
"flake-utils": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"systems": "systems"
|
"systems": "systems"
|
||||||
},
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1689068808,
|
||||||
|
"narHash": "sha256-6ixXo3wt24N/melDWjq70UuHQLxGV8jZvooRanIHXw0=",
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "flake-utils",
|
||||||
|
"rev": "919d646de7be200f3bf08cb76ae1f09402b6f9b4",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "flake-utils",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"flake-utils_2": {
|
||||||
|
"inputs": {
|
||||||
|
"systems": "systems_2"
|
||||||
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1681202837,
|
"lastModified": 1681202837,
|
||||||
"narHash": "sha256-H+Rh19JDwRtpVPAWp64F+rlEtxUWBAQW28eAi3SRSzg=",
|
"narHash": "sha256-H+Rh19JDwRtpVPAWp64F+rlEtxUWBAQW28eAi3SRSzg=",
|
||||||
@@ -43,11 +80,11 @@
|
|||||||
"nixpkgs": "nixpkgs"
|
"nixpkgs": "nixpkgs"
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1698420672,
|
"lastModified": 1692351612,
|
||||||
"narHash": "sha256-/TdeHMPRjjdJub7p7+w55vyABrsJlt5QkznPYy55vKA=",
|
"narHash": "sha256-KTGonidcdaLadRnv9KFgwSMh1ZbXoR/OBmPjeNMhFwU=",
|
||||||
"owner": "nix-community",
|
"owner": "nix-community",
|
||||||
"repo": "naersk",
|
"repo": "naersk",
|
||||||
"rev": "aeb58d5e8faead8980a807c840232697982d47b9",
|
"rev": "78789c30d64dea2396c9da516bbcc8db3a475207",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -58,11 +95,11 @@
|
|||||||
},
|
},
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1704008649,
|
"lastModified": 1693355128,
|
||||||
"narHash": "sha256-rGPSWjXTXTurQN9beuHdyJhB8O761w1Zc5BqSSmHvoM=",
|
"narHash": "sha256-+ZoAny3ZxLcfMaUoLVgL9Ywb/57wP+EtsdNGuXUJrwg=",
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "d44d59d2b5bd694cd9d996fd8c51d03e3e9ba7f7",
|
"rev": "a63a64b593dcf2fe05f7c5d666eb395950f36bc9",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -72,11 +109,11 @@
|
|||||||
},
|
},
|
||||||
"nixpkgs_2": {
|
"nixpkgs_2": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1703637592,
|
"lastModified": 1693377291,
|
||||||
"narHash": "sha256-8MXjxU0RfFfzl57Zy3OfXCITS0qWDNLzlBAdwxGZwfY=",
|
"narHash": "sha256-vYGY9bnqEeIncNarDZYhm6KdLKgXMS+HA2mTRaWEc80=",
|
||||||
"owner": "nixos",
|
"owner": "nixos",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "cfc3698c31b1fb9cdcf10f36c9643460264d0ca8",
|
"rev": "e7f38be3775bab9659575f192ece011c033655f0",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -91,22 +128,47 @@
|
|||||||
"crane": "crane",
|
"crane": "crane",
|
||||||
"naersk": "naersk",
|
"naersk": "naersk",
|
||||||
"nixpkgs": "nixpkgs_2",
|
"nixpkgs": "nixpkgs_2",
|
||||||
"rust-overlay": "rust-overlay"
|
"rust-overlay": "rust-overlay_2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"rust-overlay": {
|
"rust-overlay": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"flake-utils": "flake-utils",
|
"flake-utils": [
|
||||||
|
"crane",
|
||||||
|
"flake-utils"
|
||||||
|
],
|
||||||
|
"nixpkgs": [
|
||||||
|
"crane",
|
||||||
|
"nixpkgs"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1691374719,
|
||||||
|
"narHash": "sha256-HCodqnx1Mi2vN4f3hjRPc7+lSQy18vRn8xWW68GeQOg=",
|
||||||
|
"owner": "oxalica",
|
||||||
|
"repo": "rust-overlay",
|
||||||
|
"rev": "b520a3889b24aaf909e287d19d406862ced9ffc9",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "oxalica",
|
||||||
|
"repo": "rust-overlay",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"rust-overlay_2": {
|
||||||
|
"inputs": {
|
||||||
|
"flake-utils": "flake-utils_2",
|
||||||
"nixpkgs": [
|
"nixpkgs": [
|
||||||
"nixpkgs"
|
"nixpkgs"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1703902408,
|
"lastModified": 1693447852,
|
||||||
"narHash": "sha256-qXdWvu+tlgNjeoz8yQMRKSom6QyRROfgpmeOhwbujqw=",
|
"narHash": "sha256-K9npbs4S6+r51vpiElJi+0vwbAeftCAcOGbot/PCBnQ=",
|
||||||
"owner": "oxalica",
|
"owner": "oxalica",
|
||||||
"repo": "rust-overlay",
|
"repo": "rust-overlay",
|
||||||
"rev": "319f57cd2c34348c55970a4bf2b35afe82088681",
|
"rev": "40e851593ef4f9f8cd0b69c8cae7b722b9953a23",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -129,6 +191,21 @@
|
|||||||
"repo": "default",
|
"repo": "default",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"systems_2": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1681028828,
|
||||||
|
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||||
|
"owner": "nix-systems",
|
||||||
|
"repo": "default",
|
||||||
|
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "nix-systems",
|
||||||
|
"repo": "default",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"root": "root",
|
"root": "root",
|
||||||
|
|||||||
@@ -193,8 +193,8 @@
|
|||||||
ExecStart = "${pkg}/bin/ironbar";
|
ExecStart = "${pkg}/bin/ironbar";
|
||||||
};
|
};
|
||||||
Install.WantedBy = [
|
Install.WantedBy = [
|
||||||
(lib.mkIf config.wayland.windowManager.hyprland.systemd.enable "hyprland-session.target")
|
(lib.mkIf config.wayland.windowManager.hyprland.systemdIntegration "hyprland-session.target")
|
||||||
(lib.mkIf config.wayland.windowManager.sway.systemd.enable "sway-session.target")
|
(lib.mkIf config.wayland.windowManager.sway.systemdIntegration "sway-session.target")
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
476
src/bar.rs
476
src/bar.rs
@@ -3,311 +3,139 @@ use crate::modules::{
|
|||||||
create_module, set_widget_identifiers, wrap_widget, ModuleInfo, ModuleLocation,
|
create_module, set_widget_identifiers, wrap_widget, ModuleInfo, ModuleLocation,
|
||||||
};
|
};
|
||||||
use crate::popup::Popup;
|
use crate::popup::Popup;
|
||||||
use crate::{Config, Ironbar};
|
use crate::unique_id::get_unique_usize;
|
||||||
|
use crate::{Config, GlobalState};
|
||||||
use color_eyre::Result;
|
use color_eyre::Result;
|
||||||
use glib::Propagation;
|
|
||||||
use gtk::gdk::Monitor;
|
use gtk::gdk::Monitor;
|
||||||
use gtk::prelude::*;
|
use gtk::prelude::*;
|
||||||
use gtk::{Application, ApplicationWindow, IconTheme, Orientation, Window, WindowType};
|
use gtk::{Application, ApplicationWindow, IconTheme, Orientation};
|
||||||
use gtk_layer_shell::LayerShell;
|
|
||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
use std::time::Duration;
|
|
||||||
use tracing::{debug, info};
|
use tracing::{debug, info};
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
/// Creates a new window for a bar,
|
||||||
enum Inner {
|
/// sets it up and adds its widgets.
|
||||||
New { config: Option<Config> },
|
pub fn create_bar(
|
||||||
Loaded { popup: Rc<RefCell<Popup>> },
|
app: &Application,
|
||||||
|
monitor: &Monitor,
|
||||||
|
monitor_name: &str,
|
||||||
|
config: Config,
|
||||||
|
global_state: &Rc<RefCell<GlobalState>>,
|
||||||
|
) -> Result<()> {
|
||||||
|
let win = ApplicationWindow::builder().application(app).build();
|
||||||
|
let bar_name = config
|
||||||
|
.name
|
||||||
|
.clone()
|
||||||
|
.unwrap_or_else(|| format!("bar-{}", get_unique_usize()));
|
||||||
|
|
||||||
|
win.set_widget_name(&bar_name);
|
||||||
|
info!("Creating bar {}", bar_name);
|
||||||
|
|
||||||
|
setup_layer_shell(
|
||||||
|
&win,
|
||||||
|
monitor,
|
||||||
|
config.position,
|
||||||
|
config.anchor_to_edges,
|
||||||
|
config.margin,
|
||||||
|
);
|
||||||
|
|
||||||
|
let orientation = config.position.get_orientation();
|
||||||
|
|
||||||
|
let content = gtk::Box::builder()
|
||||||
|
.orientation(orientation)
|
||||||
|
.spacing(0)
|
||||||
|
.hexpand(false)
|
||||||
|
.name("bar");
|
||||||
|
|
||||||
|
let content = if orientation == Orientation::Horizontal {
|
||||||
|
content.height_request(config.height)
|
||||||
|
} else {
|
||||||
|
content.width_request(config.height)
|
||||||
|
}
|
||||||
|
.build();
|
||||||
|
|
||||||
|
content.style_context().add_class("container");
|
||||||
|
|
||||||
|
let start = create_container("start", orientation);
|
||||||
|
let center = create_container("center", orientation);
|
||||||
|
let end = create_container("end", orientation);
|
||||||
|
|
||||||
|
content.add(&start);
|
||||||
|
content.set_center_widget(Some(¢er));
|
||||||
|
content.pack_end(&end, false, false, 0);
|
||||||
|
|
||||||
|
let load_result = load_modules(&start, ¢er, &end, app, config, monitor, monitor_name)?;
|
||||||
|
global_state
|
||||||
|
.borrow_mut()
|
||||||
|
.popups_mut()
|
||||||
|
.insert(bar_name.into(), load_result.popup);
|
||||||
|
|
||||||
|
win.add(&content);
|
||||||
|
|
||||||
|
win.connect_destroy_event(|_, _| {
|
||||||
|
info!("Shutting down");
|
||||||
|
gtk::main_quit();
|
||||||
|
Inhibit(false)
|
||||||
|
});
|
||||||
|
|
||||||
|
debug!("Showing bar");
|
||||||
|
|
||||||
|
// show each box but do not use `show_all`.
|
||||||
|
// this ensures `show_if` option works as intended.
|
||||||
|
start.show();
|
||||||
|
center.show();
|
||||||
|
end.show();
|
||||||
|
content.show();
|
||||||
|
win.show();
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
/// Sets up GTK layer shell for a provided application window.
|
||||||
pub struct Bar {
|
fn setup_layer_shell(
|
||||||
name: String,
|
win: &ApplicationWindow,
|
||||||
monitor_name: String,
|
monitor: &Monitor,
|
||||||
position: BarPosition,
|
position: BarPosition,
|
||||||
|
anchor_to_edges: bool,
|
||||||
|
margin: MarginConfig,
|
||||||
|
) {
|
||||||
|
gtk_layer_shell::init_for_window(win);
|
||||||
|
gtk_layer_shell::set_monitor(win, monitor);
|
||||||
|
gtk_layer_shell::set_layer(win, gtk_layer_shell::Layer::Top);
|
||||||
|
gtk_layer_shell::auto_exclusive_zone_enable(win);
|
||||||
|
gtk_layer_shell::set_namespace(win, env!("CARGO_PKG_NAME"));
|
||||||
|
|
||||||
window: ApplicationWindow,
|
gtk_layer_shell::set_margin(win, gtk_layer_shell::Edge::Top, margin.top);
|
||||||
|
gtk_layer_shell::set_margin(win, gtk_layer_shell::Edge::Bottom, margin.bottom);
|
||||||
|
gtk_layer_shell::set_margin(win, gtk_layer_shell::Edge::Left, margin.left);
|
||||||
|
gtk_layer_shell::set_margin(win, gtk_layer_shell::Edge::Right, margin.right);
|
||||||
|
|
||||||
content: gtk::Box,
|
let bar_orientation = position.get_orientation();
|
||||||
|
|
||||||
start: gtk::Box,
|
gtk_layer_shell::set_anchor(
|
||||||
center: gtk::Box,
|
win,
|
||||||
end: gtk::Box,
|
gtk_layer_shell::Edge::Top,
|
||||||
|
position == BarPosition::Top
|
||||||
inner: Inner,
|
|| (bar_orientation == Orientation::Vertical && anchor_to_edges),
|
||||||
}
|
);
|
||||||
|
gtk_layer_shell::set_anchor(
|
||||||
impl Bar {
|
win,
|
||||||
pub fn new(app: &Application, monitor_name: String, config: Config) -> Self {
|
gtk_layer_shell::Edge::Bottom,
|
||||||
let window = ApplicationWindow::builder()
|
position == BarPosition::Bottom
|
||||||
.application(app)
|
|| (bar_orientation == Orientation::Vertical && anchor_to_edges),
|
||||||
.type_(WindowType::Toplevel)
|
);
|
||||||
.build();
|
gtk_layer_shell::set_anchor(
|
||||||
|
win,
|
||||||
let name = config
|
gtk_layer_shell::Edge::Left,
|
||||||
.name
|
position == BarPosition::Left
|
||||||
.clone()
|
|| (bar_orientation == Orientation::Horizontal && anchor_to_edges),
|
||||||
.unwrap_or_else(|| format!("bar-{}", Ironbar::unique_id()));
|
);
|
||||||
|
gtk_layer_shell::set_anchor(
|
||||||
window.set_widget_name(&name);
|
win,
|
||||||
|
gtk_layer_shell::Edge::Right,
|
||||||
let position = config.position;
|
position == BarPosition::Right
|
||||||
let orientation = position.get_orientation();
|
|| (bar_orientation == Orientation::Horizontal && anchor_to_edges),
|
||||||
|
);
|
||||||
let content = gtk::Box::builder()
|
|
||||||
.orientation(orientation)
|
|
||||||
.spacing(0)
|
|
||||||
.hexpand(false)
|
|
||||||
.name("bar");
|
|
||||||
|
|
||||||
let content = if orientation == Orientation::Horizontal {
|
|
||||||
content.height_request(config.height)
|
|
||||||
} else {
|
|
||||||
content.width_request(config.height)
|
|
||||||
}
|
|
||||||
.build();
|
|
||||||
|
|
||||||
content.style_context().add_class("container");
|
|
||||||
|
|
||||||
let start = create_container("start", orientation);
|
|
||||||
let center = create_container("center", orientation);
|
|
||||||
let end = create_container("end", orientation);
|
|
||||||
|
|
||||||
content.add(&start);
|
|
||||||
content.set_center_widget(Some(¢er));
|
|
||||||
content.pack_end(&end, false, false, 0);
|
|
||||||
|
|
||||||
window.add(&content);
|
|
||||||
|
|
||||||
window.connect_destroy_event(|_, _| {
|
|
||||||
info!("Shutting down");
|
|
||||||
gtk::main_quit();
|
|
||||||
Propagation::Proceed
|
|
||||||
});
|
|
||||||
|
|
||||||
Bar {
|
|
||||||
name,
|
|
||||||
monitor_name,
|
|
||||||
position,
|
|
||||||
window,
|
|
||||||
content,
|
|
||||||
start,
|
|
||||||
center,
|
|
||||||
end,
|
|
||||||
inner: Inner::New {
|
|
||||||
config: Some(config),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn init(mut self, monitor: &Monitor) -> Result<Self> {
|
|
||||||
let Inner::New { ref mut config } = self.inner else {
|
|
||||||
return Ok(self);
|
|
||||||
};
|
|
||||||
|
|
||||||
let Some(config) = config.take() else {
|
|
||||||
return Ok(self);
|
|
||||||
};
|
|
||||||
|
|
||||||
info!(
|
|
||||||
"Initializing bar '{}' on '{}'",
|
|
||||||
self.name, self.monitor_name
|
|
||||||
);
|
|
||||||
|
|
||||||
self.setup_layer_shell(
|
|
||||||
&self.window,
|
|
||||||
true,
|
|
||||||
config.anchor_to_edges,
|
|
||||||
config.margin,
|
|
||||||
monitor,
|
|
||||||
);
|
|
||||||
|
|
||||||
let start_hidden = config.start_hidden.unwrap_or(config.autohide.is_some());
|
|
||||||
|
|
||||||
if let Some(autohide) = config.autohide {
|
|
||||||
let hotspot_window = Window::new(WindowType::Toplevel);
|
|
||||||
|
|
||||||
Self::setup_autohide(&self.window, &hotspot_window, autohide);
|
|
||||||
self.setup_layer_shell(
|
|
||||||
&hotspot_window,
|
|
||||||
false,
|
|
||||||
config.anchor_to_edges,
|
|
||||||
config.margin,
|
|
||||||
monitor,
|
|
||||||
);
|
|
||||||
|
|
||||||
if start_hidden {
|
|
||||||
hotspot_window.show();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let load_result = self.load_modules(config, monitor)?;
|
|
||||||
|
|
||||||
self.show(!start_hidden);
|
|
||||||
|
|
||||||
self.inner = Inner::Loaded {
|
|
||||||
popup: load_result.popup,
|
|
||||||
};
|
|
||||||
Ok(self)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Sets up GTK layer shell for a provided application window.
|
|
||||||
fn setup_layer_shell(
|
|
||||||
&self,
|
|
||||||
win: &impl IsA<Window>,
|
|
||||||
exclusive_zone: bool,
|
|
||||||
anchor_to_edges: bool,
|
|
||||||
margin: MarginConfig,
|
|
||||||
monitor: &Monitor,
|
|
||||||
) {
|
|
||||||
let position = self.position;
|
|
||||||
|
|
||||||
win.init_layer_shell();
|
|
||||||
win.set_monitor(monitor);
|
|
||||||
win.set_layer(gtk_layer_shell::Layer::Top);
|
|
||||||
win.set_namespace(env!("CARGO_PKG_NAME"));
|
|
||||||
|
|
||||||
if exclusive_zone {
|
|
||||||
win.auto_exclusive_zone_enable();
|
|
||||||
}
|
|
||||||
|
|
||||||
win.set_layer_shell_margin(gtk_layer_shell::Edge::Top, margin.top);
|
|
||||||
win.set_layer_shell_margin(gtk_layer_shell::Edge::Bottom, margin.bottom);
|
|
||||||
win.set_layer_shell_margin(gtk_layer_shell::Edge::Left, margin.left);
|
|
||||||
win.set_layer_shell_margin(gtk_layer_shell::Edge::Right, margin.right);
|
|
||||||
|
|
||||||
let bar_orientation = position.get_orientation();
|
|
||||||
|
|
||||||
win.set_anchor(
|
|
||||||
gtk_layer_shell::Edge::Top,
|
|
||||||
position == BarPosition::Top
|
|
||||||
|| (bar_orientation == Orientation::Vertical && anchor_to_edges),
|
|
||||||
);
|
|
||||||
win.set_anchor(
|
|
||||||
gtk_layer_shell::Edge::Bottom,
|
|
||||||
position == BarPosition::Bottom
|
|
||||||
|| (bar_orientation == Orientation::Vertical && anchor_to_edges),
|
|
||||||
);
|
|
||||||
win.set_anchor(
|
|
||||||
gtk_layer_shell::Edge::Left,
|
|
||||||
position == BarPosition::Left
|
|
||||||
|| (bar_orientation == Orientation::Horizontal && anchor_to_edges),
|
|
||||||
);
|
|
||||||
win.set_anchor(
|
|
||||||
gtk_layer_shell::Edge::Right,
|
|
||||||
position == BarPosition::Right
|
|
||||||
|| (bar_orientation == Orientation::Horizontal && anchor_to_edges),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn setup_autohide(window: &ApplicationWindow, hotspot_window: &Window, timeout: u64) {
|
|
||||||
hotspot_window.hide();
|
|
||||||
|
|
||||||
hotspot_window.set_opacity(0.0);
|
|
||||||
hotspot_window.set_decorated(false);
|
|
||||||
hotspot_window.set_size_request(0, 1);
|
|
||||||
|
|
||||||
{
|
|
||||||
let hotspot_window = hotspot_window.clone();
|
|
||||||
|
|
||||||
window.connect_leave_notify_event(move |win, _| {
|
|
||||||
let win = win.clone();
|
|
||||||
let hotspot_window = hotspot_window.clone();
|
|
||||||
|
|
||||||
glib::timeout_add_local_once(Duration::from_millis(timeout), move || {
|
|
||||||
win.hide();
|
|
||||||
hotspot_window.show();
|
|
||||||
});
|
|
||||||
Propagation::Proceed
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
let win = window.clone();
|
|
||||||
|
|
||||||
hotspot_window.connect_enter_notify_event(move |hotspot_win, _| {
|
|
||||||
hotspot_win.hide();
|
|
||||||
win.show();
|
|
||||||
|
|
||||||
Propagation::Proceed
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Loads the configured modules onto a bar.
|
|
||||||
fn load_modules(&self, config: Config, monitor: &Monitor) -> Result<BarLoadResult> {
|
|
||||||
let icon_theme = IconTheme::new();
|
|
||||||
if let Some(ref theme) = config.icon_theme {
|
|
||||||
icon_theme.set_custom_theme(Some(theme));
|
|
||||||
}
|
|
||||||
|
|
||||||
let app = &self.window.application().expect("to exist");
|
|
||||||
|
|
||||||
macro_rules! info {
|
|
||||||
($location:expr) => {
|
|
||||||
ModuleInfo {
|
|
||||||
app,
|
|
||||||
bar_position: config.position,
|
|
||||||
monitor,
|
|
||||||
output_name: &self.monitor_name,
|
|
||||||
location: $location,
|
|
||||||
icon_theme: &icon_theme,
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// popup ignores module location so can bodge this for now
|
|
||||||
let popup = Popup::new(&info!(ModuleLocation::Left), config.popup_gap);
|
|
||||||
let popup = Rc::new(RefCell::new(popup));
|
|
||||||
|
|
||||||
if let Some(modules) = config.start {
|
|
||||||
let info = info!(ModuleLocation::Left);
|
|
||||||
add_modules(&self.start, modules, &info, &popup)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(modules) = config.center {
|
|
||||||
let info = info!(ModuleLocation::Center);
|
|
||||||
add_modules(&self.center, modules, &info, &popup)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(modules) = config.end {
|
|
||||||
let info = info!(ModuleLocation::Right);
|
|
||||||
add_modules(&self.end, modules, &info, &popup)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
let result = BarLoadResult { popup };
|
|
||||||
|
|
||||||
Ok(result)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn show(&self, include_window: bool) {
|
|
||||||
debug!("Showing bar: {}", self.name);
|
|
||||||
|
|
||||||
// show each box but do not use `show_all`.
|
|
||||||
// this ensures `show_if` option works as intended.
|
|
||||||
self.start.show();
|
|
||||||
self.center.show();
|
|
||||||
self.end.show();
|
|
||||||
self.content.show();
|
|
||||||
|
|
||||||
if include_window {
|
|
||||||
self.window.show();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn name(&self) -> &str {
|
|
||||||
&self.name
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn popup(&self) -> Rc<RefCell<Popup>> {
|
|
||||||
match &self.inner {
|
|
||||||
Inner::New { .. } => {
|
|
||||||
panic!("Attempted to get popup of uninitialized bar. This is a serious bug!")
|
|
||||||
}
|
|
||||||
Inner::Loaded { popup } => popup.clone(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a `gtk::Box` container to place widgets inside.
|
/// Creates a `gtk::Box` container to place widgets inside.
|
||||||
@@ -327,6 +155,58 @@ struct BarLoadResult {
|
|||||||
popup: Rc<RefCell<Popup>>,
|
popup: Rc<RefCell<Popup>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Loads the configured modules onto a bar.
|
||||||
|
fn load_modules(
|
||||||
|
left: >k::Box,
|
||||||
|
center: >k::Box,
|
||||||
|
right: >k::Box,
|
||||||
|
app: &Application,
|
||||||
|
config: Config,
|
||||||
|
monitor: &Monitor,
|
||||||
|
output_name: &str,
|
||||||
|
) -> Result<BarLoadResult> {
|
||||||
|
let icon_theme = IconTheme::new();
|
||||||
|
if let Some(ref theme) = config.icon_theme {
|
||||||
|
icon_theme.set_custom_theme(Some(theme));
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! info {
|
||||||
|
($location:expr) => {
|
||||||
|
ModuleInfo {
|
||||||
|
app,
|
||||||
|
bar_position: config.position,
|
||||||
|
monitor,
|
||||||
|
output_name,
|
||||||
|
location: $location,
|
||||||
|
icon_theme: &icon_theme,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// popup ignores module location so can bodge this for now
|
||||||
|
let popup = Popup::new(&info!(ModuleLocation::Left), config.popup_gap);
|
||||||
|
let popup = Rc::new(RefCell::new(popup));
|
||||||
|
|
||||||
|
if let Some(modules) = config.start {
|
||||||
|
let info = info!(ModuleLocation::Left);
|
||||||
|
add_modules(left, modules, &info, &popup)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(modules) = config.center {
|
||||||
|
let info = info!(ModuleLocation::Center);
|
||||||
|
add_modules(center, modules, &info, &popup)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(modules) = config.end {
|
||||||
|
let info = info!(ModuleLocation::Right);
|
||||||
|
add_modules(right, modules, &info, &popup)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let result = BarLoadResult { popup };
|
||||||
|
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
|
|
||||||
/// Adds modules into a provided GTK box,
|
/// Adds modules into a provided GTK box,
|
||||||
/// which should be one of its left, center or right containers.
|
/// which should be one of its left, center or right containers.
|
||||||
fn add_modules(
|
fn add_modules(
|
||||||
@@ -355,7 +235,7 @@ fn add_modules(
|
|||||||
}
|
}
|
||||||
|
|
||||||
for config in modules {
|
for config in modules {
|
||||||
let id = Ironbar::unique_id();
|
let id = get_unique_usize();
|
||||||
match config {
|
match config {
|
||||||
#[cfg(feature = "clipboard")]
|
#[cfg(feature = "clipboard")]
|
||||||
ModuleConfig::Clipboard(mut module) => add_module!(module, id),
|
ModuleConfig::Clipboard(mut module) => add_module!(module, id),
|
||||||
@@ -381,13 +261,3 @@ fn add_modules(
|
|||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn create_bar(
|
|
||||||
app: &Application,
|
|
||||||
monitor: &Monitor,
|
|
||||||
monitor_name: String,
|
|
||||||
config: Config,
|
|
||||||
) -> Result<Bar> {
|
|
||||||
let bar = Bar::new(app, monitor_name, config);
|
|
||||||
bar.init(monitor)
|
|
||||||
}
|
|
||||||
|
|||||||
44
src/bridge_channel.rs
Normal file
44
src/bridge_channel.rs
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
use crate::send;
|
||||||
|
use tokio::spawn;
|
||||||
|
use tokio::sync::mpsc;
|
||||||
|
|
||||||
|
/// MPSC async -> GTK sync channel.
|
||||||
|
/// The sender uses `tokio::sync::mpsc`
|
||||||
|
/// while the receiver uses `glib::MainContext::channel`.
|
||||||
|
///
|
||||||
|
/// This makes it possible to send events asynchronously
|
||||||
|
/// and receive them on the main thread,
|
||||||
|
/// allowing UI updates to be handled on the receiving end.
|
||||||
|
pub struct BridgeChannel<T> {
|
||||||
|
async_tx: mpsc::Sender<T>,
|
||||||
|
sync_rx: glib::Receiver<T>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Send + 'static> BridgeChannel<T> {
|
||||||
|
/// Creates a new channel
|
||||||
|
pub fn new() -> Self {
|
||||||
|
let (async_tx, mut async_rx) = mpsc::channel(32);
|
||||||
|
let (sync_tx, sync_rx) = glib::MainContext::channel(glib::PRIORITY_DEFAULT);
|
||||||
|
|
||||||
|
spawn(async move {
|
||||||
|
while let Some(val) = async_rx.recv().await {
|
||||||
|
send!(sync_tx, val);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Self { async_tx, sync_rx }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets a clone of the sender.
|
||||||
|
pub fn create_sender(&self) -> mpsc::Sender<T> {
|
||||||
|
self.async_tx.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Attaches a callback to the receiver.
|
||||||
|
pub fn recv<F>(self, f: F) -> glib::SourceId
|
||||||
|
where
|
||||||
|
F: FnMut(T) -> glib::Continue + 'static,
|
||||||
|
{
|
||||||
|
self.sync_rx.attach(None, f)
|
||||||
|
}
|
||||||
|
}
|
||||||
145
src/cached_broadcast.rs
Normal file
145
src/cached_broadcast.rs
Normal file
@@ -0,0 +1,145 @@
|
|||||||
|
use crate::{arc_rw, read_lock, send_async, write_lock};
|
||||||
|
use std::fmt::Debug;
|
||||||
|
use std::ops::Deref;
|
||||||
|
use std::sync::{Arc, RwLock};
|
||||||
|
// use std::thread::sleep;
|
||||||
|
use std::time::Duration;
|
||||||
|
use tokio::spawn;
|
||||||
|
use tokio::sync::mpsc;
|
||||||
|
use tokio::task::spawn_blocking;
|
||||||
|
use tokio::time::sleep;
|
||||||
|
use tracing::trace;
|
||||||
|
|
||||||
|
pub trait Cacheable: Debug + Clone + Send + Sync {
|
||||||
|
type Key: Debug + Clone + Send + Sync + Eq;
|
||||||
|
|
||||||
|
fn get_key(&self) -> Self::Key;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type Sender<T> = mpsc::Sender<Event<T>>;
|
||||||
|
pub type Receiver<T> = mpsc::Receiver<Event<T>>;
|
||||||
|
|
||||||
|
pub struct CachedBroadcastChannel<T>
|
||||||
|
where
|
||||||
|
T: Cacheable,
|
||||||
|
{
|
||||||
|
capacity: usize,
|
||||||
|
data: Vec<T>,
|
||||||
|
channels: Arc<RwLock<Vec<mpsc::Sender<Event<T>>>>>,
|
||||||
|
base_tx: mpsc::Sender<Event<T>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum Event<T>
|
||||||
|
where
|
||||||
|
T: Cacheable,
|
||||||
|
{
|
||||||
|
Add(T),
|
||||||
|
Remove(T::Key),
|
||||||
|
Replace(T::Key, T),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> CachedBroadcastChannel<T>
|
||||||
|
where
|
||||||
|
T: Cacheable + 'static,
|
||||||
|
{
|
||||||
|
pub fn new(capacity: usize) -> Self {
|
||||||
|
let (tx, rx) = mpsc::channel::<Event<T>>(capacity);
|
||||||
|
let mut rx = DropDetector(rx);
|
||||||
|
|
||||||
|
// spawn_blocking(move || loop {
|
||||||
|
// let ev = rx.0.try_recv();
|
||||||
|
// println!("{ev:?}");
|
||||||
|
// sleep(Duration::from_secs(1))
|
||||||
|
// });
|
||||||
|
|
||||||
|
let channels = arc_rw!(Vec::<Sender<T>>::new());
|
||||||
|
|
||||||
|
let channels = Arc::clone(&channels);
|
||||||
|
spawn(async move {
|
||||||
|
println!("hello");
|
||||||
|
|
||||||
|
while let Some(event) = rx.0.recv().await {
|
||||||
|
println!("ev");
|
||||||
|
// trace!("{event:?}");
|
||||||
|
// let iter = read_lock!(channels).clone().into_iter();
|
||||||
|
// for channel in iter {
|
||||||
|
// send_async!(channel, event.clone());
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
println!("goodbye");
|
||||||
|
});
|
||||||
|
|
||||||
|
Self {
|
||||||
|
capacity,
|
||||||
|
data: vec![],
|
||||||
|
channels,
|
||||||
|
base_tx: tx,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn send(&mut self, event: Event<T>) {
|
||||||
|
match event.clone() {
|
||||||
|
Event::Add(data) => {
|
||||||
|
self.data.push(data);
|
||||||
|
}
|
||||||
|
Event::Remove(key) => {
|
||||||
|
let Some(index) = self.data.iter().position(|t| t.get_key() == key) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
self.data.remove(index);
|
||||||
|
}
|
||||||
|
Event::Replace(key, data) => {
|
||||||
|
let Some(index) = self.data.iter().position(|t| t.get_key() == key) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let _ = std::mem::replace(&mut self.data[index], data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
send_async!(self.base_tx, event);
|
||||||
|
|
||||||
|
// let mut closed = vec![];
|
||||||
|
// for (i, channel) in read_lock!(self.channels).iter().enumerate() {
|
||||||
|
// if channel.is_closed() {
|
||||||
|
// closed.push(i);
|
||||||
|
// } else {
|
||||||
|
// send_async!(channel, event.clone());
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// for channel in closed.into_iter().rev() {
|
||||||
|
// write_lock!(self.channels).remove(channel);
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn sender(&self) -> mpsc::Sender<Event<T>> {
|
||||||
|
self.base_tx.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn receiver(&mut self) -> mpsc::Receiver<Event<T>> {
|
||||||
|
let (tx, rx) = mpsc::channel(self.capacity);
|
||||||
|
write_lock!(self.channels).push(tx);
|
||||||
|
|
||||||
|
rx
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn data(&self) -> &Vec<T> {
|
||||||
|
&self.data
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct DropDetector<T>(T);
|
||||||
|
|
||||||
|
impl<T> Drop for DropDetector<T> {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
println!("DROPPED")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Cacheable> Drop for CachedBroadcastChannel<T> {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
println!("Channel DROPPED")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,9 +1,10 @@
|
|||||||
use super::wayland::{self, ClipboardItem};
|
use super::wayland::{self, ClipboardItem};
|
||||||
use crate::{arc_mut, lock, spawn, try_send};
|
use crate::{arc_mut, lock, try_send};
|
||||||
use indexmap::map::Iter;
|
use indexmap::map::Iter;
|
||||||
use indexmap::IndexMap;
|
use indexmap::IndexMap;
|
||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
|
use tokio::spawn;
|
||||||
use tokio::sync::mpsc;
|
use tokio::sync::mpsc;
|
||||||
use tracing::{debug, trace};
|
use tracing::{debug, trace};
|
||||||
|
|
||||||
|
|||||||
@@ -1,13 +1,14 @@
|
|||||||
use super::{Visibility, Workspace, WorkspaceClient, WorkspaceUpdate};
|
use super::{Visibility, Workspace, WorkspaceClient, WorkspaceUpdate};
|
||||||
use crate::{arc_mut, lock, send, spawn_blocking};
|
use crate::{arc_mut, lock, send};
|
||||||
use color_eyre::Result;
|
use color_eyre::Result;
|
||||||
use hyprland::data::{Workspace as HWorkspace, Workspaces};
|
use hyprland::data::{Workspace as HWorkspace, Workspaces};
|
||||||
use hyprland::dispatch::{Dispatch, DispatchType, WorkspaceIdentifierWithSpecial};
|
use hyprland::dispatch::{Dispatch, DispatchType, WorkspaceIdentifierWithSpecial};
|
||||||
use hyprland::event_listener::EventListener;
|
use hyprland::event_listener::EventListener;
|
||||||
use hyprland::prelude::*;
|
use hyprland::prelude::*;
|
||||||
use hyprland::shared::{HyprDataVec, WorkspaceType};
|
use hyprland::shared::WorkspaceType;
|
||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
use tokio::sync::broadcast::{channel, Receiver, Sender};
|
use tokio::sync::broadcast::{channel, Receiver, Sender};
|
||||||
|
use tokio::task::spawn_blocking;
|
||||||
use tracing::{debug, error, info};
|
use tracing::{debug, error, info};
|
||||||
|
|
||||||
pub struct EventClient {
|
pub struct EventClient {
|
||||||
@@ -84,6 +85,7 @@ impl EventClient {
|
|||||||
},
|
},
|
||||||
|workspace| {
|
|workspace| {
|
||||||
// there may be another type of update so dispatch that regardless of focus change
|
// there may be another type of update so dispatch that regardless of focus change
|
||||||
|
send!(tx, WorkspaceUpdate::Update(workspace.clone()));
|
||||||
if !workspace.visibility.is_focused() {
|
if !workspace.visibility.is_focused() {
|
||||||
Self::send_focus_change(&mut prev_workspace, workspace, &tx);
|
Self::send_focus_change(&mut prev_workspace, workspace, &tx);
|
||||||
}
|
}
|
||||||
@@ -258,12 +260,9 @@ fn get_workspace_name(name: WorkspaceType) -> String {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a function which determines if a workspace is visible.
|
/// Creates a function which determines if a workspace is visible. This function makes a Hyprland call that allocates so it should be cached when possible, but it is only valid so long as workspaces do not change so it should not be stored long term
|
||||||
///
|
|
||||||
/// This function makes a Hyprland call that allocates so it should be cached when possible,
|
|
||||||
/// but it is only valid so long as workspaces do not change so it should not be stored long term
|
|
||||||
fn create_is_visible() -> impl Fn(&HWorkspace) -> bool {
|
fn create_is_visible() -> impl Fn(&HWorkspace) -> bool {
|
||||||
let monitors = hyprland::data::Monitors::get().map_or(Vec::new(), HyprDataVec::to_vec);
|
let monitors = hyprland::data::Monitors::get().map_or(Vec::new(), |ms| ms.to_vec());
|
||||||
|
|
||||||
move |w| monitors.iter().any(|m| m.active_workspace.id == w.id)
|
move |w| monitors.iter().any(|m| m.active_workspace.id == w.id)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -116,17 +116,13 @@ pub enum WorkspaceUpdate {
|
|||||||
Init(Vec<Workspace>),
|
Init(Vec<Workspace>),
|
||||||
Add(Workspace),
|
Add(Workspace),
|
||||||
Remove(String),
|
Remove(String),
|
||||||
|
Update(Workspace),
|
||||||
Move(Workspace),
|
Move(Workspace),
|
||||||
/// Declares focus moved from the old workspace to the new.
|
/// Declares focus moved from the old workspace to the new.
|
||||||
Focus {
|
Focus {
|
||||||
old: Option<Workspace>,
|
old: Option<Workspace>,
|
||||||
new: Workspace,
|
new: Workspace,
|
||||||
},
|
},
|
||||||
/// An update was triggered by the compositor but this was not mapped by Ironbar.
|
|
||||||
///
|
|
||||||
/// This is purely used for ergonomics within the compositor clients
|
|
||||||
/// and should be ignored by consumers.
|
|
||||||
Unknown,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait WorkspaceClient {
|
pub trait WorkspaceClient {
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
use super::{Visibility, Workspace, WorkspaceClient, WorkspaceUpdate};
|
use super::{Visibility, Workspace, WorkspaceClient, WorkspaceUpdate};
|
||||||
use crate::{await_sync, send, spawn};
|
use crate::{await_sync, send};
|
||||||
use async_once::AsyncOnce;
|
use async_once::AsyncOnce;
|
||||||
use color_eyre::Report;
|
use color_eyre::Report;
|
||||||
use futures_util::StreamExt;
|
use futures_util::StreamExt;
|
||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use swayipc_async::{Connection, Event, EventType, Node, WorkspaceChange, WorkspaceEvent};
|
use swayipc_async::{Connection, Event, EventType, Node, WorkspaceChange, WorkspaceEvent};
|
||||||
|
use tokio::spawn;
|
||||||
use tokio::sync::broadcast::{channel, Receiver, Sender};
|
use tokio::sync::broadcast::{channel, Receiver, Sender};
|
||||||
use tokio::sync::Mutex;
|
use tokio::sync::Mutex;
|
||||||
use tracing::{info, trace};
|
use tracing::{info, trace};
|
||||||
@@ -31,11 +32,8 @@ impl SwayEventClient {
|
|||||||
|
|
||||||
while let Some(event) = events.next().await {
|
while let Some(event) = events.next().await {
|
||||||
trace!("event: {:?}", event);
|
trace!("event: {:?}", event);
|
||||||
if let Event::Workspace(event) = event? {
|
if let Event::Workspace(ev) = event? {
|
||||||
let event = WorkspaceUpdate::from(*event);
|
workspace_tx.send(WorkspaceUpdate::from(*ev))?;
|
||||||
if !matches!(event, WorkspaceUpdate::Unknown) {
|
|
||||||
workspace_tx.send(event)?;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -175,7 +173,7 @@ impl From<WorkspaceEvent> for WorkspaceUpdate {
|
|||||||
WorkspaceChange::Move => {
|
WorkspaceChange::Move => {
|
||||||
Self::Move(event.current.expect("Missing current workspace").into())
|
Self::Move(event.current.expect("Missing current workspace").into())
|
||||||
}
|
}
|
||||||
_ => Self::Unknown,
|
_ => Self::Update(event.current.expect("Missing current workspace").into()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
use super::{
|
use super::{
|
||||||
MusicClient, PlayerState, PlayerUpdate, ProgressTick, Status, Track, TICK_INTERVAL_MS,
|
MusicClient, PlayerState, PlayerUpdate, ProgressTick, Status, Track, TICK_INTERVAL_MS,
|
||||||
};
|
};
|
||||||
use crate::{await_sync, send, spawn};
|
use crate::{await_sync, send};
|
||||||
use color_eyre::Result;
|
use color_eyre::Result;
|
||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
use mpd_client::client::{Connection, ConnectionEvent, Subsystem};
|
use mpd_client::client::{Connection, ConnectionEvent, Subsystem};
|
||||||
@@ -17,6 +17,7 @@ use std::path::{Path, PathBuf};
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use tokio::net::{TcpStream, UnixStream};
|
use tokio::net::{TcpStream, UnixStream};
|
||||||
|
use tokio::spawn;
|
||||||
use tokio::sync::broadcast;
|
use tokio::sync::broadcast;
|
||||||
use tokio::sync::Mutex;
|
use tokio::sync::Mutex;
|
||||||
use tokio::time::sleep;
|
use tokio::time::sleep;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
use super::{MusicClient, PlayerState, PlayerUpdate, Status, Track, TICK_INTERVAL_MS};
|
use super::{MusicClient, PlayerState, PlayerUpdate, Status, Track, TICK_INTERVAL_MS};
|
||||||
use crate::clients::music::ProgressTick;
|
use crate::clients::music::ProgressTick;
|
||||||
use crate::{arc_mut, lock, send, spawn_blocking};
|
use crate::{arc_mut, lock, send};
|
||||||
use color_eyre::Result;
|
use color_eyre::Result;
|
||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
use mpris::{DBusError, Event, Metadata, PlaybackStatus, Player, PlayerFinder};
|
use mpris::{DBusError, Event, Metadata, PlaybackStatus, Player, PlayerFinder};
|
||||||
@@ -10,6 +10,7 @@ use std::thread::sleep;
|
|||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use std::{cmp, string};
|
use std::{cmp, string};
|
||||||
use tokio::sync::broadcast;
|
use tokio::sync::broadcast;
|
||||||
|
use tokio::task::spawn_blocking;
|
||||||
use tracing::{debug, error, trace};
|
use tracing::{debug, error, trace};
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
@@ -244,7 +245,7 @@ impl MusicClient for Client {
|
|||||||
|
|
||||||
fn set_volume_percent(&self, vol: u8) -> Result<()> {
|
fn set_volume_percent(&self, vol: u8) -> Result<()> {
|
||||||
if let Some(player) = Self::get_player(self) {
|
if let Some(player) = Self::get_player(self) {
|
||||||
player.set_volume(f64::from(vol) / 100.0)?;
|
player.set_volume(vol as f64 / 100.0)?;
|
||||||
} else {
|
} else {
|
||||||
error!("Could not find player");
|
error!("Could not find player");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
use crate::{arc_mut, lock, send, spawn, Ironbar};
|
use crate::unique_id::get_unique_usize;
|
||||||
|
use crate::{arc_mut, lock, send};
|
||||||
use async_once::AsyncOnce;
|
use async_once::AsyncOnce;
|
||||||
use color_eyre::Report;
|
use color_eyre::Report;
|
||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
@@ -8,6 +9,7 @@ use system_tray::message::menu::TrayMenu;
|
|||||||
use system_tray::message::tray::StatusNotifierItem;
|
use system_tray::message::tray::StatusNotifierItem;
|
||||||
use system_tray::message::{NotifierItemCommand, NotifierItemMessage};
|
use system_tray::message::{NotifierItemCommand, NotifierItemMessage};
|
||||||
use system_tray::StatusNotifierWatcher;
|
use system_tray::StatusNotifierWatcher;
|
||||||
|
use tokio::spawn;
|
||||||
use tokio::sync::{broadcast, mpsc};
|
use tokio::sync::{broadcast, mpsc};
|
||||||
use tracing::{debug, error, trace};
|
use tracing::{debug, error, trace};
|
||||||
|
|
||||||
@@ -23,7 +25,7 @@ pub struct TrayEventReceiver {
|
|||||||
|
|
||||||
impl TrayEventReceiver {
|
impl TrayEventReceiver {
|
||||||
async fn new() -> system_tray::error::Result<Self> {
|
async fn new() -> system_tray::error::Result<Self> {
|
||||||
let id = format!("ironbar-{}", Ironbar::unique_id());
|
let id = format!("ironbar-{}", get_unique_usize());
|
||||||
|
|
||||||
let (tx, rx) = mpsc::channel(16);
|
let (tx, rx) = mpsc::channel(16);
|
||||||
let (b_tx, b_rx) = broadcast::channel(16);
|
let (b_tx, b_rx) = broadcast::channel(16);
|
||||||
|
|||||||
@@ -2,23 +2,24 @@ use super::wlr_foreign_toplevel::handle::ToplevelHandle;
|
|||||||
use super::wlr_foreign_toplevel::manager::ToplevelManagerState;
|
use super::wlr_foreign_toplevel::manager::ToplevelManagerState;
|
||||||
use super::wlr_foreign_toplevel::ToplevelEvent;
|
use super::wlr_foreign_toplevel::ToplevelEvent;
|
||||||
use super::Environment;
|
use super::Environment;
|
||||||
|
use crate::cached_broadcast::CachedBroadcastChannel;
|
||||||
use crate::error::ERR_CHANNEL_RECV;
|
use crate::error::ERR_CHANNEL_RECV;
|
||||||
use crate::{send, spawn_blocking};
|
use crate::{cached_broadcast, send};
|
||||||
use cfg_if::cfg_if;
|
use cfg_if::cfg_if;
|
||||||
use color_eyre::Report;
|
use color_eyre::Report;
|
||||||
use smithay_client_toolkit::output::{OutputInfo, OutputState};
|
use smithay_client_toolkit::output::{OutputInfo, OutputState};
|
||||||
use smithay_client_toolkit::reexports::calloop::channel::{channel, Event, Sender};
|
use smithay_client_toolkit::reexports::calloop::channel::{channel, Event, Sender};
|
||||||
use smithay_client_toolkit::reexports::calloop::EventLoop;
|
use smithay_client_toolkit::reexports::calloop::EventLoop;
|
||||||
use smithay_client_toolkit::reexports::calloop_wayland_source::WaylandSource;
|
|
||||||
use smithay_client_toolkit::registry::RegistryState;
|
use smithay_client_toolkit::registry::RegistryState;
|
||||||
use smithay_client_toolkit::seat::SeatState;
|
use smithay_client_toolkit::seat::SeatState;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::sync::mpsc;
|
use std::sync::mpsc;
|
||||||
use tokio::sync::broadcast;
|
use tokio::sync::broadcast;
|
||||||
|
use tokio::task::spawn_blocking;
|
||||||
use tracing::{debug, error, trace};
|
use tracing::{debug, error, trace};
|
||||||
use wayland_client::globals::registry_queue_init;
|
use wayland_client::globals::registry_queue_init;
|
||||||
use wayland_client::protocol::wl_seat::WlSeat;
|
use wayland_client::protocol::wl_seat::WlSeat;
|
||||||
use wayland_client::Connection;
|
use wayland_client::{Connection, WaylandSource};
|
||||||
|
|
||||||
cfg_if! {
|
cfg_if! {
|
||||||
if #[cfg(feature = "clipboard")] {
|
if #[cfg(feature = "clipboard")] {
|
||||||
@@ -31,11 +32,8 @@ cfg_if! {
|
|||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum Request {
|
pub enum Request {
|
||||||
/// Sends a request for all the outputs.
|
|
||||||
/// These are then sent on the `output` channel.
|
|
||||||
Outputs,
|
|
||||||
/// Sends a request for all the seats.
|
/// Sends a request for all the seats.
|
||||||
/// These are then sent ont the `seat` channel.
|
/// These are then sent on the `seat` channel.
|
||||||
Seats,
|
Seats,
|
||||||
/// Sends a request for all the toplevels.
|
/// Sends a request for all the toplevels.
|
||||||
/// These are then sent on the `toplevel_init` channel.
|
/// These are then sent on the `toplevel_init` channel.
|
||||||
@@ -53,8 +51,10 @@ pub enum Request {
|
|||||||
|
|
||||||
pub struct WaylandClient {
|
pub struct WaylandClient {
|
||||||
// External channels
|
// External channels
|
||||||
|
output_channel: CachedBroadcastChannel<OutputInfo>,
|
||||||
toplevel_tx: broadcast::Sender<ToplevelEvent>,
|
toplevel_tx: broadcast::Sender<ToplevelEvent>,
|
||||||
_toplevel_rx: broadcast::Receiver<ToplevelEvent>,
|
_toplevel_rx: broadcast::Receiver<ToplevelEvent>,
|
||||||
|
|
||||||
#[cfg(feature = "clipboard")]
|
#[cfg(feature = "clipboard")]
|
||||||
clipboard_tx: broadcast::Sender<Arc<ClipboardItem>>,
|
clipboard_tx: broadcast::Sender<Arc<ClipboardItem>>,
|
||||||
#[cfg(feature = "clipboard")]
|
#[cfg(feature = "clipboard")]
|
||||||
@@ -62,7 +62,6 @@ pub struct WaylandClient {
|
|||||||
|
|
||||||
// Internal channels
|
// Internal channels
|
||||||
toplevel_init_rx: mpsc::Receiver<HashMap<usize, ToplevelHandle>>,
|
toplevel_init_rx: mpsc::Receiver<HashMap<usize, ToplevelHandle>>,
|
||||||
output_rx: mpsc::Receiver<Vec<OutputInfo>>,
|
|
||||||
seat_rx: mpsc::Receiver<Vec<WlSeat>>,
|
seat_rx: mpsc::Receiver<Vec<WlSeat>>,
|
||||||
#[cfg(feature = "clipboard")]
|
#[cfg(feature = "clipboard")]
|
||||||
clipboard_init_rx: mpsc::Receiver<Option<Arc<ClipboardItem>>>,
|
clipboard_init_rx: mpsc::Receiver<Option<Arc<ClipboardItem>>>,
|
||||||
@@ -74,10 +73,14 @@ impl WaylandClient {
|
|||||||
pub(super) fn new() -> Self {
|
pub(super) fn new() -> Self {
|
||||||
let (toplevel_tx, toplevel_rx) = broadcast::channel(32);
|
let (toplevel_tx, toplevel_rx) = broadcast::channel(32);
|
||||||
|
|
||||||
|
let mut output_channel = CachedBroadcastChannel::new(8);
|
||||||
|
let output_tx = output_channel.sender();
|
||||||
|
|
||||||
|
let tx2 = output_tx.clone();
|
||||||
|
|
||||||
let (toplevel_init_tx, toplevel_init_rx) = mpsc::channel();
|
let (toplevel_init_tx, toplevel_init_rx) = mpsc::channel();
|
||||||
#[cfg(feature = "clipboard")]
|
#[cfg(feature = "clipboard")]
|
||||||
let (clipboard_init_tx, clipboard_init_rx) = mpsc::channel();
|
let (clipboard_init_tx, clipboard_init_rx) = mpsc::channel();
|
||||||
let (output_tx, output_rx) = mpsc::channel();
|
|
||||||
let (seat_tx, seat_rx) = mpsc::channel();
|
let (seat_tx, seat_rx) = mpsc::channel();
|
||||||
|
|
||||||
let toplevel_tx2 = toplevel_tx.clone();
|
let toplevel_tx2 = toplevel_tx.clone();
|
||||||
@@ -99,6 +102,7 @@ impl WaylandClient {
|
|||||||
|
|
||||||
let conn =
|
let conn =
|
||||||
Connection::connect_to_env().expect("Failed to connect to Wayland compositor");
|
Connection::connect_to_env().expect("Failed to connect to Wayland compositor");
|
||||||
|
|
||||||
let (globals, queue) =
|
let (globals, queue) =
|
||||||
registry_queue_init(&conn).expect("Failed to retrieve Wayland globals");
|
registry_queue_init(&conn).expect("Failed to retrieve Wayland globals");
|
||||||
|
|
||||||
@@ -106,7 +110,8 @@ impl WaylandClient {
|
|||||||
let mut event_loop =
|
let mut event_loop =
|
||||||
EventLoop::<Environment>::try_new().expect("Failed to create new event loop");
|
EventLoop::<Environment>::try_new().expect("Failed to create new event loop");
|
||||||
|
|
||||||
WaylandSource::new(conn, queue)
|
WaylandSource::new(queue)
|
||||||
|
.expect("Failed to create Wayland source from queue")
|
||||||
.insert(event_loop.handle())
|
.insert(event_loop.handle())
|
||||||
.expect("Failed to insert Wayland event queue into event loop");
|
.expect("Failed to insert Wayland event queue into event loop");
|
||||||
|
|
||||||
@@ -138,6 +143,7 @@ impl WaylandClient {
|
|||||||
handles: HashMap::new(),
|
handles: HashMap::new(),
|
||||||
#[cfg(feature = "clipboard")]
|
#[cfg(feature = "clipboard")]
|
||||||
clipboard: crate::arc_mut!(None),
|
clipboard: crate::arc_mut!(None),
|
||||||
|
output_tx,
|
||||||
toplevel_tx,
|
toplevel_tx,
|
||||||
#[cfg(feature = "clipboard")]
|
#[cfg(feature = "clipboard")]
|
||||||
clipboard_tx,
|
clipboard_tx,
|
||||||
@@ -155,11 +161,6 @@ impl WaylandClient {
|
|||||||
trace!("{event:?}");
|
trace!("{event:?}");
|
||||||
match event {
|
match event {
|
||||||
Event::Msg(Request::Roundtrip) => debug!("Received refresh event"),
|
Event::Msg(Request::Roundtrip) => debug!("Received refresh event"),
|
||||||
Event::Msg(Request::Outputs) => {
|
|
||||||
trace!("Received get outputs request");
|
|
||||||
|
|
||||||
send!(output_tx, env.output_info());
|
|
||||||
}
|
|
||||||
Event::Msg(Request::Seats) => {
|
Event::Msg(Request::Seats) => {
|
||||||
trace!("Receive get seats request");
|
trace!("Receive get seats request");
|
||||||
send!(seat_tx, env.seats.clone());
|
send!(seat_tx, env.seats.clone());
|
||||||
@@ -195,12 +196,12 @@ impl WaylandClient {
|
|||||||
});
|
});
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
|
output_channel,
|
||||||
toplevel_tx,
|
toplevel_tx,
|
||||||
_toplevel_rx: toplevel_rx,
|
_toplevel_rx: toplevel_rx,
|
||||||
toplevel_init_rx,
|
toplevel_init_rx,
|
||||||
#[cfg(feature = "clipboard")]
|
#[cfg(feature = "clipboard")]
|
||||||
clipboard_init_rx,
|
clipboard_init_rx,
|
||||||
output_rx,
|
|
||||||
seat_rx,
|
seat_rx,
|
||||||
#[cfg(feature = "clipboard")]
|
#[cfg(feature = "clipboard")]
|
||||||
clipboard_tx,
|
clipboard_tx,
|
||||||
@@ -241,6 +242,10 @@ impl WaylandClient {
|
|||||||
(rx, data)
|
(rx, data)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn subscribe_outputs(&mut self) -> cached_broadcast::Receiver<OutputInfo> {
|
||||||
|
self.output_channel.receiver()
|
||||||
|
}
|
||||||
|
|
||||||
/// Force a roundtrip on the wayland connection,
|
/// Force a roundtrip on the wayland connection,
|
||||||
/// flushing any queued events and immediately receiving any new ones.
|
/// flushing any queued events and immediately receiving any new ones.
|
||||||
pub fn roundtrip(&self) {
|
pub fn roundtrip(&self) {
|
||||||
@@ -248,11 +253,13 @@ impl WaylandClient {
|
|||||||
send!(self.request_tx, Request::Roundtrip);
|
send!(self.request_tx, Request::Roundtrip);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_outputs(&self) -> Vec<OutputInfo> {
|
/// Gets a list of all outputs.
|
||||||
trace!("Sending get outputs request");
|
///
|
||||||
|
/// This should only be used in a scenario
|
||||||
send!(self.request_tx, Request::Outputs);
|
/// where you need a snapshot of outputs at the current time.
|
||||||
self.output_rx.recv().expect(ERR_CHANNEL_RECV)
|
/// Prefer to listen to output events with `subscribe_output` where possible.
|
||||||
|
pub fn get_outputs(&self) -> &Vec<OutputInfo> {
|
||||||
|
self.output_channel.data()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_seats(&self) -> Vec<WlSeat> {
|
pub fn get_seats(&self) -> Vec<WlSeat> {
|
||||||
|
|||||||
@@ -6,10 +6,10 @@ mod wl_seat;
|
|||||||
mod wlr_foreign_toplevel;
|
mod wlr_foreign_toplevel;
|
||||||
|
|
||||||
use self::wlr_foreign_toplevel::manager::ToplevelManagerState;
|
use self::wlr_foreign_toplevel::manager::ToplevelManagerState;
|
||||||
use crate::{arc_mut, delegate_foreign_toplevel_handle, delegate_foreign_toplevel_manager};
|
use crate::{arc_mut, cached_broadcast, delegate_foreign_toplevel_handle, delegate_foreign_toplevel_manager};
|
||||||
use cfg_if::cfg_if;
|
use cfg_if::cfg_if;
|
||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
use smithay_client_toolkit::output::OutputState;
|
use smithay_client_toolkit::output::{OutputInfo, OutputState};
|
||||||
use smithay_client_toolkit::reexports::calloop::LoopHandle;
|
use smithay_client_toolkit::reexports::calloop::LoopHandle;
|
||||||
use smithay_client_toolkit::registry::{ProvidesRegistryState, RegistryState};
|
use smithay_client_toolkit::registry::{ProvidesRegistryState, RegistryState};
|
||||||
use smithay_client_toolkit::seat::SeatState;
|
use smithay_client_toolkit::seat::SeatState;
|
||||||
@@ -65,6 +65,7 @@ pub struct Environment {
|
|||||||
#[cfg(feature = "clipboard")]
|
#[cfg(feature = "clipboard")]
|
||||||
clipboard: Arc<Mutex<Option<Arc<ClipboardItem>>>>,
|
clipboard: Arc<Mutex<Option<Arc<ClipboardItem>>>>,
|
||||||
|
|
||||||
|
output_tx: cached_broadcast::Sender<OutputInfo>,
|
||||||
toplevel_tx: broadcast::Sender<ToplevelEvent>,
|
toplevel_tx: broadcast::Sender<ToplevelEvent>,
|
||||||
#[cfg(feature = "clipboard")]
|
#[cfg(feature = "clipboard")]
|
||||||
clipboard_tx: broadcast::Sender<Arc<ClipboardItem>>,
|
clipboard_tx: broadcast::Sender<Arc<ClipboardItem>>,
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
use super::Environment;
|
use super::Environment;
|
||||||
|
use crate::cached_broadcast::Cacheable;
|
||||||
|
use crate::{cached_broadcast, try_send};
|
||||||
use smithay_client_toolkit::output::{OutputHandler, OutputInfo, OutputState};
|
use smithay_client_toolkit::output::{OutputHandler, OutputInfo, OutputState};
|
||||||
use tracing::debug;
|
use tracing::debug;
|
||||||
use wayland_client::protocol::wl_output;
|
use wayland_client::protocol::wl_output;
|
||||||
@@ -31,9 +33,12 @@ impl OutputHandler for Environment {
|
|||||||
&mut self,
|
&mut self,
|
||||||
_conn: &Connection,
|
_conn: &Connection,
|
||||||
_qh: &QueueHandle<Self>,
|
_qh: &QueueHandle<Self>,
|
||||||
_output: wl_output::WlOutput,
|
output: wl_output::WlOutput,
|
||||||
) {
|
) {
|
||||||
debug!("Handler received new output");
|
debug!("Handler received new output");
|
||||||
|
if let Some(info) = self.output_state.info(&output) {
|
||||||
|
try_send!(self.output_tx, cached_broadcast::Event::Add(info));
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_output(
|
fn update_output(
|
||||||
@@ -42,14 +47,26 @@ impl OutputHandler for Environment {
|
|||||||
_qh: &QueueHandle<Self>,
|
_qh: &QueueHandle<Self>,
|
||||||
_output: wl_output::WlOutput,
|
_output: wl_output::WlOutput,
|
||||||
) {
|
) {
|
||||||
|
debug!("Handle received output update");
|
||||||
}
|
}
|
||||||
|
|
||||||
fn output_destroyed(
|
fn output_destroyed(
|
||||||
&mut self,
|
&mut self,
|
||||||
_conn: &Connection,
|
_conn: &Connection,
|
||||||
_qh: &QueueHandle<Self>,
|
_qh: &QueueHandle<Self>,
|
||||||
_output: wl_output::WlOutput,
|
output: wl_output::WlOutput,
|
||||||
) {
|
) {
|
||||||
debug!("Handle received output destruction");
|
debug!("Handle received output destruction");
|
||||||
|
if let Some(info) = self.output_state.info(&output) {
|
||||||
|
try_send!(self.output_tx, cached_broadcast::Event::Remove(info.id));
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Cacheable for OutputInfo {
|
||||||
|
type Key = u32;
|
||||||
|
|
||||||
|
fn get_key(&self) -> Self::Key {
|
||||||
|
self.id
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,13 +7,14 @@ use self::device::{DataControlDeviceDataExt, DataControlDeviceHandler};
|
|||||||
use self::offer::{DataControlDeviceOffer, DataControlOfferHandler, SelectionOffer};
|
use self::offer::{DataControlDeviceOffer, DataControlOfferHandler, SelectionOffer};
|
||||||
use self::source::DataControlSourceHandler;
|
use self::source::DataControlSourceHandler;
|
||||||
use crate::clients::wayland::Environment;
|
use crate::clients::wayland::Environment;
|
||||||
use crate::{lock, send, Ironbar};
|
use crate::unique_id::get_unique_usize;
|
||||||
|
use crate::{lock, send};
|
||||||
use device::DataControlDevice;
|
use device::DataControlDevice;
|
||||||
use glib::Bytes;
|
use glib::Bytes;
|
||||||
use nix::fcntl::{fcntl, F_GETPIPE_SZ, F_SETPIPE_SZ};
|
use nix::fcntl::{fcntl, F_GETPIPE_SZ, F_SETPIPE_SZ};
|
||||||
use nix::sys::epoll::{Epoll, EpollCreateFlags, EpollEvent, EpollFlags};
|
use nix::sys::epoll::{Epoll, EpollCreateFlags, EpollEvent, EpollFlags};
|
||||||
use smithay_client_toolkit::data_device_manager::WritePipe;
|
use smithay_client_toolkit::data_device_manager::WritePipe;
|
||||||
use smithay_client_toolkit::reexports::calloop::{PostAction, RegistrationToken};
|
use smithay_client_toolkit::reexports::calloop::RegistrationToken;
|
||||||
use std::cmp::min;
|
use std::cmp::min;
|
||||||
use std::fmt::{Debug, Formatter};
|
use std::fmt::{Debug, Formatter};
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
@@ -144,7 +145,7 @@ impl Environment {
|
|||||||
};
|
};
|
||||||
|
|
||||||
Ok(ClipboardItem {
|
Ok(ClipboardItem {
|
||||||
id: Ironbar::unique_id(),
|
id: get_unique_usize(),
|
||||||
value,
|
value,
|
||||||
mime_type: mime_type.value.clone(),
|
mime_type: mime_type.value.clone(),
|
||||||
})
|
})
|
||||||
@@ -195,31 +196,29 @@ impl DataControlDeviceHandler for Environment {
|
|||||||
let tx = self.clipboard_tx.clone();
|
let tx = self.clipboard_tx.clone();
|
||||||
let clipboard = self.clipboard.clone();
|
let clipboard = self.clipboard.clone();
|
||||||
|
|
||||||
let token =
|
let token = self
|
||||||
self.loop_handle
|
.loop_handle
|
||||||
.insert_source(read_pipe, move |(), file, state| unsafe {
|
.insert_source(read_pipe, move |_, file, state| {
|
||||||
let item = state
|
let item = state
|
||||||
.selection_offers
|
.selection_offers
|
||||||
.iter()
|
.iter()
|
||||||
.position(|o| o.offer == offer_clone)
|
.position(|o| o.offer == offer_clone)
|
||||||
.map(|p| state.selection_offers.remove(p))
|
.map(|p| state.selection_offers.remove(p))
|
||||||
.expect("Failed to find selection offer item");
|
.expect("Failed to find selection offer item");
|
||||||
|
|
||||||
match Self::read_file(&mime_type, file.get_mut()) {
|
match Self::read_file(&mime_type, file) {
|
||||||
Ok(item) => {
|
Ok(item) => {
|
||||||
let item = Arc::new(item);
|
let item = Arc::new(item);
|
||||||
lock!(clipboard).replace(item.clone());
|
lock!(clipboard).replace(item.clone());
|
||||||
send!(tx, item);
|
send!(tx, item);
|
||||||
}
|
|
||||||
Err(err) => error!("{err:?}"),
|
|
||||||
}
|
}
|
||||||
|
Err(err) => error!("{err:?}"),
|
||||||
|
}
|
||||||
|
|
||||||
state
|
state
|
||||||
.loop_handle
|
.loop_handle
|
||||||
.remove(item.token.expect("Missing item token"));
|
.remove(item.token.expect("Missing item token"));
|
||||||
|
});
|
||||||
PostAction::Remove
|
|
||||||
});
|
|
||||||
|
|
||||||
match token {
|
match token {
|
||||||
Ok(token) => {
|
Ok(token) => {
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ use nix::unistd::{close, pipe2};
|
|||||||
use smithay_client_toolkit::data_device_manager::data_offer::DataOfferError;
|
use smithay_client_toolkit::data_device_manager::data_offer::DataOfferError;
|
||||||
use smithay_client_toolkit::data_device_manager::ReadPipe;
|
use smithay_client_toolkit::data_device_manager::ReadPipe;
|
||||||
use std::ops::DerefMut;
|
use std::ops::DerefMut;
|
||||||
use std::os::fd::{BorrowedFd, FromRawFd};
|
use std::os::fd::FromRawFd;
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
use tracing::{trace, warn};
|
use tracing::{trace, warn};
|
||||||
use wayland_client::{Connection, Dispatch, Proxy, QueueHandle};
|
use wayland_client::{Connection, Dispatch, Proxy, QueueHandle};
|
||||||
@@ -37,7 +37,7 @@ impl PartialEq for SelectionOffer {
|
|||||||
|
|
||||||
impl SelectionOffer {
|
impl SelectionOffer {
|
||||||
pub fn receive(&self, mime_type: String) -> Result<ReadPipe, DataOfferError> {
|
pub fn receive(&self, mime_type: String) -> Result<ReadPipe, DataOfferError> {
|
||||||
unsafe { receive(&self.data_offer, mime_type) }.map_err(DataOfferError::Io)
|
receive(&self.data_offer, mime_type).map_err(DataOfferError::Io)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -169,18 +169,15 @@ where
|
|||||||
///
|
///
|
||||||
/// Fails if too many file descriptors were already open and a pipe
|
/// Fails if too many file descriptors were already open and a pipe
|
||||||
/// could not be created.
|
/// could not be created.
|
||||||
pub unsafe fn receive(
|
pub fn receive(offer: &ZwlrDataControlOfferV1, mime_type: String) -> std::io::Result<ReadPipe> {
|
||||||
offer: &ZwlrDataControlOfferV1,
|
|
||||||
mime_type: String,
|
|
||||||
) -> std::io::Result<ReadPipe> {
|
|
||||||
// create a pipe
|
// create a pipe
|
||||||
let (readfd, writefd) = pipe2(OFlag::O_CLOEXEC)?;
|
let (readfd, writefd) = pipe2(OFlag::O_CLOEXEC)?;
|
||||||
|
|
||||||
offer.receive(mime_type, BorrowedFd::borrow_raw(writefd));
|
offer.receive(mime_type, writefd);
|
||||||
|
|
||||||
if let Err(err) = close(writefd) {
|
if let Err(err) = close(writefd) {
|
||||||
warn!("Failed to close write pipe: {}", err);
|
warn!("Failed to close write pipe: {}", err);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(FromRawFd::from_raw_fd(readfd))
|
Ok(unsafe { FromRawFd::from_raw_fd(readfd) })
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
use super::manager::ToplevelManagerState;
|
use super::manager::ToplevelManagerState;
|
||||||
use crate::{lock, Ironbar};
|
use crate::lock;
|
||||||
|
use crate::unique_id::get_unique_usize;
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
use tracing::trace;
|
use tracing::trace;
|
||||||
@@ -67,7 +68,7 @@ pub struct ToplevelInfo {
|
|||||||
impl Default for ToplevelInfo {
|
impl Default for ToplevelInfo {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
id: Ironbar::unique_id(),
|
id: get_unique_usize(),
|
||||||
app_id: String::new(),
|
app_id: String::new(),
|
||||||
title: String::new(),
|
title: String::new(),
|
||||||
fullscreen: false,
|
fullscreen: false,
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
use crate::dynamic_value::{dynamic_string, DynamicBool};
|
use crate::dynamic_value::{dynamic_string, DynamicBool};
|
||||||
use crate::script::{Script, ScriptInput};
|
use crate::script::{Script, ScriptInput};
|
||||||
use glib::Propagation;
|
|
||||||
use gtk::gdk::ScrollDirection;
|
use gtk::gdk::ScrollDirection;
|
||||||
use gtk::prelude::*;
|
use gtk::prelude::*;
|
||||||
use gtk::{EventBox, Orientation, Revealer, RevealerTransitionType};
|
use gtk::{EventBox, Orientation, Revealer, RevealerTransitionType};
|
||||||
@@ -76,7 +75,7 @@ impl CommonConfig {
|
|||||||
script.run_as_oneshot(None);
|
script.run_as_oneshot(None);
|
||||||
}
|
}
|
||||||
|
|
||||||
Propagation::Proceed
|
Inhibit(false)
|
||||||
});
|
});
|
||||||
|
|
||||||
let scroll_up_script = self.on_scroll_up.map(Script::new_polling);
|
let scroll_up_script = self.on_scroll_up.map(Script::new_polling);
|
||||||
@@ -94,7 +93,7 @@ impl CommonConfig {
|
|||||||
script.run_as_oneshot(None);
|
script.run_as_oneshot(None);
|
||||||
}
|
}
|
||||||
|
|
||||||
Propagation::Proceed
|
Inhibit(false)
|
||||||
});
|
});
|
||||||
|
|
||||||
macro_rules! install_oneshot {
|
macro_rules! install_oneshot {
|
||||||
@@ -102,7 +101,7 @@ impl CommonConfig {
|
|||||||
$option.map(Script::new_polling).map(|script| {
|
$option.map(Script::new_polling).map(|script| {
|
||||||
container.$method(move |_, _| {
|
container.$method(move |_, _| {
|
||||||
script.run_as_oneshot(None);
|
script.run_as_oneshot(None);
|
||||||
Propagation::Proceed
|
Inhibit(false)
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
@@ -115,6 +114,7 @@ impl CommonConfig {
|
|||||||
let container = container.clone();
|
let container = container.clone();
|
||||||
dynamic_string(&tooltip, move |string| {
|
dynamic_string(&tooltip, move |string| {
|
||||||
container.set_tooltip_text(Some(&string));
|
container.set_tooltip_text(Some(&string));
|
||||||
|
Continue(true)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -136,6 +136,7 @@ impl CommonConfig {
|
|||||||
container.show_all();
|
container.show_all();
|
||||||
}
|
}
|
||||||
revealer.set_reveal_child(success);
|
revealer.set_reveal_child(success);
|
||||||
|
Continue(true)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ use serde::Deserialize;
|
|||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
pub use self::common::{CommonConfig, TransitionType};
|
pub use self::common::{CommonConfig, TransitionType};
|
||||||
pub use self::truncate::TruncateMode;
|
pub use self::truncate::{EllipsizeMode, TruncateMode};
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Clone)]
|
#[derive(Debug, Deserialize, Clone)]
|
||||||
#[serde(tag = "type", rename_all = "snake_case")]
|
#[serde(tag = "type", rename_all = "snake_case")]
|
||||||
@@ -99,11 +99,6 @@ pub struct Config {
|
|||||||
pub popup_gap: i32,
|
pub popup_gap: i32,
|
||||||
pub name: Option<String>,
|
pub name: Option<String>,
|
||||||
|
|
||||||
#[serde(default)]
|
|
||||||
pub start_hidden: Option<bool>,
|
|
||||||
#[serde(default)]
|
|
||||||
pub autohide: Option<u64>,
|
|
||||||
|
|
||||||
/// GTK icon theme to use.
|
/// GTK icon theme to use.
|
||||||
pub icon_theme: Option<String>,
|
pub icon_theme: Option<String>,
|
||||||
|
|
||||||
@@ -132,8 +127,6 @@ impl Default for Config {
|
|||||||
height: default_bar_height(),
|
height: default_bar_height(),
|
||||||
margin: MarginConfig::default(),
|
margin: MarginConfig::default(),
|
||||||
name: None,
|
name: None,
|
||||||
start_hidden: None,
|
|
||||||
autohide: None,
|
|
||||||
popup_gap: default_popup_gap(),
|
popup_gap: default_popup_gap(),
|
||||||
icon_theme: None,
|
icon_theme: None,
|
||||||
ironvar_defaults: None,
|
ironvar_defaults: None,
|
||||||
|
|||||||
@@ -180,7 +180,7 @@ fn parse_desktop_file(path: &Path) -> Option<DesktopFile> {
|
|||||||
.for_each(|(key, value)| {
|
.for_each(|(key, value)| {
|
||||||
desktop_file
|
desktop_file
|
||||||
.entry(key.to_string())
|
.entry(key.to_string())
|
||||||
.or_default()
|
.or_insert_with(Vec::new)
|
||||||
.push(value.to_string());
|
.push(value.to_string());
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
use crate::script::Script;
|
|
||||||
use crate::{glib_recv_mpsc, spawn, try_send};
|
|
||||||
#[cfg(feature = "ipc")]
|
#[cfg(feature = "ipc")]
|
||||||
use crate::{send_async, Ironbar};
|
use crate::ironvar::get_variable_manager;
|
||||||
|
use crate::script::Script;
|
||||||
|
use crate::send;
|
||||||
use cfg_if::cfg_if;
|
use cfg_if::cfg_if;
|
||||||
|
use glib::Continue;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use tokio::sync::mpsc;
|
use tokio::spawn;
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Clone)]
|
#[derive(Debug, Deserialize, Clone)]
|
||||||
#[serde(untagged)]
|
#[serde(untagged)]
|
||||||
@@ -17,9 +18,9 @@ pub enum DynamicBool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl DynamicBool {
|
impl DynamicBool {
|
||||||
pub fn subscribe<F>(self, mut f: F)
|
pub fn subscribe<F>(self, f: F)
|
||||||
where
|
where
|
||||||
F: FnMut(bool) + 'static,
|
F: FnMut(bool) -> Continue + 'static,
|
||||||
{
|
{
|
||||||
let value = match self {
|
let value = match self {
|
||||||
Self::Unknown(input) => {
|
Self::Unknown(input) => {
|
||||||
@@ -39,29 +40,29 @@ impl DynamicBool {
|
|||||||
_ => self,
|
_ => self,
|
||||||
};
|
};
|
||||||
|
|
||||||
let (tx, rx) = mpsc::channel(32);
|
let (tx, rx) = glib::MainContext::channel(glib::PRIORITY_DEFAULT);
|
||||||
|
|
||||||
glib_recv_mpsc!(rx, val => f(val));
|
rx.attach(None, f);
|
||||||
|
|
||||||
spawn(async move {
|
spawn(async move {
|
||||||
match value {
|
match value {
|
||||||
DynamicBool::Script(script) => {
|
DynamicBool::Script(script) => {
|
||||||
script
|
script
|
||||||
.run(None, |_, success| {
|
.run(None, |_, success| {
|
||||||
try_send!(tx, success);
|
send!(tx, success);
|
||||||
})
|
})
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
#[cfg(feature = "ipc")]
|
#[cfg(feature = "ipc")]
|
||||||
DynamicBool::Variable(variable) => {
|
DynamicBool::Variable(variable) => {
|
||||||
let variable_manager = Ironbar::variable_manager();
|
let variable_manager = get_variable_manager();
|
||||||
|
|
||||||
let variable_name = variable[1..].into(); // remove hash
|
let variable_name = variable[1..].into(); // remove hash
|
||||||
let mut rx = crate::write_lock!(variable_manager).subscribe(variable_name);
|
let mut rx = crate::write_lock!(variable_manager).subscribe(variable_name);
|
||||||
|
|
||||||
while let Ok(value) = rx.recv().await {
|
while let Ok(value) = rx.recv().await {
|
||||||
let has_value = value.map(|s| is_truthy(&s)).unwrap_or_default();
|
let has_value = value.map(|s| is_truthy(&s)).unwrap_or_default();
|
||||||
send_async!(tx, has_value);
|
send!(tx, has_value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
DynamicBool::Unknown(_) => unreachable!(),
|
DynamicBool::Unknown(_) => unreachable!(),
|
||||||
@@ -70,10 +71,7 @@ impl DynamicBool {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Check if a string ironvar is 'truthy',
|
/// Check if a string ironvar is 'truthy'
|
||||||
/// i.e should be evaluated to true.
|
|
||||||
///
|
|
||||||
/// This loosely follows the common JavaScript cases.
|
|
||||||
#[cfg(feature = "ipc")]
|
#[cfg(feature = "ipc")]
|
||||||
fn is_truthy(string: &str) -> bool {
|
fn is_truthy(string: &str) -> bool {
|
||||||
!(string.is_empty() || string == "0" || string == "false")
|
!(string.is_empty() || string == "0" || string == "false")
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
use crate::script::{OutputStream, Script};
|
|
||||||
#[cfg(feature = "ipc")]
|
#[cfg(feature = "ipc")]
|
||||||
use crate::Ironbar;
|
use crate::ironvar::get_variable_manager;
|
||||||
use crate::{arc_mut, glib_recv_mpsc, lock, spawn, try_send};
|
use crate::script::{OutputStream, Script};
|
||||||
use tokio::sync::mpsc;
|
use crate::{arc_mut, lock, send};
|
||||||
|
use gtk::prelude::*;
|
||||||
|
use tokio::spawn;
|
||||||
|
|
||||||
/// A segment of a dynamic string,
|
/// A segment of a dynamic string,
|
||||||
/// containing either a static string
|
/// containing either a static string
|
||||||
@@ -23,16 +24,17 @@ enum DynamicStringSegment {
|
|||||||
/// ```rs
|
/// ```rs
|
||||||
/// dynamic_string(&text, move |string| {
|
/// dynamic_string(&text, move |string| {
|
||||||
/// label.set_markup(&string);
|
/// label.set_markup(&string);
|
||||||
|
/// Continue(true)
|
||||||
/// });
|
/// });
|
||||||
/// ```
|
/// ```
|
||||||
pub fn dynamic_string<F>(input: &str, mut f: F)
|
pub fn dynamic_string<F>(input: &str, f: F)
|
||||||
where
|
where
|
||||||
F: FnMut(String) + 'static,
|
F: FnMut(String) -> Continue + 'static,
|
||||||
{
|
{
|
||||||
let tokens = parse_input(input);
|
let tokens = parse_input(input);
|
||||||
|
|
||||||
let label_parts = arc_mut!(vec![]);
|
let label_parts = arc_mut!(vec![]);
|
||||||
let (tx, rx) = mpsc::channel(32);
|
let (tx, rx) = glib::MainContext::channel(glib::PRIORITY_DEFAULT);
|
||||||
|
|
||||||
for (i, segment) in tokens.into_iter().enumerate() {
|
for (i, segment) in tokens.into_iter().enumerate() {
|
||||||
match segment {
|
match segment {
|
||||||
@@ -55,7 +57,7 @@ where
|
|||||||
let _: String = std::mem::replace(&mut label_parts[i], out);
|
let _: String = std::mem::replace(&mut label_parts[i], out);
|
||||||
|
|
||||||
let string = label_parts.join("");
|
let string = label_parts.join("");
|
||||||
try_send!(tx, string);
|
send!(tx, string);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.await;
|
.await;
|
||||||
@@ -70,7 +72,7 @@ where
|
|||||||
lock!(label_parts).push(String::new());
|
lock!(label_parts).push(String::new());
|
||||||
|
|
||||||
spawn(async move {
|
spawn(async move {
|
||||||
let variable_manager = Ironbar::variable_manager();
|
let variable_manager = get_variable_manager();
|
||||||
let mut rx = crate::write_lock!(variable_manager).subscribe(name);
|
let mut rx = crate::write_lock!(variable_manager).subscribe(name);
|
||||||
|
|
||||||
while let Ok(value) = rx.recv().await {
|
while let Ok(value) = rx.recv().await {
|
||||||
@@ -80,7 +82,7 @@ where
|
|||||||
let _: String = std::mem::replace(&mut label_parts[i], value);
|
let _: String = std::mem::replace(&mut label_parts[i], value);
|
||||||
|
|
||||||
let string = label_parts.join("");
|
let string = label_parts.join("");
|
||||||
try_send!(tx, string);
|
send!(tx, string);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -88,12 +90,12 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
glib_recv_mpsc!(rx , val => f(val));
|
rx.attach(None, f);
|
||||||
|
|
||||||
// initialize
|
// initialize
|
||||||
{
|
{
|
||||||
let label_parts = lock!(label_parts).join("");
|
let label_parts = lock!(label_parts).join("");
|
||||||
try_send!(tx, label_parts);
|
send!(tx, label_parts);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
43
src/global_state.rs
Normal file
43
src/global_state.rs
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
use crate::popup::Popup;
|
||||||
|
use std::cell::{RefCell, RefMut};
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::rc::Rc;
|
||||||
|
|
||||||
|
/// Global application state shared across all bars.
|
||||||
|
///
|
||||||
|
/// Data that needs to be accessed from anywhere
|
||||||
|
/// that is not otherwise accessible should be placed on here.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct GlobalState {
|
||||||
|
popups: HashMap<Box<str>, Rc<RefCell<Popup>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GlobalState {
|
||||||
|
pub(crate) fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
popups: HashMap::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn popups(&self) -> &HashMap<Box<str>, Rc<RefCell<Popup>>> {
|
||||||
|
&self.popups
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn popups_mut(&mut self) -> &mut HashMap<Box<str>, Rc<RefCell<Popup>>> {
|
||||||
|
&mut self.popups
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_popup_mut<F, T>(&self, monitor_name: &str, f: F) -> Option<T>
|
||||||
|
where
|
||||||
|
F: FnOnce(RefMut<Popup>) -> T,
|
||||||
|
{
|
||||||
|
let popup = self.popups().get(monitor_name);
|
||||||
|
|
||||||
|
if let Some(popup) = popup {
|
||||||
|
let popup = popup.borrow_mut();
|
||||||
|
Some(f(popup))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,4 @@
|
|||||||
use crate::desktop_file::get_desktop_icon_name;
|
use crate::desktop_file::get_desktop_icon_name;
|
||||||
#[cfg(feature = "http")]
|
|
||||||
use crate::{glib_recv_mpsc, send_async, spawn};
|
|
||||||
use cfg_if::cfg_if;
|
use cfg_if::cfg_if;
|
||||||
use color_eyre::{Help, Report, Result};
|
use color_eyre::{Help, Report, Result};
|
||||||
use gtk::cairo::Surface;
|
use gtk::cairo::Surface;
|
||||||
@@ -9,13 +7,13 @@ use gtk::gdk_pixbuf::Pixbuf;
|
|||||||
use gtk::prelude::*;
|
use gtk::prelude::*;
|
||||||
use gtk::{IconLookupFlags, IconTheme};
|
use gtk::{IconLookupFlags, IconTheme};
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
#[cfg(feature = "http")]
|
|
||||||
use tokio::sync::mpsc;
|
|
||||||
use tracing::warn;
|
use tracing::warn;
|
||||||
|
|
||||||
cfg_if!(
|
cfg_if!(
|
||||||
if #[cfg(feature = "http")] {
|
if #[cfg(feature = "http")] {
|
||||||
|
use crate::send;
|
||||||
use gtk::gio::{Cancellable, MemoryInputStream};
|
use gtk::gio::{Cancellable, MemoryInputStream};
|
||||||
|
use tokio::spawn;
|
||||||
use tracing::error;
|
use tracing::error;
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
@@ -145,18 +143,18 @@ impl<'a> ImageProvider<'a> {
|
|||||||
#[cfg(feature = "http")]
|
#[cfg(feature = "http")]
|
||||||
if let ImageLocation::Remote(url) = &self.location {
|
if let ImageLocation::Remote(url) = &self.location {
|
||||||
let url = url.clone();
|
let url = url.clone();
|
||||||
let (tx, rx) = mpsc::channel(64);
|
let (tx, rx) = glib::MainContext::channel(glib::PRIORITY_DEFAULT);
|
||||||
|
|
||||||
spawn(async move {
|
spawn(async move {
|
||||||
let bytes = Self::get_bytes_from_http(url).await;
|
let bytes = Self::get_bytes_from_http(url).await;
|
||||||
if let Ok(bytes) = bytes {
|
if let Ok(bytes) = bytes {
|
||||||
send_async!(tx, bytes);
|
send!(tx, bytes);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
{
|
{
|
||||||
let size = self.size;
|
let size = self.size;
|
||||||
glib_recv_mpsc!(rx, bytes => {
|
rx.attach(None, move |bytes| {
|
||||||
let stream = MemoryInputStream::from_bytes(&bytes);
|
let stream = MemoryInputStream::from_bytes(&bytes);
|
||||||
|
|
||||||
let scale = image.scale_factor();
|
let scale = image.scale_factor();
|
||||||
@@ -177,6 +175,8 @@ impl<'a> ImageProvider<'a> {
|
|||||||
Err(err) => error!("{err:?}"),
|
Err(err) => error!("{err:?}"),
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Continue(false)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -3,21 +3,25 @@ pub mod commands;
|
|||||||
pub mod responses;
|
pub mod responses;
|
||||||
mod server;
|
mod server;
|
||||||
|
|
||||||
|
use std::cell::RefCell;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
use std::rc::Rc;
|
||||||
use tracing::warn;
|
use tracing::warn;
|
||||||
|
|
||||||
|
use crate::GlobalState;
|
||||||
pub use commands::Command;
|
pub use commands::Command;
|
||||||
pub use responses::Response;
|
pub use responses::Response;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Ipc {
|
pub struct Ipc {
|
||||||
path: PathBuf,
|
path: PathBuf,
|
||||||
|
global_state: Rc<RefCell<GlobalState>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Ipc {
|
impl Ipc {
|
||||||
/// Creates a new IPC instance.
|
/// Creates a new IPC instance.
|
||||||
/// This can be used as both a server and client.
|
/// This can be used as both a server and client.
|
||||||
pub fn new() -> Self {
|
pub fn new(global_state: Rc<RefCell<GlobalState>>) -> Self {
|
||||||
let ipc_socket_file = std::env::var("XDG_RUNTIME_DIR")
|
let ipc_socket_file = std::env::var("XDG_RUNTIME_DIR")
|
||||||
.map_or_else(|_| PathBuf::from("/tmp"), PathBuf::from)
|
.map_or_else(|_| PathBuf::from("/tmp"), PathBuf::from)
|
||||||
.join("ironbar-ipc.sock");
|
.join("ironbar-ipc.sock");
|
||||||
@@ -28,6 +32,7 @@ impl Ipc {
|
|||||||
|
|
||||||
Self {
|
Self {
|
||||||
path: ipc_socket_file,
|
path: ipc_socket_file,
|
||||||
|
global_state,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,19 +1,24 @@
|
|||||||
|
use std::cell::RefCell;
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
use color_eyre::{Report, Result};
|
use color_eyre::{Report, Result};
|
||||||
|
use glib::Continue;
|
||||||
use gtk::prelude::*;
|
use gtk::prelude::*;
|
||||||
use gtk::Application;
|
use gtk::Application;
|
||||||
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
||||||
use tokio::net::{UnixListener, UnixStream};
|
use tokio::net::{UnixListener, UnixStream};
|
||||||
|
use tokio::spawn;
|
||||||
use tokio::sync::mpsc::{self, Receiver, Sender};
|
use tokio::sync::mpsc::{self, Receiver, Sender};
|
||||||
use tracing::{debug, error, info, warn};
|
use tracing::{debug, error, info, warn};
|
||||||
|
|
||||||
|
use crate::bridge_channel::BridgeChannel;
|
||||||
use crate::ipc::{Command, Response};
|
use crate::ipc::{Command, Response};
|
||||||
|
use crate::ironvar::get_variable_manager;
|
||||||
use crate::modules::PopupButton;
|
use crate::modules::PopupButton;
|
||||||
use crate::style::load_css;
|
use crate::style::load_css;
|
||||||
use crate::{glib_recv_mpsc, read_lock, send_async, spawn, try_send, write_lock, Ironbar};
|
use crate::{await_sync, read_lock, send_async, try_send, write_lock, GlobalState};
|
||||||
|
|
||||||
use super::Ipc;
|
use super::Ipc;
|
||||||
|
|
||||||
@@ -21,8 +26,9 @@ impl Ipc {
|
|||||||
/// Starts the IPC server on its socket.
|
/// Starts the IPC server on its socket.
|
||||||
///
|
///
|
||||||
/// Once started, the server will begin accepting connections.
|
/// Once started, the server will begin accepting connections.
|
||||||
pub fn start(&self, application: &Application, ironbar: Rc<Ironbar>) {
|
pub fn start(&self, application: &Application) {
|
||||||
let (cmd_tx, cmd_rx) = mpsc::channel(32);
|
let bridge = BridgeChannel::<Command>::new();
|
||||||
|
let cmd_tx = bridge.create_sender();
|
||||||
let (res_tx, mut res_rx) = mpsc::channel(32);
|
let (res_tx, mut res_rx) = mpsc::channel(32);
|
||||||
|
|
||||||
let path = self.path.clone();
|
let path = self.path.clone();
|
||||||
@@ -64,9 +70,11 @@ impl Ipc {
|
|||||||
});
|
});
|
||||||
|
|
||||||
let application = application.clone();
|
let application = application.clone();
|
||||||
glib_recv_mpsc!(cmd_rx, command => {
|
let global_state = self.global_state.clone();
|
||||||
let res = Self::handle_command(command, &application, &ironbar);
|
bridge.recv(move |command| {
|
||||||
|
let res = Self::handle_command(command, &application, &global_state);
|
||||||
try_send!(res_tx, res);
|
try_send!(res_tx, res);
|
||||||
|
Continue(true)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -104,33 +112,30 @@ impl Ipc {
|
|||||||
/// Takes an input command, runs it and returns with the appropriate response.
|
/// Takes an input command, runs it and returns with the appropriate response.
|
||||||
///
|
///
|
||||||
/// This runs on the main thread, allowing commands to interact with GTK.
|
/// This runs on the main thread, allowing commands to interact with GTK.
|
||||||
fn handle_command(command: Command, application: &Application, ironbar: &Ironbar) -> Response {
|
fn handle_command(
|
||||||
|
command: Command,
|
||||||
|
application: &Application,
|
||||||
|
global_state: &Rc<RefCell<GlobalState>>,
|
||||||
|
) -> Response {
|
||||||
match command {
|
match command {
|
||||||
Command::Inspect => {
|
Command::Inspect => {
|
||||||
gtk::Window::set_interactive_debugging(true);
|
gtk::Window::set_interactive_debugging(true);
|
||||||
Response::Ok
|
Response::Ok
|
||||||
}
|
}
|
||||||
Command::Reload => {
|
Command::Reload => {
|
||||||
info!("Closing existing bars");
|
await_sync(async move { crate::reload(application, global_state).await }).unwrap();
|
||||||
let windows = application.windows();
|
|
||||||
for window in windows {
|
|
||||||
window.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
*ironbar.bars.borrow_mut() = crate::load_interface(application);
|
|
||||||
|
|
||||||
Response::Ok
|
Response::Ok
|
||||||
}
|
}
|
||||||
Command::Set { key, value } => {
|
Command::Set { key, value } => {
|
||||||
let variable_manager = Ironbar::variable_manager();
|
let variable_manager = get_variable_manager();
|
||||||
let mut variable_manager = write_lock!(variable_manager);
|
let mut variable_manager = write_lock!(variable_manager);
|
||||||
match variable_manager.set(key, value) {
|
match variable_manager.set(key, value) {
|
||||||
Ok(()) => Response::Ok,
|
Ok(_) => Response::Ok,
|
||||||
Err(err) => Response::error(&format!("{err}")),
|
Err(err) => Response::error(&format!("{err}")),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Command::Get { key } => {
|
Command::Get { key } => {
|
||||||
let variable_manager = Ironbar::variable_manager();
|
let variable_manager = get_variable_manager();
|
||||||
let value = read_lock!(variable_manager).get(&key);
|
let value = read_lock!(variable_manager).get(&key);
|
||||||
match value {
|
match value {
|
||||||
Some(value) => Response::OkValue { value },
|
Some(value) => Response::OkValue { value },
|
||||||
@@ -146,85 +151,68 @@ impl Ipc {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Command::TogglePopup { bar_name, name } => {
|
Command::TogglePopup { bar_name, name } => {
|
||||||
let bar = ironbar.bar_by_name(&bar_name);
|
let global_state = global_state.borrow();
|
||||||
|
let response = global_state.with_popup_mut(&bar_name, |mut popup| {
|
||||||
|
let current_widget = popup.current_widget();
|
||||||
|
popup.hide();
|
||||||
|
|
||||||
match bar {
|
let data = popup
|
||||||
Some(bar) => {
|
.cache
|
||||||
let popup = bar.popup();
|
.iter()
|
||||||
let current_widget = popup.borrow().current_widget();
|
.find(|(_, (module_name, _))| module_name == &name)
|
||||||
|
.map(|module| (module, module.1 .1.buttons.first()));
|
||||||
|
|
||||||
popup.borrow_mut().hide();
|
match data {
|
||||||
|
Some(((&id, _), Some(button))) if current_widget != Some(id) => {
|
||||||
|
let button_id = button.popup_id();
|
||||||
|
popup.show(id, button_id);
|
||||||
|
|
||||||
let data = popup
|
Response::Ok
|
||||||
.borrow()
|
|
||||||
.cache
|
|
||||||
.iter()
|
|
||||||
.find(|(_, value)| value.name == name)
|
|
||||||
.map(|(id, value)| (*id, value.content.buttons.first().cloned()));
|
|
||||||
|
|
||||||
match data {
|
|
||||||
Some((id, Some(button))) if current_widget != Some(id) => {
|
|
||||||
let button_id = button.popup_id();
|
|
||||||
let mut popup = popup.borrow_mut();
|
|
||||||
|
|
||||||
if popup.is_visible() {
|
|
||||||
popup.hide();
|
|
||||||
} else {
|
|
||||||
popup.show(id, button_id);
|
|
||||||
}
|
|
||||||
|
|
||||||
Response::Ok
|
|
||||||
}
|
|
||||||
Some((_, None)) => Response::error("Module has no popup functionality"),
|
|
||||||
Some(_) => Response::Ok,
|
|
||||||
None => Response::error("Invalid module name"),
|
|
||||||
}
|
}
|
||||||
|
Some((_, None)) => Response::error("Module has no popup functionality"),
|
||||||
|
Some(_) => Response::Ok,
|
||||||
|
None => Response::error("Invalid module name"),
|
||||||
}
|
}
|
||||||
None => Response::error("Invalid bar name"),
|
});
|
||||||
}
|
|
||||||
|
response.unwrap_or_else(|| Response::error("Invalid monitor name"))
|
||||||
}
|
}
|
||||||
Command::OpenPopup { bar_name, name } => {
|
Command::OpenPopup { bar_name, name } => {
|
||||||
let bar = ironbar.bar_by_name(&bar_name);
|
let global_state = global_state.borrow();
|
||||||
|
let response = global_state.with_popup_mut(&bar_name, |mut popup| {
|
||||||
|
// only one popup per bar, so hide if open for another widget
|
||||||
|
popup.hide();
|
||||||
|
|
||||||
match bar {
|
let data = popup
|
||||||
Some(bar) => {
|
.cache
|
||||||
let popup = bar.popup();
|
.iter()
|
||||||
|
.find(|(_, (module_name, _))| module_name == &name)
|
||||||
|
.map(|module| (module, module.1 .1.buttons.first()));
|
||||||
|
|
||||||
// only one popup per bar, so hide if open for another widget
|
match data {
|
||||||
popup.borrow_mut().hide();
|
Some(((&id, _), Some(button))) => {
|
||||||
|
let button_id = button.popup_id();
|
||||||
|
popup.show(id, button_id);
|
||||||
|
|
||||||
let data = popup
|
Response::Ok
|
||||||
.borrow()
|
|
||||||
.cache
|
|
||||||
.iter()
|
|
||||||
.find(|(_, value)| value.name == name)
|
|
||||||
.map(|(id, value)| (*id, value.content.buttons.first().cloned()));
|
|
||||||
|
|
||||||
match data {
|
|
||||||
Some((id, Some(button))) => {
|
|
||||||
let button_id = button.popup_id();
|
|
||||||
popup.borrow_mut().show(id, button_id);
|
|
||||||
|
|
||||||
Response::Ok
|
|
||||||
}
|
|
||||||
Some((_, None)) => Response::error("Module has no popup functionality"),
|
|
||||||
None => Response::error("Invalid module name"),
|
|
||||||
}
|
}
|
||||||
|
Some((_, None)) => Response::error("Module has no popup functionality"),
|
||||||
|
None => Response::error("Invalid module name"),
|
||||||
}
|
}
|
||||||
None => Response::error("Invalid bar name"),
|
});
|
||||||
}
|
|
||||||
|
response.unwrap_or_else(|| Response::error("Invalid monitor name"))
|
||||||
}
|
}
|
||||||
Command::ClosePopup { bar_name } => {
|
Command::ClosePopup { bar_name } => {
|
||||||
let bar = ironbar.bar_by_name(&bar_name);
|
let global_state = global_state.borrow();
|
||||||
|
let popup_found = global_state
|
||||||
|
.with_popup_mut(&bar_name, |mut popup| popup.hide())
|
||||||
|
.is_some();
|
||||||
|
|
||||||
match bar {
|
if popup_found {
|
||||||
Some(bar) => {
|
Response::Ok
|
||||||
let popup = bar.popup();
|
} else {
|
||||||
popup.borrow_mut().hide();
|
Response::error("Invalid monitor name")
|
||||||
|
|
||||||
Response::Ok
|
|
||||||
}
|
|
||||||
None => Response::error("Invalid bar name"),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Command::Ping => Response::Ok,
|
Command::Ping => Response::Ok,
|
||||||
|
|||||||
@@ -1,21 +1,25 @@
|
|||||||
#![doc = include_str!("../docs/Ironvars.md")]
|
#![doc = include_str!("../docs/Ironvars.md")]
|
||||||
|
|
||||||
use crate::send;
|
use crate::{arc_rw, send};
|
||||||
use color_eyre::{Report, Result};
|
use color_eyre::{Report, Result};
|
||||||
|
use lazy_static::lazy_static;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
use std::sync::{Arc, RwLock};
|
||||||
use tokio::sync::broadcast;
|
use tokio::sync::broadcast;
|
||||||
|
|
||||||
|
lazy_static! {
|
||||||
|
static ref VARIABLE_MANAGER: Arc<RwLock<VariableManager>> = arc_rw!(VariableManager::new());
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_variable_manager() -> Arc<RwLock<VariableManager>> {
|
||||||
|
VARIABLE_MANAGER.clone()
|
||||||
|
}
|
||||||
|
|
||||||
/// Global singleton manager for `IronVar` variables.
|
/// Global singleton manager for `IronVar` variables.
|
||||||
pub struct VariableManager {
|
pub struct VariableManager {
|
||||||
variables: HashMap<Box<str>, IronVar>,
|
variables: HashMap<Box<str>, IronVar>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for VariableManager {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self::new()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl VariableManager {
|
impl VariableManager {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
|||||||
@@ -43,59 +43,6 @@ macro_rules! try_send {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Spawns a `GLib` future on the local thread, and calls `rx.recv()`
|
|
||||||
/// in a loop.
|
|
||||||
///
|
|
||||||
/// This allows use of `GObjects` and futures in the same context.
|
|
||||||
///
|
|
||||||
/// For use with receivers which return a `Result`.
|
|
||||||
///
|
|
||||||
/// # Example
|
|
||||||
///
|
|
||||||
/// ```rs
|
|
||||||
/// let (tx, mut rx) = broadcast::channel(32);
|
|
||||||
/// glib_recv(rx, msg => println!("{msg}"));
|
|
||||||
/// ```
|
|
||||||
#[macro_export]
|
|
||||||
macro_rules! glib_recv {
|
|
||||||
($rx:expr, $val:ident => $expr:expr) => {{
|
|
||||||
glib::spawn_future_local(async move {
|
|
||||||
// re-delcare in case ie `context.subscribe()` is passed directly
|
|
||||||
let mut rx = $rx;
|
|
||||||
while let Ok($val) = rx.recv().await {
|
|
||||||
$expr
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}};
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Spawns a `GLib` future on the local thread, and calls `rx.recv()`
|
|
||||||
/// in a loop.
|
|
||||||
///
|
|
||||||
/// This allows use of `GObjects` and futures in the same context.
|
|
||||||
///
|
|
||||||
/// For use with receivers which return an `Option`,
|
|
||||||
/// such as Tokio's `mpsc` channel.
|
|
||||||
///
|
|
||||||
/// # Example
|
|
||||||
///
|
|
||||||
/// ```rs
|
|
||||||
/// let (tx, mut rx) = broadcast::channel(32);
|
|
||||||
/// glib_recv_mpsc(rx, msg => println!("{msg}"));
|
|
||||||
/// ```
|
|
||||||
#[macro_export]
|
|
||||||
macro_rules! glib_recv_mpsc {
|
|
||||||
($rx:expr, $val:ident => $expr:expr) => {{
|
|
||||||
glib::spawn_future_local(async move {
|
|
||||||
// re-delcare in case ie `context.subscribe()` is passed directly
|
|
||||||
let mut rx = $rx;
|
|
||||||
while let Some($val) = rx.recv().await {
|
|
||||||
$expr
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}};
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Locks a `Mutex`.
|
/// Locks a `Mutex`.
|
||||||
/// Panics if the `Mutex` cannot be locked.
|
/// Panics if the `Mutex` cannot be locked.
|
||||||
///
|
///
|
||||||
|
|||||||
428
src/main.rs
428
src/main.rs
@@ -1,15 +1,12 @@
|
|||||||
#![doc = include_str!("../README.md")]
|
#![doc = include_str!("../README.md")]
|
||||||
|
|
||||||
use std::cell::RefCell;
|
use std::cell::{Cell, RefCell};
|
||||||
use std::env;
|
use std::env;
|
||||||
use std::future::Future;
|
use std::future::Future;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::process::exit;
|
use std::process::exit;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
|
use std::sync::mpsc;
|
||||||
#[cfg(feature = "ipc")]
|
|
||||||
use std::sync::RwLock;
|
|
||||||
use std::sync::{mpsc, Arc};
|
|
||||||
|
|
||||||
use cfg_if::cfg_if;
|
use cfg_if::cfg_if;
|
||||||
#[cfg(feature = "cli")]
|
#[cfg(feature = "cli")]
|
||||||
@@ -17,25 +14,29 @@ use clap::Parser;
|
|||||||
use color_eyre::eyre::Result;
|
use color_eyre::eyre::Result;
|
||||||
use color_eyre::Report;
|
use color_eyre::Report;
|
||||||
use dirs::config_dir;
|
use dirs::config_dir;
|
||||||
use glib::PropertySet;
|
use gtk::gdk::{Display, Monitor};
|
||||||
use gtk::gdk::Display;
|
|
||||||
use gtk::prelude::*;
|
use gtk::prelude::*;
|
||||||
use gtk::Application;
|
use gtk::Application;
|
||||||
use tokio::runtime::Runtime;
|
use smithay_client_toolkit::output::OutputInfo;
|
||||||
use tokio::task::{block_in_place, JoinHandle};
|
use tokio::runtime::Handle;
|
||||||
|
use tokio::spawn;
|
||||||
|
use tokio::task::{block_in_place, spawn_blocking};
|
||||||
use tracing::{debug, error, info, warn};
|
use tracing::{debug, error, info, warn};
|
||||||
use universal_config::ConfigLoader;
|
use universal_config::ConfigLoader;
|
||||||
|
|
||||||
use clients::wayland;
|
use clients::wayland;
|
||||||
|
|
||||||
use crate::bar::{create_bar, Bar};
|
use crate::bar::create_bar;
|
||||||
|
use crate::bridge_channel::BridgeChannel;
|
||||||
|
use crate::cached_broadcast::Event;
|
||||||
use crate::config::{Config, MonitorConfig};
|
use crate::config::{Config, MonitorConfig};
|
||||||
use crate::error::ExitCode;
|
use crate::error::ExitCode;
|
||||||
#[cfg(feature = "ipc")]
|
use crate::global_state::GlobalState;
|
||||||
use crate::ironvar::VariableManager;
|
|
||||||
use crate::style::load_css;
|
use crate::style::load_css;
|
||||||
|
|
||||||
mod bar;
|
mod bar;
|
||||||
|
mod bridge_channel;
|
||||||
|
mod cached_broadcast;
|
||||||
#[cfg(feature = "cli")]
|
#[cfg(feature = "cli")]
|
||||||
mod cli;
|
mod cli;
|
||||||
mod clients;
|
mod clients;
|
||||||
@@ -43,6 +44,7 @@ mod config;
|
|||||||
mod desktop_file;
|
mod desktop_file;
|
||||||
mod dynamic_value;
|
mod dynamic_value;
|
||||||
mod error;
|
mod error;
|
||||||
|
mod global_state;
|
||||||
mod gtk_helpers;
|
mod gtk_helpers;
|
||||||
mod image;
|
mod image;
|
||||||
#[cfg(feature = "ipc")]
|
#[cfg(feature = "ipc")]
|
||||||
@@ -55,190 +57,227 @@ mod modules;
|
|||||||
mod popup;
|
mod popup;
|
||||||
mod script;
|
mod script;
|
||||||
mod style;
|
mod style;
|
||||||
|
mod unique_id;
|
||||||
|
|
||||||
const GTK_APP_ID: &str = "dev.jstanger.ironbar";
|
const GTK_APP_ID: &str = "dev.jstanger.ironbar";
|
||||||
const VERSION: &str = env!("CARGO_PKG_VERSION");
|
const VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||||
|
|
||||||
fn main() {
|
#[tokio::main]
|
||||||
|
async fn main() {
|
||||||
let _guard = logging::install_logging();
|
let _guard = logging::install_logging();
|
||||||
|
|
||||||
|
let global_state = Rc::new(RefCell::new(GlobalState::new()));
|
||||||
|
|
||||||
cfg_if! {
|
cfg_if! {
|
||||||
if #[cfg(feature = "cli")] {
|
if #[cfg(feature = "cli")] {
|
||||||
run_with_args();
|
run_with_args(global_state).await;
|
||||||
} else {
|
} else {
|
||||||
start_ironbar();
|
start_ironbar(global_state);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "cli")]
|
#[cfg(feature = "cli")]
|
||||||
fn run_with_args() {
|
async fn run_with_args(global_state: Rc<RefCell<GlobalState>>) {
|
||||||
let args = cli::Args::parse();
|
let args = cli::Args::parse();
|
||||||
|
|
||||||
match args.command {
|
match args.command {
|
||||||
Some(command) => {
|
Some(command) => {
|
||||||
let rt = create_runtime();
|
let ipc = ipc::Ipc::new(global_state);
|
||||||
rt.block_on(async move {
|
match ipc.send(command).await {
|
||||||
let ipc = ipc::Ipc::new();
|
Ok(res) => cli::handle_response(res),
|
||||||
match ipc.send(command).await {
|
Err(err) => error!("{err:?}"),
|
||||||
Ok(res) => cli::handle_response(res),
|
};
|
||||||
Err(err) => error!("{err:?}"),
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
None => start_ironbar(),
|
None => start_ironbar(global_state).await,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static COUNTER: AtomicUsize = AtomicUsize::new(1);
|
async fn start_ironbar(global_state: Rc<RefCell<GlobalState>>) {
|
||||||
|
info!("Ironbar version {}", VERSION);
|
||||||
|
info!("Starting application");
|
||||||
|
|
||||||
lazy_static::lazy_static! {
|
let app = Application::builder().application_id(GTK_APP_ID).build();
|
||||||
static ref RUNTIME: Arc<Runtime> = Arc::new(create_runtime());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "ipc")]
|
let output_bridge = BridgeChannel::<Event<OutputInfo>>::new();
|
||||||
lazy_static::lazy_static! {
|
let output_tx = output_bridge.create_sender();
|
||||||
static ref VARIABLE_MANAGER: Arc<RwLock<VariableManager>> = arc_rw!(VariableManager::new());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
let running = Rc::new(Cell::new(false));
|
||||||
pub struct Ironbar {
|
|
||||||
bars: Rc<RefCell<Vec<Bar>>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Ironbar {
|
let global_state2 = global_state.clone();
|
||||||
fn new() -> Self {
|
app.connect_activate(move |app| {
|
||||||
Self {
|
if running.get() {
|
||||||
bars: Rc::new(RefCell::new(vec![])),
|
info!("Ironbar already running, returning");
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
fn start(self) {
|
running.set(true);
|
||||||
info!("Ironbar version {}", VERSION);
|
|
||||||
info!("Starting application");
|
|
||||||
|
|
||||||
let app = Application::builder().application_id(GTK_APP_ID).build();
|
cfg_if! {
|
||||||
|
if #[cfg(feature = "ipc")] {
|
||||||
let running = AtomicBool::new(false);
|
let ipc = ipc::Ipc::new(global_state2.clone());
|
||||||
|
ipc.start(app);
|
||||||
let instance = Rc::new(self);
|
|
||||||
|
|
||||||
// force start wayland client ahead of ui
|
|
||||||
let wl = wayland::get_client();
|
|
||||||
lock!(wl).roundtrip();
|
|
||||||
|
|
||||||
app.connect_activate(move |app| {
|
|
||||||
if running.load(Ordering::Relaxed) {
|
|
||||||
info!("Ironbar already running, returning");
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
running.set(true);
|
let style_path = env::var("IRONBAR_CSS").ok().map_or_else(
|
||||||
|
|| {
|
||||||
|
config_dir().map_or_else(
|
||||||
|
|| {
|
||||||
|
let report = Report::msg("Failed to locate user config dir");
|
||||||
|
error!("{:?}", report);
|
||||||
|
exit(ExitCode::CreateBars as i32);
|
||||||
|
},
|
||||||
|
|dir| dir.join("ironbar").join("style.css"),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
PathBuf::from,
|
||||||
|
);
|
||||||
|
|
||||||
cfg_if! {
|
if style_path.exists() {
|
||||||
if #[cfg(feature = "ipc")] {
|
load_css(style_path);
|
||||||
let ipc = ipc::Ipc::new();
|
}
|
||||||
ipc.start(app, instance.clone());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
*instance.bars.borrow_mut() = load_interface(app);
|
let (tx, rx) = mpsc::channel();
|
||||||
|
|
||||||
let style_path = env::var("IRONBAR_CSS").ok().map_or_else(
|
#[cfg(feature = "ipc")]
|
||||||
|| {
|
let ipc_path = ipc.path().to_path_buf();
|
||||||
config_dir().map_or_else(
|
spawn_blocking(move || {
|
||||||
|| {
|
rx.recv().expect("to receive from channel");
|
||||||
let report = Report::msg("Failed to locate user config dir");
|
|
||||||
error!("{:?}", report);
|
|
||||||
exit(ExitCode::CreateBars as i32);
|
|
||||||
},
|
|
||||||
|dir| dir.join("ironbar").join("style.css"),
|
|
||||||
)
|
|
||||||
},
|
|
||||||
PathBuf::from,
|
|
||||||
);
|
|
||||||
|
|
||||||
if style_path.exists() {
|
info!("Shutting down");
|
||||||
load_css(style_path);
|
|
||||||
}
|
|
||||||
|
|
||||||
let (tx, rx) = mpsc::channel();
|
|
||||||
|
|
||||||
#[cfg(feature = "ipc")]
|
#[cfg(feature = "ipc")]
|
||||||
let ipc_path = ipc.path().to_path_buf();
|
ipc::Ipc::shutdown(ipc_path);
|
||||||
spawn_blocking(move || {
|
|
||||||
rx.recv().expect("to receive from channel");
|
|
||||||
|
|
||||||
info!("Shutting down");
|
exit(0);
|
||||||
|
|
||||||
#[cfg(feature = "ipc")]
|
|
||||||
ipc::Ipc::shutdown(ipc_path);
|
|
||||||
|
|
||||||
exit(0);
|
|
||||||
});
|
|
||||||
|
|
||||||
ctrlc::set_handler(move || tx.send(()).expect("Could not send signal on channel."))
|
|
||||||
.expect("Error setting Ctrl-C handler");
|
|
||||||
|
|
||||||
// TODO: Start wayland client - listen for outputs
|
|
||||||
// All bar loading should happen as an event response to this
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Ignore CLI args
|
let wc = wayland::get_client();
|
||||||
// Some are provided by swaybar_config but not currently supported
|
|
||||||
app.run_with_args(&Vec::<&str>::new());
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Gets the current Tokio runtime.
|
let output_tx = output_tx.clone();
|
||||||
#[must_use]
|
let mut output_rx = lock!(wc).subscribe_outputs();
|
||||||
pub fn runtime() -> Arc<Runtime> {
|
|
||||||
RUNTIME.clone()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Gets a `usize` ID value that is unique to the entire Ironbar instance.
|
spawn(async move {
|
||||||
/// This is just a static `AtomicUsize` that increments every time this function is called.
|
while let Some(event) = output_rx.recv().await {
|
||||||
pub fn unique_id() -> usize {
|
try_send!(output_tx.clone(), event);
|
||||||
COUNTER.fetch_add(1, Ordering::Relaxed)
|
}
|
||||||
}
|
});
|
||||||
|
|
||||||
/// Gets the `Ironvar` manager singleton.
|
ctrlc::set_handler(move || send!(tx, ())).expect("Error setting Ctrl-C handler");
|
||||||
#[cfg(feature = "ipc")]
|
});
|
||||||
#[must_use]
|
|
||||||
pub fn variable_manager() -> Arc<RwLock<VariableManager>> {
|
|
||||||
VARIABLE_MANAGER.clone()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Gets a clone of a bar by its unique name.
|
let config = load_config();
|
||||||
///
|
|
||||||
/// Since the bar contains mostly GTK objects,
|
{
|
||||||
/// the clone is cheap enough to not worry about.
|
let app = app.clone();
|
||||||
#[must_use]
|
let global_state = global_state.clone();
|
||||||
pub fn bar_by_name(&self, name: &str) -> Option<Bar> {
|
|
||||||
self.bars
|
output_bridge.recv(move |event: cached_broadcast::Event<_>| {
|
||||||
.borrow()
|
let display = get_display();
|
||||||
.iter()
|
match event {
|
||||||
.find(|&bar| bar.name() == name)
|
Event::Add(output) => {
|
||||||
.cloned()
|
debug!("Adding bar(s) for monitor {:?}", &output.name);
|
||||||
|
create_bars_for_monitor(&app, &display, &output, config.clone(), &global_state)
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
// TODO: Implement
|
||||||
|
Event::Remove(_) => {}
|
||||||
|
Event::Replace(_, _) => {}
|
||||||
|
}
|
||||||
|
Continue(true)
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
// Ignore CLI args
|
||||||
|
// Some are provided by swaybar_config but not currently supported
|
||||||
|
app.run_with_args(&Vec::<&str>::new());
|
||||||
}
|
}
|
||||||
|
|
||||||
fn start_ironbar() {
|
/// Closes all current bars and entirely reloads Ironbar.
|
||||||
let ironbar = Ironbar::new();
|
/// This re-reads the config file.
|
||||||
ironbar.start();
|
pub async fn reload(app: &Application, global_state: &Rc<RefCell<GlobalState>>) -> Result<()> {
|
||||||
}
|
info!("Closing existing bars");
|
||||||
|
let windows = app.windows();
|
||||||
|
for window in windows {
|
||||||
|
window.close();
|
||||||
|
}
|
||||||
|
|
||||||
/// Loads the Ironbar config and interface.
|
let config = load_config();
|
||||||
pub fn load_interface(app: &Application) -> Vec<Bar> {
|
|
||||||
let display = Display::default().map_or_else(
|
let wl = wayland::get_client();
|
||||||
|
let wl = lock!(wl);
|
||||||
|
let outputs = wl.get_outputs();
|
||||||
|
|
||||||
|
let display = get_display();
|
||||||
|
for output in outputs.iter() {
|
||||||
|
create_bars_for_monitor(app, &display, output, config.clone(), global_state)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
fn create_bars_for_monitor(
|
||||||
|
app: &Application,
|
||||||
|
display: &Display,
|
||||||
|
output: &OutputInfo,
|
||||||
|
config: Config,
|
||||||
|
global_state: &Rc<RefCell<GlobalState>>,
|
||||||
|
) -> Result<()> {
|
||||||
|
let Some(monitor_name) = &output.name else {
|
||||||
|
return Ok(());
|
||||||
|
};
|
||||||
|
|
||||||
|
let monitor = match get_monitor(&monitor_name, display) {
|
||||||
|
Ok(monitor) => monitor,
|
||||||
|
Err(err) => return Err(err),
|
||||||
|
};
|
||||||
|
|
||||||
|
let Some(monitor_config) = config.get_monitor_config(&monitor_name) else {
|
||||||
|
return Ok(());
|
||||||
|
};
|
||||||
|
|
||||||
|
match monitor_config {
|
||||||
|
MonitorConfig::Single(config) => {
|
||||||
|
create_bar(&app, &monitor, &monitor_name, config, global_state)
|
||||||
|
}
|
||||||
|
MonitorConfig::Multiple(configs) => configs
|
||||||
|
.into_iter()
|
||||||
|
.map(|config| create_bar(&app, &monitor, &monitor_name, config, global_state))
|
||||||
|
.collect(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn get_display() -> Display {
|
||||||
|
Display::default().map_or_else(
|
||||||
|| {
|
|| {
|
||||||
let report = Report::msg("Failed to get default GTK display");
|
let report = Report::msg("Failed to get default GTK display");
|
||||||
error!("{:?}", report);
|
error!("{:?}", report);
|
||||||
exit(ExitCode::GtkDisplay as i32)
|
exit(ExitCode::GtkDisplay as i32)
|
||||||
},
|
},
|
||||||
|display| display,
|
|display| display,
|
||||||
);
|
)
|
||||||
|
}
|
||||||
|
|
||||||
let mut config = env::var("IRONBAR_CONFIG")
|
fn get_monitor(name: &str, display: &Display) -> Result<Monitor> {
|
||||||
|
let wl = wayland::get_client();
|
||||||
|
let wl = lock!(wl);
|
||||||
|
let outputs = wl.get_outputs();
|
||||||
|
|
||||||
|
let monitor = (0..display.n_monitors()).into_iter().find_map(|i| {
|
||||||
|
let monitor = display.monitor(i)?;
|
||||||
|
let output = outputs.get(i as usize)?;
|
||||||
|
|
||||||
|
let is_match = output.name.as_ref().map(|n| n == name).unwrap_or_default();
|
||||||
|
if is_match {
|
||||||
|
Some(monitor)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
monitor.ok_or_else(|| Report::msg(error::ERR_OUTPUTS))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn load_config() -> Config {
|
||||||
|
let config = env::var("IRONBAR_CONFIG")
|
||||||
.map_or_else(
|
.map_or_else(
|
||||||
|_| ConfigLoader::new("ironbar").find_and_load(),
|
|_| ConfigLoader::new("ironbar").find_and_load(),
|
||||||
ConfigLoader::load,
|
ConfigLoader::load,
|
||||||
@@ -253,110 +292,7 @@ pub fn load_interface(app: &Application) -> Vec<Bar> {
|
|||||||
});
|
});
|
||||||
|
|
||||||
debug!("Loaded config file");
|
debug!("Loaded config file");
|
||||||
|
config
|
||||||
#[cfg(feature = "ipc")]
|
|
||||||
if let Some(ironvars) = config.ironvar_defaults.take() {
|
|
||||||
let variable_manager = Ironbar::variable_manager();
|
|
||||||
for (k, v) in ironvars {
|
|
||||||
if write_lock!(variable_manager).set(k.clone(), v).is_err() {
|
|
||||||
warn!("Ignoring invalid ironvar: '{k}'");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
match create_bars(app, &display, &config) {
|
|
||||||
Ok(bars) => {
|
|
||||||
debug!("Created {} bars", bars.len());
|
|
||||||
bars
|
|
||||||
}
|
|
||||||
Err(err) => {
|
|
||||||
error!("{:?}", err);
|
|
||||||
exit(ExitCode::CreateBars as i32);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Creates each of the bars across each of the (configured) outputs.
|
|
||||||
fn create_bars(app: &Application, display: &Display, config: &Config) -> Result<Vec<Bar>> {
|
|
||||||
let wl = wayland::get_client();
|
|
||||||
let outputs = lock!(wl).get_outputs();
|
|
||||||
|
|
||||||
debug!("Received {} outputs from Wayland", outputs.len());
|
|
||||||
debug!("Outputs: {:?}", outputs);
|
|
||||||
|
|
||||||
let num_monitors = display.n_monitors();
|
|
||||||
|
|
||||||
let show_default_bar =
|
|
||||||
config.start.is_some() || config.center.is_some() || config.end.is_some();
|
|
||||||
|
|
||||||
let mut all_bars = vec![];
|
|
||||||
for i in 0..num_monitors {
|
|
||||||
let monitor = display
|
|
||||||
.monitor(i)
|
|
||||||
.ok_or_else(|| Report::msg(error::ERR_OUTPUTS))?;
|
|
||||||
let output = outputs
|
|
||||||
.get(i as usize)
|
|
||||||
.ok_or_else(|| Report::msg(error::ERR_OUTPUTS))?;
|
|
||||||
|
|
||||||
let Some(monitor_name) = &output.name else {
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut bars = match config
|
|
||||||
.monitors
|
|
||||||
.as_ref()
|
|
||||||
.and_then(|config| config.get(monitor_name))
|
|
||||||
{
|
|
||||||
Some(MonitorConfig::Single(config)) => {
|
|
||||||
vec![create_bar(
|
|
||||||
app,
|
|
||||||
&monitor,
|
|
||||||
monitor_name.to_string(),
|
|
||||||
config.clone(),
|
|
||||||
)?]
|
|
||||||
}
|
|
||||||
Some(MonitorConfig::Multiple(configs)) => configs
|
|
||||||
.iter()
|
|
||||||
.map(|config| create_bar(app, &monitor, monitor_name.to_string(), config.clone()))
|
|
||||||
.collect::<Result<_>>()?,
|
|
||||||
None if show_default_bar => vec![create_bar(
|
|
||||||
app,
|
|
||||||
&monitor,
|
|
||||||
monitor_name.to_string(),
|
|
||||||
config.clone(),
|
|
||||||
)?],
|
|
||||||
None => vec![],
|
|
||||||
};
|
|
||||||
|
|
||||||
all_bars.append(&mut bars);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(all_bars)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn create_runtime() -> Runtime {
|
|
||||||
tokio::runtime::Builder::new_multi_thread()
|
|
||||||
.enable_all()
|
|
||||||
.build()
|
|
||||||
.expect("tokio to create a valid runtime")
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Calls `spawn` on the Tokio runtime.
|
|
||||||
pub fn spawn<F>(f: F) -> JoinHandle<F::Output>
|
|
||||||
where
|
|
||||||
F: Future + Send + 'static,
|
|
||||||
F::Output: Send + 'static,
|
|
||||||
{
|
|
||||||
Ironbar::runtime().spawn(f)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Calls `spawn_blocking` on the Tokio runtime.
|
|
||||||
pub fn spawn_blocking<F, R>(f: F) -> JoinHandle<R>
|
|
||||||
where
|
|
||||||
F: FnOnce() -> R + Send + 'static,
|
|
||||||
R: Send + 'static,
|
|
||||||
{
|
|
||||||
Ironbar::runtime().spawn_blocking(f)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Blocks on a `Future` until it resolves.
|
/// Blocks on a `Future` until it resolves.
|
||||||
@@ -370,5 +306,5 @@ where
|
|||||||
///
|
///
|
||||||
/// TODO: remove all instances of this once async trait funcs are stable
|
/// TODO: remove all instances of this once async trait funcs are stable
|
||||||
pub fn await_sync<F: Future>(f: F) -> F::Output {
|
pub fn await_sync<F: Future>(f: F) -> F::Output {
|
||||||
block_in_place(|| Ironbar::runtime().block_on(f))
|
block_in_place(|| Handle::current().block_on(f))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,8 +5,7 @@ use crate::image::new_icon_button;
|
|||||||
use crate::modules::{
|
use crate::modules::{
|
||||||
Module, ModuleInfo, ModuleParts, ModulePopup, ModuleUpdateEvent, PopupButton, WidgetContext,
|
Module, ModuleInfo, ModuleParts, ModulePopup, ModuleUpdateEvent, PopupButton, WidgetContext,
|
||||||
};
|
};
|
||||||
use crate::{glib_recv, spawn, try_send};
|
use crate::try_send;
|
||||||
use glib::Propagation;
|
|
||||||
use gtk::gdk_pixbuf::Pixbuf;
|
use gtk::gdk_pixbuf::Pixbuf;
|
||||||
use gtk::gio::{Cancellable, MemoryInputStream};
|
use gtk::gio::{Cancellable, MemoryInputStream};
|
||||||
use gtk::prelude::*;
|
use gtk::prelude::*;
|
||||||
@@ -14,7 +13,8 @@ use gtk::{Button, EventBox, Image, Label, Orientation, RadioButton, Widget};
|
|||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use tokio::sync::{broadcast, mpsc};
|
use tokio::spawn;
|
||||||
|
use tokio::sync::mpsc::{Receiver, Sender};
|
||||||
use tracing::{debug, error};
|
use tracing::{debug, error};
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Clone)]
|
#[derive(Debug, Deserialize, Clone)]
|
||||||
@@ -72,8 +72,8 @@ impl Module<Button> for ClipboardModule {
|
|||||||
fn spawn_controller(
|
fn spawn_controller(
|
||||||
&self,
|
&self,
|
||||||
_info: &ModuleInfo,
|
_info: &ModuleInfo,
|
||||||
tx: mpsc::Sender<ModuleUpdateEvent<Self::SendMessage>>,
|
tx: Sender<ModuleUpdateEvent<Self::SendMessage>>,
|
||||||
mut rx: mpsc::Receiver<Self::ReceiveMessage>,
|
mut rx: Receiver<Self::ReceiveMessage>,
|
||||||
) -> color_eyre::Result<()> {
|
) -> color_eyre::Result<()> {
|
||||||
let max_items = self.max_items;
|
let max_items = self.max_items;
|
||||||
|
|
||||||
@@ -129,14 +129,19 @@ impl Module<Button> for ClipboardModule {
|
|||||||
let button = new_icon_button(&self.icon, info.icon_theme, self.icon_size);
|
let button = new_icon_button(&self.icon, info.icon_theme, self.icon_size);
|
||||||
button.style_context().add_class("btn");
|
button.style_context().add_class("btn");
|
||||||
|
|
||||||
let tx = context.tx.clone();
|
|
||||||
button.connect_clicked(move |button| {
|
button.connect_clicked(move |button| {
|
||||||
try_send!(tx, ModuleUpdateEvent::TogglePopup(button.popup_id()));
|
try_send!(
|
||||||
|
context.tx,
|
||||||
|
ModuleUpdateEvent::TogglePopup(button.popup_id())
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
let rx = context.subscribe();
|
// we need to bind to the receiver as the channel does not open
|
||||||
|
// until the popup is first opened.
|
||||||
|
context.widget_rx.attach(None, |_| Continue(true));
|
||||||
|
|
||||||
let popup = self
|
let popup = self
|
||||||
.into_popup(context.controller_tx, rx, info)
|
.into_popup(context.controller_tx, context.popup_rx, info)
|
||||||
.into_popup_parts(vec![&button]);
|
.into_popup_parts(vec![&button]);
|
||||||
|
|
||||||
Ok(ModuleParts::new(button, popup))
|
Ok(ModuleParts::new(button, popup))
|
||||||
@@ -144,8 +149,8 @@ impl Module<Button> for ClipboardModule {
|
|||||||
|
|
||||||
fn into_popup(
|
fn into_popup(
|
||||||
self,
|
self,
|
||||||
tx: mpsc::Sender<Self::ReceiveMessage>,
|
tx: Sender<Self::ReceiveMessage>,
|
||||||
rx: broadcast::Receiver<Self::SendMessage>,
|
rx: glib::Receiver<Self::SendMessage>,
|
||||||
_info: &ModuleInfo,
|
_info: &ModuleInfo,
|
||||||
) -> Option<gtk::Box>
|
) -> Option<gtk::Box>
|
||||||
where
|
where
|
||||||
@@ -163,7 +168,7 @@ impl Module<Button> for ClipboardModule {
|
|||||||
|
|
||||||
{
|
{
|
||||||
let hidden_option = hidden_option.clone();
|
let hidden_option = hidden_option.clone();
|
||||||
glib_recv!(rx, event => {
|
rx.attach(None, move |event| {
|
||||||
match event {
|
match event {
|
||||||
ControllerEvent::Add(id, item) => {
|
ControllerEvent::Add(id, item) => {
|
||||||
debug!("Adding new value with ID {}", id);
|
debug!("Adding new value with ID {}", id);
|
||||||
@@ -229,7 +234,7 @@ impl Module<Button> for ClipboardModule {
|
|||||||
try_send!(tx, UIEvent::Copy(id));
|
try_send!(tx, UIEvent::Copy(id));
|
||||||
}
|
}
|
||||||
|
|
||||||
Propagation::Stop
|
Inhibit(true)
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -288,6 +293,8 @@ impl Module<Button> for ClipboardModule {
|
|||||||
hidden_option.set_active(true);
|
hidden_option.set_active(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Continue(true)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,10 +2,12 @@ use std::env;
|
|||||||
|
|
||||||
use chrono::{DateTime, Local, Locale};
|
use chrono::{DateTime, Local, Locale};
|
||||||
use color_eyre::Result;
|
use color_eyre::Result;
|
||||||
|
use glib::Continue;
|
||||||
use gtk::prelude::*;
|
use gtk::prelude::*;
|
||||||
use gtk::{Align, Button, Calendar, Label, Orientation};
|
use gtk::{Align, Button, Calendar, Label, Orientation};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use tokio::sync::{broadcast, mpsc};
|
use tokio::spawn;
|
||||||
|
use tokio::sync::mpsc;
|
||||||
use tokio::time::sleep;
|
use tokio::time::sleep;
|
||||||
|
|
||||||
use crate::config::CommonConfig;
|
use crate::config::CommonConfig;
|
||||||
@@ -13,7 +15,7 @@ use crate::gtk_helpers::IronbarGtkExt;
|
|||||||
use crate::modules::{
|
use crate::modules::{
|
||||||
Module, ModuleInfo, ModuleParts, ModulePopup, ModuleUpdateEvent, PopupButton, WidgetContext,
|
Module, ModuleInfo, ModuleParts, ModulePopup, ModuleUpdateEvent, PopupButton, WidgetContext,
|
||||||
};
|
};
|
||||||
use crate::{glib_recv, send_async, spawn, try_send};
|
use crate::{send_async, try_send};
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Clone)]
|
#[derive(Debug, Deserialize, Clone)]
|
||||||
pub struct ClockModule {
|
pub struct ClockModule {
|
||||||
@@ -102,22 +104,24 @@ impl Module<Button> for ClockModule {
|
|||||||
label.set_angle(info.bar_position.get_angle());
|
label.set_angle(info.bar_position.get_angle());
|
||||||
button.add(&label);
|
button.add(&label);
|
||||||
|
|
||||||
let tx = context.tx.clone();
|
|
||||||
button.connect_clicked(move |button| {
|
button.connect_clicked(move |button| {
|
||||||
try_send!(tx, ModuleUpdateEvent::TogglePopup(button.popup_id()));
|
try_send!(
|
||||||
|
context.tx,
|
||||||
|
ModuleUpdateEvent::TogglePopup(button.popup_id())
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
let format = self.format.clone();
|
let format = self.format.clone();
|
||||||
let locale = Locale::try_from(self.locale.as_str()).unwrap_or(Locale::POSIX);
|
let locale = Locale::try_from(self.locale.as_str()).unwrap_or(Locale::POSIX);
|
||||||
|
|
||||||
let rx = context.subscribe();
|
context.widget_rx.attach(None, move |date| {
|
||||||
glib_recv!(rx, date => {
|
|
||||||
let date_string = format!("{}", date.format_localized(&format, locale));
|
let date_string = format!("{}", date.format_localized(&format, locale));
|
||||||
label.set_label(&date_string);
|
label.set_label(&date_string);
|
||||||
|
Continue(true)
|
||||||
});
|
});
|
||||||
|
|
||||||
let popup = self
|
let popup = self
|
||||||
.into_popup(context.controller_tx.clone(), context.subscribe(), info)
|
.into_popup(context.controller_tx, context.popup_rx, info)
|
||||||
.into_popup_parts(vec![&button]);
|
.into_popup_parts(vec![&button]);
|
||||||
|
|
||||||
Ok(ModuleParts::new(button, popup))
|
Ok(ModuleParts::new(button, popup))
|
||||||
@@ -126,7 +130,7 @@ impl Module<Button> for ClockModule {
|
|||||||
fn into_popup(
|
fn into_popup(
|
||||||
self,
|
self,
|
||||||
_tx: mpsc::Sender<Self::ReceiveMessage>,
|
_tx: mpsc::Sender<Self::ReceiveMessage>,
|
||||||
rx: broadcast::Receiver<Self::SendMessage>,
|
rx: glib::Receiver<Self::SendMessage>,
|
||||||
_info: &ModuleInfo,
|
_info: &ModuleInfo,
|
||||||
) -> Option<gtk::Box> {
|
) -> Option<gtk::Box> {
|
||||||
let container = gtk::Box::new(Orientation::Vertical, 0);
|
let container = gtk::Box::new(Orientation::Vertical, 0);
|
||||||
@@ -143,9 +147,10 @@ impl Module<Button> for ClockModule {
|
|||||||
let format = self.format_popup;
|
let format = self.format_popup;
|
||||||
let locale = Locale::try_from(self.locale.as_str()).unwrap_or(Locale::POSIX);
|
let locale = Locale::try_from(self.locale.as_str()).unwrap_or(Locale::POSIX);
|
||||||
|
|
||||||
glib_recv!(rx, date => {
|
rx.attach(None, move |date| {
|
||||||
let date_string = format!("{}", date.format_localized(&format, locale));
|
let date_string = format!("{}", date.format_localized(&format, locale));
|
||||||
clock.set_label(&date_string);
|
clock.set_label(&date_string);
|
||||||
|
Continue(true)
|
||||||
});
|
});
|
||||||
|
|
||||||
container.show_all();
|
container.show_all();
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ impl CustomWidget for ButtonWidget {
|
|||||||
|
|
||||||
dynamic_string(&text, move |string| {
|
dynamic_string(&text, move |string| {
|
||||||
label.set_markup(&string);
|
label.set_markup(&string);
|
||||||
|
Continue(true)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -34,6 +34,8 @@ impl CustomWidget for ImageWidget {
|
|||||||
dynamic_string(&self.src, move |src| {
|
dynamic_string(&self.src, move |src| {
|
||||||
ImageProvider::parse(&src, &icon_theme, false, self.size)
|
ImageProvider::parse(&src, &icon_theme, false, self.size)
|
||||||
.map(|image| image.load_into_image(gtk_image.clone()));
|
.map(|image| image.load_into_image(gtk_image.clone()));
|
||||||
|
|
||||||
|
Continue(true)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ impl CustomWidget for LabelWidget {
|
|||||||
let label = label.clone();
|
let label = label.clone();
|
||||||
dynamic_string(&self.label, move |string| {
|
dynamic_string(&self.label, move |string| {
|
||||||
label.set_markup(&string);
|
label.set_markup(&string);
|
||||||
|
Continue(true)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,14 +16,15 @@ use crate::modules::{
|
|||||||
wrap_widget, Module, ModuleInfo, ModuleParts, ModulePopup, ModuleUpdateEvent, WidgetContext,
|
wrap_widget, Module, ModuleInfo, ModuleParts, ModulePopup, ModuleUpdateEvent, WidgetContext,
|
||||||
};
|
};
|
||||||
use crate::script::Script;
|
use crate::script::Script;
|
||||||
use crate::{send_async, spawn};
|
use crate::send_async;
|
||||||
use color_eyre::{Report, Result};
|
use color_eyre::{Report, Result};
|
||||||
use gtk::prelude::*;
|
use gtk::prelude::*;
|
||||||
use gtk::{Button, IconTheme, Orientation};
|
use gtk::{Button, IconTheme, Orientation};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
use tokio::sync::{broadcast, mpsc};
|
use tokio::spawn;
|
||||||
|
use tokio::sync::mpsc::{Receiver, Sender};
|
||||||
use tracing::{debug, error};
|
use tracing::{debug, error};
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Clone)]
|
#[derive(Debug, Deserialize, Clone)]
|
||||||
@@ -58,7 +59,7 @@ pub enum Widget {
|
|||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
struct CustomWidgetContext<'a> {
|
struct CustomWidgetContext<'a> {
|
||||||
tx: &'a mpsc::Sender<ExecEvent>,
|
tx: &'a Sender<ExecEvent>,
|
||||||
bar_orientation: Orientation,
|
bar_orientation: Orientation,
|
||||||
icon_theme: &'a IconTheme,
|
icon_theme: &'a IconTheme,
|
||||||
popup_buttons: Rc<RefCell<Vec<Button>>>,
|
popup_buttons: Rc<RefCell<Vec<Button>>>,
|
||||||
@@ -158,8 +159,8 @@ impl Module<gtk::Box> for CustomModule {
|
|||||||
fn spawn_controller(
|
fn spawn_controller(
|
||||||
&self,
|
&self,
|
||||||
_info: &ModuleInfo,
|
_info: &ModuleInfo,
|
||||||
tx: mpsc::Sender<ModuleUpdateEvent<Self::SendMessage>>,
|
tx: Sender<ModuleUpdateEvent<Self::SendMessage>>,
|
||||||
mut rx: mpsc::Receiver<Self::ReceiveMessage>,
|
mut rx: Receiver<Self::ReceiveMessage>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
spawn(async move {
|
spawn(async move {
|
||||||
while let Some(event) = rx.recv().await {
|
while let Some(event) = rx.recv().await {
|
||||||
@@ -212,7 +213,7 @@ impl Module<gtk::Box> for CustomModule {
|
|||||||
});
|
});
|
||||||
|
|
||||||
let popup = self
|
let popup = self
|
||||||
.into_popup(context.controller_tx.clone(), context.subscribe(), info)
|
.into_popup(context.controller_tx, context.popup_rx, info)
|
||||||
.into_popup_parts_owned(popup_buttons.take());
|
.into_popup_parts_owned(popup_buttons.take());
|
||||||
|
|
||||||
Ok(ModuleParts {
|
Ok(ModuleParts {
|
||||||
@@ -223,8 +224,8 @@ impl Module<gtk::Box> for CustomModule {
|
|||||||
|
|
||||||
fn into_popup(
|
fn into_popup(
|
||||||
self,
|
self,
|
||||||
tx: mpsc::Sender<Self::ReceiveMessage>,
|
tx: Sender<Self::ReceiveMessage>,
|
||||||
_rx: broadcast::Receiver<Self::SendMessage>,
|
_rx: glib::Receiver<Self::SendMessage>,
|
||||||
info: &ModuleInfo,
|
info: &ModuleInfo,
|
||||||
) -> Option<gtk::Box>
|
) -> Option<gtk::Box>
|
||||||
where
|
where
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
use gtk::prelude::*;
|
use gtk::prelude::*;
|
||||||
use gtk::ProgressBar;
|
use gtk::ProgressBar;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use tokio::sync::mpsc;
|
use tokio::spawn;
|
||||||
use tracing::error;
|
use tracing::error;
|
||||||
|
|
||||||
use crate::dynamic_value::dynamic_string;
|
use crate::dynamic_value::dynamic_string;
|
||||||
use crate::modules::custom::set_length;
|
use crate::modules::custom::set_length;
|
||||||
use crate::script::{OutputStream, Script, ScriptInput};
|
use crate::script::{OutputStream, Script, ScriptInput};
|
||||||
use crate::{build, glib_recv_mpsc, spawn, try_send};
|
use crate::{build, send};
|
||||||
|
|
||||||
use super::{try_get_orientation, CustomWidget, CustomWidgetContext};
|
use super::{try_get_orientation, CustomWidget, CustomWidgetContext};
|
||||||
|
|
||||||
@@ -47,13 +47,13 @@ impl CustomWidget for ProgressWidget {
|
|||||||
let script = Script::from(value);
|
let script = Script::from(value);
|
||||||
let progress = progress.clone();
|
let progress = progress.clone();
|
||||||
|
|
||||||
let (tx, rx) = mpsc::channel(128);
|
let (tx, rx) = glib::MainContext::channel(glib::PRIORITY_DEFAULT);
|
||||||
|
|
||||||
spawn(async move {
|
spawn(async move {
|
||||||
script
|
script
|
||||||
.run(None, move |stream, _success| match stream {
|
.run(None, move |stream, _success| match stream {
|
||||||
OutputStream::Stdout(out) => match out.parse::<f64>() {
|
OutputStream::Stdout(out) => match out.parse::<f64>() {
|
||||||
Ok(value) => try_send!(tx, value),
|
Ok(value) => send!(tx, value),
|
||||||
Err(err) => error!("{err:?}"),
|
Err(err) => error!("{err:?}"),
|
||||||
},
|
},
|
||||||
OutputStream::Stderr(err) => error!("{err:?}"),
|
OutputStream::Stderr(err) => error!("{err:?}"),
|
||||||
@@ -61,7 +61,10 @@ impl CustomWidget for ProgressWidget {
|
|||||||
.await;
|
.await;
|
||||||
});
|
});
|
||||||
|
|
||||||
glib_recv_mpsc!(rx, value => progress.set_fraction(value / self.max));
|
rx.attach(None, move |value| {
|
||||||
|
progress.set_fraction(value / self.max);
|
||||||
|
Continue(true)
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(text) = self.label {
|
if let Some(text) = self.label {
|
||||||
@@ -70,6 +73,7 @@ impl CustomWidget for ProgressWidget {
|
|||||||
|
|
||||||
dynamic_string(&text, move |string| {
|
dynamic_string(&text, move |string| {
|
||||||
progress.set_text(Some(&string));
|
progress.set_text(Some(&string));
|
||||||
|
Continue(true)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,16 +1,15 @@
|
|||||||
use glib::Propagation;
|
|
||||||
use std::cell::Cell;
|
use std::cell::Cell;
|
||||||
use std::ops::Neg;
|
use std::ops::Neg;
|
||||||
|
|
||||||
use gtk::prelude::*;
|
use gtk::prelude::*;
|
||||||
use gtk::Scale;
|
use gtk::Scale;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use tokio::sync::mpsc;
|
use tokio::spawn;
|
||||||
use tracing::error;
|
use tracing::error;
|
||||||
|
|
||||||
use crate::modules::custom::set_length;
|
use crate::modules::custom::set_length;
|
||||||
use crate::script::{OutputStream, Script, ScriptInput};
|
use crate::script::{OutputStream, Script, ScriptInput};
|
||||||
use crate::{build, glib_recv_mpsc, spawn, try_send};
|
use crate::{build, send, try_send};
|
||||||
|
|
||||||
use super::{try_get_orientation, CustomWidget, CustomWidgetContext, ExecEvent};
|
use super::{try_get_orientation, CustomWidget, CustomWidgetContext, ExecEvent};
|
||||||
|
|
||||||
@@ -78,7 +77,7 @@ impl CustomWidget for SliderWidget {
|
|||||||
};
|
};
|
||||||
|
|
||||||
scale.set_value(value + delta);
|
scale.set_value(value + delta);
|
||||||
Propagation::Proceed
|
Inhibit(false)
|
||||||
});
|
});
|
||||||
|
|
||||||
scale.connect_change_value(move |_, _, val| {
|
scale.connect_change_value(move |_, _, val| {
|
||||||
@@ -98,7 +97,7 @@ impl CustomWidget for SliderWidget {
|
|||||||
prev_value.set(val);
|
prev_value.set(val);
|
||||||
}
|
}
|
||||||
|
|
||||||
Propagation::Proceed
|
Inhibit(false)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -106,13 +105,13 @@ impl CustomWidget for SliderWidget {
|
|||||||
let script = Script::from(value);
|
let script = Script::from(value);
|
||||||
let scale = scale.clone();
|
let scale = scale.clone();
|
||||||
|
|
||||||
let (tx, rx) = mpsc::channel(128);
|
let (tx, rx) = glib::MainContext::channel(glib::PRIORITY_DEFAULT);
|
||||||
|
|
||||||
spawn(async move {
|
spawn(async move {
|
||||||
script
|
script
|
||||||
.run(None, move |stream, _success| match stream {
|
.run(None, move |stream, _success| match stream {
|
||||||
OutputStream::Stdout(out) => match out.parse() {
|
OutputStream::Stdout(out) => match out.parse() {
|
||||||
Ok(value) => try_send!(tx, value),
|
Ok(value) => send!(tx, value),
|
||||||
Err(err) => error!("{err:?}"),
|
Err(err) => error!("{err:?}"),
|
||||||
},
|
},
|
||||||
OutputStream::Stderr(err) => error!("{err:?}"),
|
OutputStream::Stderr(err) => error!("{err:?}"),
|
||||||
@@ -120,7 +119,10 @@ impl CustomWidget for SliderWidget {
|
|||||||
.await;
|
.await;
|
||||||
});
|
});
|
||||||
|
|
||||||
glib_recv_mpsc!(rx, value => scale.set_value(value));
|
rx.attach(None, move |value| {
|
||||||
|
scale.set_value(value);
|
||||||
|
Continue(true)
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
scale
|
scale
|
||||||
|
|||||||
@@ -3,11 +3,13 @@ use crate::config::{CommonConfig, TruncateMode};
|
|||||||
use crate::gtk_helpers::IronbarGtkExt;
|
use crate::gtk_helpers::IronbarGtkExt;
|
||||||
use crate::image::ImageProvider;
|
use crate::image::ImageProvider;
|
||||||
use crate::modules::{Module, ModuleInfo, ModuleParts, ModuleUpdateEvent, WidgetContext};
|
use crate::modules::{Module, ModuleInfo, ModuleParts, ModuleUpdateEvent, WidgetContext};
|
||||||
use crate::{glib_recv, lock, send_async, spawn, try_send};
|
use crate::{lock, send_async, try_send};
|
||||||
use color_eyre::Result;
|
use color_eyre::Result;
|
||||||
|
use glib::Continue;
|
||||||
use gtk::prelude::*;
|
use gtk::prelude::*;
|
||||||
use gtk::Label;
|
use gtk::Label;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
use tokio::spawn;
|
||||||
use tokio::sync::mpsc::{Receiver, Sender};
|
use tokio::sync::mpsc::{Receiver, Sender};
|
||||||
use tracing::debug;
|
use tracing::debug;
|
||||||
|
|
||||||
@@ -47,7 +49,7 @@ const fn default_icon_size() -> i32 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Module<gtk::Box> for FocusedModule {
|
impl Module<gtk::Box> for FocusedModule {
|
||||||
type SendMessage = Option<(String, String)>;
|
type SendMessage = (String, String);
|
||||||
type ReceiveMessage = ();
|
type ReceiveMessage = ();
|
||||||
|
|
||||||
fn name() -> &'static str {
|
fn name() -> &'static str {
|
||||||
@@ -76,36 +78,21 @@ impl Module<gtk::Box> for FocusedModule {
|
|||||||
if let Some(focused) = focused {
|
if let Some(focused) = focused {
|
||||||
try_send!(
|
try_send!(
|
||||||
tx,
|
tx,
|
||||||
ModuleUpdateEvent::Update(Some((focused.title.clone(), focused.app_id)))
|
ModuleUpdateEvent::Update((focused.title.clone(), focused.app_id))
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
while let Ok(event) = wlrx.recv().await {
|
while let Ok(event) = wlrx.recv().await {
|
||||||
match event {
|
if let ToplevelEvent::Update(handle) = event {
|
||||||
ToplevelEvent::Update(handle) => {
|
let info = handle.info().unwrap_or_default();
|
||||||
let info = handle.info().unwrap_or_default();
|
|
||||||
|
|
||||||
if info.focused {
|
if info.focused {
|
||||||
debug!("Changing focus");
|
debug!("Changing focus");
|
||||||
send_async!(
|
send_async!(
|
||||||
tx,
|
tx,
|
||||||
ModuleUpdateEvent::Update(Some((
|
ModuleUpdateEvent::Update((info.title.clone(), info.app_id.clone()))
|
||||||
info.title.clone(),
|
);
|
||||||
info.app_id.clone()
|
|
||||||
)))
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
send_async!(tx, ModuleUpdateEvent::Update(None));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
ToplevelEvent::Remove(handle) => {
|
|
||||||
let info = handle.info().unwrap_or_default();
|
|
||||||
if info.focused {
|
|
||||||
debug!("Clearing focus");
|
|
||||||
send_async!(tx, ModuleUpdateEvent::Update(None));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ToplevelEvent::New(_) => {}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -139,25 +126,21 @@ impl Module<gtk::Box> for FocusedModule {
|
|||||||
|
|
||||||
{
|
{
|
||||||
let icon_theme = icon_theme.clone();
|
let icon_theme = icon_theme.clone();
|
||||||
glib_recv!(context.subscribe(), data => {
|
context.widget_rx.attach(None, move |(name, id)| {
|
||||||
if let Some((name, id)) = data {
|
if self.show_icon {
|
||||||
if self.show_icon {
|
match ImageProvider::parse(&id, &icon_theme, true, self.icon_size)
|
||||||
match ImageProvider::parse(&id, &icon_theme, true, self.icon_size)
|
.map(|image| image.load_into_image(icon.clone()))
|
||||||
.map(|image| image.load_into_image(icon.clone()))
|
{
|
||||||
{
|
Some(Ok(_)) => icon.show(),
|
||||||
Some(Ok(())) => icon.show(),
|
_ => icon.hide(),
|
||||||
_ => icon.hide(),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.show_title {
|
|
||||||
label.show();
|
|
||||||
label.set_label(&name);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
icon.hide();
|
|
||||||
label.hide();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if self.show_title {
|
||||||
|
label.set_label(&name);
|
||||||
|
}
|
||||||
|
|
||||||
|
Continue(true)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
use crate::config::CommonConfig;
|
use crate::config::CommonConfig;
|
||||||
use crate::dynamic_value::dynamic_string;
|
use crate::dynamic_value::dynamic_string;
|
||||||
use crate::modules::{Module, ModuleInfo, ModuleParts, ModuleUpdateEvent, WidgetContext};
|
use crate::modules::{Module, ModuleInfo, ModuleParts, ModuleUpdateEvent, WidgetContext};
|
||||||
use crate::{glib_recv, try_send};
|
use crate::try_send;
|
||||||
use color_eyre::Result;
|
use color_eyre::Result;
|
||||||
|
use glib::Continue;
|
||||||
use gtk::prelude::*;
|
use gtk::prelude::*;
|
||||||
use gtk::Label;
|
use gtk::Label;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
@@ -41,6 +42,7 @@ impl Module<Label> for LabelModule {
|
|||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
dynamic_string(&self.label, move |string| {
|
dynamic_string(&self.label, move |string| {
|
||||||
try_send!(tx, ModuleUpdateEvent::Update(string));
|
try_send!(tx, ModuleUpdateEvent::Update(string));
|
||||||
|
Continue(true)
|
||||||
});
|
});
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -56,7 +58,10 @@ impl Module<Label> for LabelModule {
|
|||||||
|
|
||||||
{
|
{
|
||||||
let label = label.clone();
|
let label = label.clone();
|
||||||
glib_recv!(context.subscribe(), string => label.set_markup(&string));
|
context.widget_rx.attach(None, move |string| {
|
||||||
|
label.set_markup(&string);
|
||||||
|
Continue(true)
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(ModuleParts {
|
Ok(ModuleParts {
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ use crate::modules::launcher::{ItemEvent, LauncherUpdate};
|
|||||||
use crate::modules::ModuleUpdateEvent;
|
use crate::modules::ModuleUpdateEvent;
|
||||||
use crate::{read_lock, try_send};
|
use crate::{read_lock, try_send};
|
||||||
use color_eyre::{Report, Result};
|
use color_eyre::{Report, Result};
|
||||||
use glib::Propagation;
|
|
||||||
use gtk::prelude::*;
|
use gtk::prelude::*;
|
||||||
use gtk::{Button, IconTheme};
|
use gtk::{Button, IconTheme};
|
||||||
use indexmap::IndexMap;
|
use indexmap::IndexMap;
|
||||||
@@ -259,7 +258,7 @@ impl ItemButton {
|
|||||||
try_send!(tx, ModuleUpdateEvent::ClosePopup);
|
try_send!(tx, ModuleUpdateEvent::ClosePopup);
|
||||||
}
|
}
|
||||||
|
|
||||||
Propagation::Proceed
|
Inhibit(false)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -274,9 +273,9 @@ impl ItemButton {
|
|||||||
let (x, y) = ev.position();
|
let (x, y) = ev.position();
|
||||||
|
|
||||||
let close = match bar_position {
|
let close = match bar_position {
|
||||||
BarPosition::Top => y + THRESHOLD < f64::from(alloc.height()),
|
BarPosition::Top => y + THRESHOLD < alloc.height() as f64,
|
||||||
BarPosition::Bottom => y > THRESHOLD,
|
BarPosition::Bottom => y > THRESHOLD,
|
||||||
BarPosition::Left => x + THRESHOLD < f64::from(alloc.width()),
|
BarPosition::Left => x + THRESHOLD < alloc.width() as f64,
|
||||||
BarPosition::Right => x > THRESHOLD,
|
BarPosition::Right => x > THRESHOLD,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -284,7 +283,7 @@ impl ItemButton {
|
|||||||
try_send!(tx, ModuleUpdateEvent::ClosePopup);
|
try_send!(tx, ModuleUpdateEvent::ClosePopup);
|
||||||
}
|
}
|
||||||
|
|
||||||
Propagation::Proceed
|
Inhibit(false)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,15 +10,17 @@ use crate::modules::launcher::item::AppearanceOptions;
|
|||||||
use crate::modules::{
|
use crate::modules::{
|
||||||
Module, ModuleInfo, ModuleParts, ModulePopup, ModuleUpdateEvent, WidgetContext,
|
Module, ModuleInfo, ModuleParts, ModulePopup, ModuleUpdateEvent, WidgetContext,
|
||||||
};
|
};
|
||||||
use crate::{arc_mut, glib_recv, lock, send_async, spawn, try_send, write_lock};
|
use crate::{arc_mut, lock, send_async, try_send, write_lock};
|
||||||
use color_eyre::{Help, Report};
|
use color_eyre::{Help, Report};
|
||||||
|
use glib::Continue;
|
||||||
use gtk::prelude::*;
|
use gtk::prelude::*;
|
||||||
use gtk::{Button, Orientation};
|
use gtk::{Button, Orientation};
|
||||||
use indexmap::IndexMap;
|
use indexmap::IndexMap;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use std::process::{Command, Stdio};
|
use std::process::{Command, Stdio};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use tokio::sync::{broadcast, mpsc};
|
use tokio::spawn;
|
||||||
|
use tokio::sync::mpsc::{Receiver, Sender};
|
||||||
use tracing::{debug, error, trace};
|
use tracing::{debug, error, trace};
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Clone)]
|
#[derive(Debug, Deserialize, Clone)]
|
||||||
@@ -90,8 +92,8 @@ impl Module<gtk::Box> for LauncherModule {
|
|||||||
fn spawn_controller(
|
fn spawn_controller(
|
||||||
&self,
|
&self,
|
||||||
_info: &ModuleInfo,
|
_info: &ModuleInfo,
|
||||||
tx: mpsc::Sender<ModuleUpdateEvent<Self::SendMessage>>,
|
tx: Sender<ModuleUpdateEvent<Self::SendMessage>>,
|
||||||
mut rx: mpsc::Receiver<Self::ReceiveMessage>,
|
mut rx: Receiver<Self::ReceiveMessage>,
|
||||||
) -> crate::Result<()> {
|
) -> crate::Result<()> {
|
||||||
let items = self
|
let items = self
|
||||||
.favorites
|
.favorites
|
||||||
@@ -336,9 +338,7 @@ impl Module<gtk::Box> for LauncherModule {
|
|||||||
|
|
||||||
let mut buttons = IndexMap::<String, ItemButton>::new();
|
let mut buttons = IndexMap::<String, ItemButton>::new();
|
||||||
|
|
||||||
let tx = context.tx.clone();
|
context.widget_rx.attach(None, move |event| {
|
||||||
let rx = context.subscribe();
|
|
||||||
glib_recv!(rx, event => {
|
|
||||||
match event {
|
match event {
|
||||||
LauncherUpdate::AddItem(item) => {
|
LauncherUpdate::AddItem(item) => {
|
||||||
debug!("Adding item with id {}", item.app_id);
|
debug!("Adding item with id {}", item.app_id);
|
||||||
@@ -351,7 +351,7 @@ impl Module<gtk::Box> for LauncherModule {
|
|||||||
appearance_options,
|
appearance_options,
|
||||||
&icon_theme,
|
&icon_theme,
|
||||||
bar_position,
|
bar_position,
|
||||||
&tx,
|
&context.tx,
|
||||||
&controller_tx,
|
&controller_tx,
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -411,12 +411,13 @@ impl Module<gtk::Box> for LauncherModule {
|
|||||||
}
|
}
|
||||||
LauncherUpdate::Hover(_) => {}
|
LauncherUpdate::Hover(_) => {}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Continue(true)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
let rx = context.subscribe();
|
|
||||||
let popup = self
|
let popup = self
|
||||||
.into_popup(context.controller_tx, rx, info)
|
.into_popup(context.controller_tx, context.popup_rx, info)
|
||||||
.into_popup_parts(vec![]); // since item buttons are dynamic, they pass their geometry directly
|
.into_popup_parts(vec![]); // since item buttons are dynamic, they pass their geometry directly
|
||||||
|
|
||||||
Ok(ModuleParts {
|
Ok(ModuleParts {
|
||||||
@@ -427,8 +428,8 @@ impl Module<gtk::Box> for LauncherModule {
|
|||||||
|
|
||||||
fn into_popup(
|
fn into_popup(
|
||||||
self,
|
self,
|
||||||
controller_tx: mpsc::Sender<Self::ReceiveMessage>,
|
controller_tx: Sender<Self::ReceiveMessage>,
|
||||||
rx: broadcast::Receiver<Self::SendMessage>,
|
rx: glib::Receiver<Self::SendMessage>,
|
||||||
_info: &ModuleInfo,
|
_info: &ModuleInfo,
|
||||||
) -> Option<gtk::Box> {
|
) -> Option<gtk::Box> {
|
||||||
const MAX_WIDTH: i32 = 250;
|
const MAX_WIDTH: i32 = 250;
|
||||||
@@ -444,7 +445,7 @@ impl Module<gtk::Box> for LauncherModule {
|
|||||||
|
|
||||||
{
|
{
|
||||||
let container = container.clone();
|
let container = container.clone();
|
||||||
glib_recv!(rx, event => {
|
rx.attach(None, move |event| {
|
||||||
match event {
|
match event {
|
||||||
LauncherUpdate::AddItem(item) => {
|
LauncherUpdate::AddItem(item) => {
|
||||||
let app_id = item.app_id.clone();
|
let app_id = item.app_id.clone();
|
||||||
@@ -531,6 +532,8 @@ impl Module<gtk::Box> for LauncherModule {
|
|||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Continue(true)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
use std::fmt::Debug;
|
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
use color_eyre::Result;
|
use color_eyre::Result;
|
||||||
@@ -7,13 +6,14 @@ use glib::IsA;
|
|||||||
use gtk::gdk::{EventMask, Monitor};
|
use gtk::gdk::{EventMask, Monitor};
|
||||||
use gtk::prelude::*;
|
use gtk::prelude::*;
|
||||||
use gtk::{Application, Button, EventBox, IconTheme, Orientation, Revealer, Widget};
|
use gtk::{Application, Button, EventBox, IconTheme, Orientation, Revealer, Widget};
|
||||||
use tokio::sync::{broadcast, mpsc};
|
use tokio::sync::mpsc;
|
||||||
use tracing::debug;
|
use tracing::debug;
|
||||||
|
|
||||||
|
use crate::bridge_channel::BridgeChannel;
|
||||||
use crate::config::{BarPosition, CommonConfig, TransitionType};
|
use crate::config::{BarPosition, CommonConfig, TransitionType};
|
||||||
use crate::gtk_helpers::{IronbarGtkExt, WidgetGeometry};
|
use crate::gtk_helpers::{IronbarGtkExt, WidgetGeometry};
|
||||||
use crate::popup::Popup;
|
use crate::popup::Popup;
|
||||||
use crate::{glib_recv_mpsc, send};
|
use crate::send;
|
||||||
|
|
||||||
#[cfg(feature = "clipboard")]
|
#[cfg(feature = "clipboard")]
|
||||||
pub mod clipboard;
|
pub mod clipboard;
|
||||||
@@ -56,8 +56,8 @@ pub struct ModuleInfo<'a> {
|
|||||||
pub icon_theme: &'a IconTheme,
|
pub icon_theme: &'a IconTheme,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug)]
|
||||||
pub enum ModuleUpdateEvent<T: Clone> {
|
pub enum ModuleUpdateEvent<T> {
|
||||||
/// Sends an update to the module UI.
|
/// Sends an update to the module UI.
|
||||||
Update(T),
|
Update(T),
|
||||||
/// Toggles the open state of the popup.
|
/// Toggles the open state of the popup.
|
||||||
@@ -71,25 +71,12 @@ pub enum ModuleUpdateEvent<T: Clone> {
|
|||||||
ClosePopup,
|
ClosePopup,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct WidgetContext<TSend, TReceive>
|
pub struct WidgetContext<TSend, TReceive> {
|
||||||
where
|
|
||||||
TSend: Clone,
|
|
||||||
{
|
|
||||||
pub id: usize,
|
pub id: usize,
|
||||||
pub tx: mpsc::Sender<ModuleUpdateEvent<TSend>>,
|
pub tx: mpsc::Sender<ModuleUpdateEvent<TSend>>,
|
||||||
pub update_tx: broadcast::Sender<TSend>,
|
|
||||||
pub controller_tx: mpsc::Sender<TReceive>,
|
pub controller_tx: mpsc::Sender<TReceive>,
|
||||||
|
pub widget_rx: glib::Receiver<TSend>,
|
||||||
_update_rx: broadcast::Receiver<TSend>,
|
pub popup_rx: glib::Receiver<TSend>,
|
||||||
}
|
|
||||||
|
|
||||||
impl<TSend, TReceive> WidgetContext<TSend, TReceive>
|
|
||||||
where
|
|
||||||
TSend: Clone,
|
|
||||||
{
|
|
||||||
pub fn subscribe(&self) -> broadcast::Receiver<TSend> {
|
|
||||||
self.update_tx.subscribe()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct ModuleParts<W: IsA<Widget>> {
|
pub struct ModuleParts<W: IsA<Widget>> {
|
||||||
@@ -164,22 +151,18 @@ where
|
|||||||
info: &ModuleInfo,
|
info: &ModuleInfo,
|
||||||
tx: mpsc::Sender<ModuleUpdateEvent<Self::SendMessage>>,
|
tx: mpsc::Sender<ModuleUpdateEvent<Self::SendMessage>>,
|
||||||
rx: mpsc::Receiver<Self::ReceiveMessage>,
|
rx: mpsc::Receiver<Self::ReceiveMessage>,
|
||||||
) -> Result<()>
|
) -> Result<()>;
|
||||||
where
|
|
||||||
<Self as Module<W>>::SendMessage: Clone;
|
|
||||||
|
|
||||||
fn into_widget(
|
fn into_widget(
|
||||||
self,
|
self,
|
||||||
context: WidgetContext<Self::SendMessage, Self::ReceiveMessage>,
|
context: WidgetContext<Self::SendMessage, Self::ReceiveMessage>,
|
||||||
info: &ModuleInfo,
|
info: &ModuleInfo,
|
||||||
) -> Result<ModuleParts<W>>
|
) -> Result<ModuleParts<W>>;
|
||||||
where
|
|
||||||
<Self as Module<W>>::SendMessage: Clone;
|
|
||||||
|
|
||||||
fn into_popup(
|
fn into_popup(
|
||||||
self,
|
self,
|
||||||
_tx: mpsc::Sender<Self::ReceiveMessage>,
|
_tx: mpsc::Sender<Self::ReceiveMessage>,
|
||||||
_rx: broadcast::Receiver<Self::SendMessage>,
|
_rx: glib::Receiver<Self::SendMessage>,
|
||||||
_info: &ModuleInfo,
|
_info: &ModuleInfo,
|
||||||
) -> Option<gtk::Box>
|
) -> Option<gtk::Box>
|
||||||
where
|
where
|
||||||
@@ -201,21 +184,22 @@ pub fn create_module<TModule, TWidget, TSend, TRec>(
|
|||||||
where
|
where
|
||||||
TModule: Module<TWidget, SendMessage = TSend, ReceiveMessage = TRec>,
|
TModule: Module<TWidget, SendMessage = TSend, ReceiveMessage = TRec>,
|
||||||
TWidget: IsA<Widget>,
|
TWidget: IsA<Widget>,
|
||||||
TSend: Debug + Clone + Send + 'static,
|
TSend: Clone + Send + 'static,
|
||||||
{
|
{
|
||||||
let (ui_tx, ui_rx) = mpsc::channel::<ModuleUpdateEvent<TSend>>(64);
|
let (w_tx, w_rx) = glib::MainContext::channel::<TSend>(glib::PRIORITY_DEFAULT);
|
||||||
let (controller_tx, controller_rx) = mpsc::channel::<TRec>(64);
|
let (p_tx, p_rx) = glib::MainContext::channel::<TSend>(glib::PRIORITY_DEFAULT);
|
||||||
|
|
||||||
let (tx, rx) = broadcast::channel(64);
|
let channel = BridgeChannel::<ModuleUpdateEvent<TSend>>::new();
|
||||||
|
let (ui_tx, ui_rx) = mpsc::channel::<TRec>(16);
|
||||||
|
|
||||||
module.spawn_controller(info, ui_tx.clone(), controller_rx)?;
|
module.spawn_controller(info, channel.create_sender(), ui_rx)?;
|
||||||
|
|
||||||
let context = WidgetContext {
|
let context = WidgetContext {
|
||||||
id,
|
id,
|
||||||
tx: ui_tx,
|
widget_rx: w_rx,
|
||||||
update_tx: tx.clone(),
|
popup_rx: p_rx,
|
||||||
controller_tx,
|
tx: channel.create_sender(),
|
||||||
_update_rx: rx,
|
controller_tx: ui_tx,
|
||||||
};
|
};
|
||||||
|
|
||||||
let module_name = TModule::name();
|
let module_name = TModule::name();
|
||||||
@@ -225,16 +209,27 @@ where
|
|||||||
module_parts.widget.add_class("widget");
|
module_parts.widget.add_class("widget");
|
||||||
module_parts.widget.add_class(module_name);
|
module_parts.widget.add_class(module_name);
|
||||||
|
|
||||||
if let Some(popup_content) = module_parts.popup.clone() {
|
let has_popup = if let Some(popup_content) = module_parts.popup.clone() {
|
||||||
popup_content
|
popup_content
|
||||||
.container
|
.container
|
||||||
.style_context()
|
.style_context()
|
||||||
.add_class(&format!("popup-{module_name}"));
|
.add_class(&format!("popup-{module_name}"));
|
||||||
|
|
||||||
register_popup_content(popup, id, instance_name, popup_content);
|
register_popup_content(popup, id, instance_name, popup_content);
|
||||||
}
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
};
|
||||||
|
|
||||||
setup_receiver(tx, ui_rx, popup.clone(), module_name, id);
|
setup_receiver(
|
||||||
|
channel,
|
||||||
|
w_tx,
|
||||||
|
p_tx,
|
||||||
|
popup.clone(),
|
||||||
|
module_name,
|
||||||
|
id,
|
||||||
|
has_popup,
|
||||||
|
);
|
||||||
|
|
||||||
Ok(module_parts)
|
Ok(module_parts)
|
||||||
}
|
}
|
||||||
@@ -255,22 +250,28 @@ fn register_popup_content(
|
|||||||
/// Handles opening/closing popups
|
/// Handles opening/closing popups
|
||||||
/// and communicating update messages between controllers and widgets/popups.
|
/// and communicating update messages between controllers and widgets/popups.
|
||||||
fn setup_receiver<TSend>(
|
fn setup_receiver<TSend>(
|
||||||
tx: broadcast::Sender<TSend>,
|
channel: BridgeChannel<ModuleUpdateEvent<TSend>>,
|
||||||
rx: mpsc::Receiver<ModuleUpdateEvent<TSend>>,
|
w_tx: glib::Sender<TSend>,
|
||||||
|
p_tx: glib::Sender<TSend>,
|
||||||
popup: Rc<RefCell<Popup>>,
|
popup: Rc<RefCell<Popup>>,
|
||||||
name: &'static str,
|
name: &'static str,
|
||||||
id: usize,
|
id: usize,
|
||||||
|
has_popup: bool,
|
||||||
) where
|
) where
|
||||||
TSend: Debug + Clone + Send + 'static,
|
TSend: Clone + Send + 'static,
|
||||||
{
|
{
|
||||||
// some rare cases can cause the popup to incorrectly calculate its size on first open.
|
// some rare cases can cause the popup to incorrectly calculate its size on first open.
|
||||||
// we can fix that by just force re-rendering it on its first open.
|
// we can fix that by just force re-rendering it on its first open.
|
||||||
let mut has_popup_opened = false;
|
let mut has_popup_opened = false;
|
||||||
|
|
||||||
glib_recv_mpsc!(rx, ev => {
|
channel.recv(move |ev| {
|
||||||
match ev {
|
match ev {
|
||||||
ModuleUpdateEvent::Update(update) => {
|
ModuleUpdateEvent::Update(update) => {
|
||||||
send!(tx, update);
|
if has_popup {
|
||||||
|
send!(p_tx, update.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
send!(w_tx, update);
|
||||||
}
|
}
|
||||||
ModuleUpdateEvent::TogglePopup(button_id) => {
|
ModuleUpdateEvent::TogglePopup(button_id) => {
|
||||||
debug!("Toggling popup for {} [#{}]", name, id);
|
debug!("Toggling popup for {} [#{}]", name, id);
|
||||||
@@ -320,6 +321,8 @@ fn setup_receiver<TSend>(
|
|||||||
popup.hide();
|
popup.hide();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Continue(true)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,11 +4,12 @@ use std::sync::Arc;
|
|||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use color_eyre::Result;
|
use color_eyre::Result;
|
||||||
use glib::{Propagation, PropertySet};
|
use glib::{Continue, PropertySet};
|
||||||
use gtk::prelude::*;
|
use gtk::prelude::*;
|
||||||
use gtk::{Button, IconTheme, Label, Orientation, Scale};
|
use gtk::{Button, IconTheme, Label, Orientation, Scale};
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
use tokio::sync::{broadcast, mpsc};
|
use tokio::spawn;
|
||||||
|
use tokio::sync::mpsc::{Receiver, Sender};
|
||||||
use tracing::error;
|
use tracing::error;
|
||||||
|
|
||||||
use crate::clients::music::{
|
use crate::clients::music::{
|
||||||
@@ -20,7 +21,7 @@ use crate::modules::PopupButton;
|
|||||||
use crate::modules::{
|
use crate::modules::{
|
||||||
Module, ModuleInfo, ModuleParts, ModulePopup, ModuleUpdateEvent, WidgetContext,
|
Module, ModuleInfo, ModuleParts, ModulePopup, ModuleUpdateEvent, WidgetContext,
|
||||||
};
|
};
|
||||||
use crate::{glib_recv, send_async, spawn, try_send};
|
use crate::{send_async, try_send};
|
||||||
|
|
||||||
pub use self::config::MusicModule;
|
pub use self::config::MusicModule;
|
||||||
use self::config::PlayerType;
|
use self::config::PlayerType;
|
||||||
@@ -90,8 +91,8 @@ impl Module<Button> for MusicModule {
|
|||||||
fn spawn_controller(
|
fn spawn_controller(
|
||||||
&self,
|
&self,
|
||||||
_info: &ModuleInfo,
|
_info: &ModuleInfo,
|
||||||
tx: mpsc::Sender<ModuleUpdateEvent<Self::SendMessage>>,
|
tx: Sender<ModuleUpdateEvent<Self::SendMessage>>,
|
||||||
mut rx: mpsc::Receiver<Self::ReceiveMessage>,
|
mut rx: Receiver<Self::ReceiveMessage>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let format = self.format.clone();
|
let format = self.format.clone();
|
||||||
|
|
||||||
@@ -212,13 +213,11 @@ impl Module<Button> for MusicModule {
|
|||||||
|
|
||||||
{
|
{
|
||||||
let button = button.clone();
|
let button = button.clone();
|
||||||
|
|
||||||
let tx = context.tx.clone();
|
let tx = context.tx.clone();
|
||||||
let rx = context.subscribe();
|
|
||||||
|
|
||||||
glib_recv!(rx, event => {
|
context.widget_rx.attach(None, move |event| {
|
||||||
let ControllerEvent::Update(mut event) = event else {
|
let ControllerEvent::Update(mut event) = event else {
|
||||||
continue;
|
return Continue(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(event) = event.take() {
|
if let Some(event) = event.take() {
|
||||||
@@ -249,12 +248,13 @@ impl Module<Button> for MusicModule {
|
|||||||
button.hide();
|
button.hide();
|
||||||
try_send!(tx, ModuleUpdateEvent::ClosePopup);
|
try_send!(tx, ModuleUpdateEvent::ClosePopup);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Continue(true)
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
let rx = context.subscribe();
|
|
||||||
let popup = self
|
let popup = self
|
||||||
.into_popup(context.controller_tx, rx, info)
|
.into_popup(context.controller_tx, context.popup_rx, info)
|
||||||
.into_popup_parts(vec![&button]);
|
.into_popup_parts(vec![&button]);
|
||||||
|
|
||||||
Ok(ModuleParts::new(button, popup))
|
Ok(ModuleParts::new(button, popup))
|
||||||
@@ -262,8 +262,8 @@ impl Module<Button> for MusicModule {
|
|||||||
|
|
||||||
fn into_popup(
|
fn into_popup(
|
||||||
self,
|
self,
|
||||||
tx: mpsc::Sender<Self::ReceiveMessage>,
|
tx: Sender<Self::ReceiveMessage>,
|
||||||
rx: broadcast::Receiver<Self::SendMessage>,
|
rx: glib::Receiver<Self::SendMessage>,
|
||||||
info: &ModuleInfo,
|
info: &ModuleInfo,
|
||||||
) -> Option<gtk::Box> {
|
) -> Option<gtk::Box> {
|
||||||
let icon_theme = info.icon_theme;
|
let icon_theme = info.icon_theme;
|
||||||
@@ -355,7 +355,7 @@ impl Module<Button> for MusicModule {
|
|||||||
let tx_vol = tx.clone();
|
let tx_vol = tx.clone();
|
||||||
volume_slider.connect_change_value(move |_, _, val| {
|
volume_slider.connect_change_value(move |_, _, val| {
|
||||||
try_send!(tx_vol, PlayerCommand::Volume(val as u8));
|
try_send!(tx_vol, PlayerCommand::Volume(val as u8));
|
||||||
Propagation::Proceed
|
Inhibit(false)
|
||||||
});
|
});
|
||||||
|
|
||||||
let progress_box = gtk::Box::new(Orientation::Horizontal, 5);
|
let progress_box = gtk::Box::new(Orientation::Horizontal, 5);
|
||||||
@@ -380,7 +380,7 @@ impl Module<Button> for MusicModule {
|
|||||||
let drag_lock = drag_lock.clone();
|
let drag_lock = drag_lock.clone();
|
||||||
progress.connect_button_press_event(move |_, _| {
|
progress.connect_button_press_event(move |_, _| {
|
||||||
drag_lock.set(true);
|
drag_lock.set(true);
|
||||||
Propagation::Proceed
|
Inhibit(false)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -391,7 +391,7 @@ impl Module<Button> for MusicModule {
|
|||||||
try_send!(tx, PlayerCommand::Seek(Duration::from_secs_f64(value)));
|
try_send!(tx, PlayerCommand::Seek(Duration::from_secs_f64(value)));
|
||||||
|
|
||||||
drag_lock.set(false);
|
drag_lock.set(false);
|
||||||
Propagation::Proceed
|
Inhibit(false)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -402,7 +402,7 @@ impl Module<Button> for MusicModule {
|
|||||||
let image_size = self.cover_image_size;
|
let image_size = self.cover_image_size;
|
||||||
|
|
||||||
let mut prev_cover = None;
|
let mut prev_cover = None;
|
||||||
glib_recv!(rx, event => {
|
rx.attach(None, move |event| {
|
||||||
match event {
|
match event {
|
||||||
ControllerEvent::Update(Some(update)) => {
|
ControllerEvent::Update(Some(update)) => {
|
||||||
// only update art when album changes
|
// only update art when album changes
|
||||||
@@ -460,7 +460,7 @@ impl Module<Button> for MusicModule {
|
|||||||
btn_next.set_sensitive(enable_next);
|
btn_next.set_sensitive(enable_next);
|
||||||
|
|
||||||
if let Some(volume) = update.status.volume_percent {
|
if let Some(volume) = update.status.volume_percent {
|
||||||
volume_slider.set_value(f64::from(volume));
|
volume_slider.set_value(volume as f64);
|
||||||
volume_box.show();
|
volume_box.show();
|
||||||
} else {
|
} else {
|
||||||
volume_box.hide();
|
volume_box.hide();
|
||||||
@@ -487,6 +487,8 @@ impl Module<Button> for MusicModule {
|
|||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Continue(true)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
use crate::config::CommonConfig;
|
use crate::config::CommonConfig;
|
||||||
use crate::modules::{Module, ModuleInfo, ModuleParts, ModuleUpdateEvent, WidgetContext};
|
use crate::modules::{Module, ModuleInfo, ModuleParts, ModuleUpdateEvent, WidgetContext};
|
||||||
use crate::script::{OutputStream, Script, ScriptMode};
|
use crate::script::{OutputStream, Script, ScriptMode};
|
||||||
use crate::{glib_recv, spawn, try_send};
|
use crate::try_send;
|
||||||
use color_eyre::{Help, Report, Result};
|
use color_eyre::{Help, Report, Result};
|
||||||
use gtk::prelude::*;
|
use gtk::prelude::*;
|
||||||
use gtk::Label;
|
use gtk::Label;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
use tokio::spawn;
|
||||||
use tokio::sync::mpsc::{Receiver, Sender};
|
use tokio::sync::mpsc::{Receiver, Sender};
|
||||||
use tracing::error;
|
use tracing::error;
|
||||||
|
|
||||||
@@ -88,7 +89,10 @@ impl Module<Label> for ScriptModule {
|
|||||||
|
|
||||||
{
|
{
|
||||||
let label = label.clone();
|
let label = label.clone();
|
||||||
glib_recv!(context.subscribe(), s => label.set_markup(s.as_str()));
|
context.widget_rx.attach(None, move |s| {
|
||||||
|
label.set_markup(s.as_str());
|
||||||
|
Continue(true)
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(ModuleParts {
|
Ok(ModuleParts {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
use crate::config::CommonConfig;
|
use crate::config::CommonConfig;
|
||||||
use crate::gtk_helpers::IronbarGtkExt;
|
use crate::gtk_helpers::IronbarGtkExt;
|
||||||
use crate::modules::{Module, ModuleInfo, ModuleParts, ModuleUpdateEvent, WidgetContext};
|
use crate::modules::{Module, ModuleInfo, ModuleParts, ModuleUpdateEvent, WidgetContext};
|
||||||
use crate::{glib_recv, send_async, spawn};
|
use crate::send_async;
|
||||||
use color_eyre::Result;
|
use color_eyre::Result;
|
||||||
use gtk::prelude::*;
|
use gtk::prelude::*;
|
||||||
use gtk::Label;
|
use gtk::Label;
|
||||||
@@ -10,6 +10,7 @@ use serde::Deserialize;
|
|||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use sysinfo::{ComponentExt, CpuExt, DiskExt, NetworkExt, RefreshKind, System, SystemExt};
|
use sysinfo::{ComponentExt, CpuExt, DiskExt, NetworkExt, RefreshKind, System, SystemExt};
|
||||||
|
use tokio::spawn;
|
||||||
use tokio::sync::mpsc;
|
use tokio::sync::mpsc;
|
||||||
use tokio::sync::mpsc::{Receiver, Sender};
|
use tokio::sync::mpsc::{Receiver, Sender};
|
||||||
use tokio::time::sleep;
|
use tokio::time::sleep;
|
||||||
@@ -204,7 +205,7 @@ impl Module<gtk::Box> for SysInfoModule {
|
|||||||
|
|
||||||
{
|
{
|
||||||
let formats = self.format;
|
let formats = self.format;
|
||||||
glib_recv!(context.subscribe(), info => {
|
context.widget_rx.attach(None, move |info| {
|
||||||
for (format, label) in formats.iter().zip(labels.clone()) {
|
for (format, label) in formats.iter().zip(labels.clone()) {
|
||||||
let format_compiled = re.replace_all(format, |caps: &Captures| {
|
let format_compiled = re.replace_all(format, |caps: &Captures| {
|
||||||
info.get(&caps[1])
|
info.get(&caps[1])
|
||||||
@@ -214,6 +215,8 @@ impl Module<gtk::Box> for SysInfoModule {
|
|||||||
|
|
||||||
label.set_markup(format_compiled.as_ref());
|
label.set_markup(format_compiled.as_ref());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Continue(true)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,8 @@
|
|||||||
use crate::clients::system_tray::get_tray_event_client;
|
use crate::clients::system_tray::get_tray_event_client;
|
||||||
use crate::config::CommonConfig;
|
use crate::config::CommonConfig;
|
||||||
use crate::modules::{Module, ModuleInfo, ModuleParts, ModuleUpdateEvent, WidgetContext};
|
use crate::modules::{Module, ModuleInfo, ModuleParts, ModuleUpdateEvent, WidgetContext};
|
||||||
use crate::{await_sync, glib_recv, spawn, try_send};
|
use crate::{await_sync, try_send};
|
||||||
use color_eyre::Result;
|
use color_eyre::Result;
|
||||||
use glib::ffi::g_strfreev;
|
|
||||||
use glib::translate::ToGlibPtr;
|
|
||||||
use gtk::ffi::gtk_icon_theme_get_search_path;
|
|
||||||
use gtk::gdk_pixbuf::{Colorspace, InterpType};
|
use gtk::gdk_pixbuf::{Colorspace, InterpType};
|
||||||
use gtk::prelude::*;
|
use gtk::prelude::*;
|
||||||
use gtk::{
|
use gtk::{
|
||||||
@@ -13,13 +10,11 @@ use gtk::{
|
|||||||
SeparatorMenuItem,
|
SeparatorMenuItem,
|
||||||
};
|
};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use std::collections::{HashMap, HashSet};
|
use std::collections::HashMap;
|
||||||
use std::ffi::CStr;
|
|
||||||
use std::os::raw::{c_char, c_int};
|
|
||||||
use std::ptr;
|
|
||||||
use system_tray::message::menu::{MenuItem as MenuItemInfo, MenuType};
|
use system_tray::message::menu::{MenuItem as MenuItemInfo, MenuType};
|
||||||
use system_tray::message::tray::StatusNotifierItem;
|
use system_tray::message::tray::StatusNotifierItem;
|
||||||
use system_tray::message::{NotifierItemCommand, NotifierItemMessage};
|
use system_tray::message::{NotifierItemCommand, NotifierItemMessage};
|
||||||
|
use tokio::spawn;
|
||||||
use tokio::sync::mpsc;
|
use tokio::sync::mpsc;
|
||||||
use tokio::sync::mpsc::{Receiver, Sender};
|
use tokio::sync::mpsc::{Receiver, Sender};
|
||||||
|
|
||||||
@@ -29,43 +24,21 @@ pub struct TrayModule {
|
|||||||
pub common: Option<CommonConfig>,
|
pub common: Option<CommonConfig>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Gets the GTK icon theme search paths by calling the FFI function.
|
|
||||||
/// Conveniently returns the result as a `HashSet`.
|
|
||||||
fn get_icon_theme_search_paths(icon_theme: &IconTheme) -> HashSet<String> {
|
|
||||||
let mut gtk_paths: *mut *mut c_char = ptr::null_mut();
|
|
||||||
let mut n_elements: c_int = 0;
|
|
||||||
let mut paths = HashSet::new();
|
|
||||||
unsafe {
|
|
||||||
gtk_icon_theme_get_search_path(
|
|
||||||
icon_theme.to_glib_none().0,
|
|
||||||
&mut gtk_paths,
|
|
||||||
&mut n_elements,
|
|
||||||
);
|
|
||||||
// n_elements is never negative (that would be weird)
|
|
||||||
for i in 0..n_elements as usize {
|
|
||||||
let c_str = CStr::from_ptr(*gtk_paths.add(i));
|
|
||||||
if let Ok(str) = c_str.to_str() {
|
|
||||||
paths.insert(str.to_owned());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
g_strfreev(gtk_paths);
|
|
||||||
}
|
|
||||||
|
|
||||||
paths
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Attempts to get a GTK `Image` component
|
/// Attempts to get a GTK `Image` component
|
||||||
/// for the status notifier item's icon.
|
/// for the status notifier item's icon.
|
||||||
fn get_image_from_icon_name(item: &StatusNotifierItem, icon_theme: &IconTheme) -> Option<Image> {
|
fn get_image_from_icon_name(item: &StatusNotifierItem) -> Option<Image> {
|
||||||
if let Some(path) = item.icon_theme_path.as_ref() {
|
let theme = item
|
||||||
if !path.is_empty() && !get_icon_theme_search_paths(icon_theme).contains(path) {
|
.icon_theme_path
|
||||||
icon_theme.append_search_path(path);
|
.as_ref()
|
||||||
}
|
.map(|path| {
|
||||||
}
|
let theme = IconTheme::new();
|
||||||
|
theme.append_search_path(path);
|
||||||
|
theme
|
||||||
|
})
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
item.icon_name.as_ref().and_then(|icon_name| {
|
item.icon_name.as_ref().and_then(|icon_name| {
|
||||||
let icon_info = icon_theme.lookup_icon(icon_name, 16, IconLookupFlags::empty());
|
let icon_info = theme.lookup_icon(icon_name, 16, IconLookupFlags::empty());
|
||||||
icon_info.map(|icon_info| Image::from_pixbuf(icon_info.load_icon().ok().as_ref()))
|
icon_info.map(|icon_info| Image::from_pixbuf(icon_info.load_icon().ok().as_ref()))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -198,17 +171,16 @@ impl Module<MenuBar> for TrayModule {
|
|||||||
fn into_widget(
|
fn into_widget(
|
||||||
self,
|
self,
|
||||||
context: WidgetContext<Self::SendMessage, Self::ReceiveMessage>,
|
context: WidgetContext<Self::SendMessage, Self::ReceiveMessage>,
|
||||||
info: &ModuleInfo,
|
_info: &ModuleInfo,
|
||||||
) -> Result<ModuleParts<MenuBar>> {
|
) -> Result<ModuleParts<MenuBar>> {
|
||||||
let container = MenuBar::new();
|
let container = MenuBar::new();
|
||||||
|
|
||||||
{
|
{
|
||||||
let container = container.clone();
|
let container = container.clone();
|
||||||
let mut widgets = HashMap::new();
|
let mut widgets = HashMap::new();
|
||||||
let icon_theme = info.icon_theme.clone();
|
|
||||||
|
|
||||||
// listen for UI updates
|
// listen for UI updates
|
||||||
glib_recv!(context.subscribe(), update => {
|
context.widget_rx.attach(None, move |update| {
|
||||||
match update {
|
match update {
|
||||||
NotifierItemMessage::Update {
|
NotifierItemMessage::Update {
|
||||||
item,
|
item,
|
||||||
@@ -220,7 +192,7 @@ impl Module<MenuBar> for TrayModule {
|
|||||||
let menu_item = MenuItem::new();
|
let menu_item = MenuItem::new();
|
||||||
menu_item.style_context().add_class("item");
|
menu_item.style_context().add_class("item");
|
||||||
|
|
||||||
get_image_from_icon_name(&item, &icon_theme)
|
get_image_from_icon_name(&item)
|
||||||
.or_else(|| get_image_from_pixmap(&item))
|
.or_else(|| get_image_from_pixmap(&item))
|
||||||
.map_or_else(
|
.map_or_else(
|
||||||
|| {
|
|| {
|
||||||
@@ -261,6 +233,8 @@ impl Module<MenuBar> for TrayModule {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Continue(true)
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,8 @@ use futures_lite::stream::StreamExt;
|
|||||||
use gtk::{prelude::*, Button};
|
use gtk::{prelude::*, Button};
|
||||||
use gtk::{Label, Orientation};
|
use gtk::{Label, Orientation};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use tokio::sync::{broadcast, mpsc};
|
use tokio::spawn;
|
||||||
|
use tokio::sync::mpsc::{Receiver, Sender};
|
||||||
use upower_dbus::BatteryState;
|
use upower_dbus::BatteryState;
|
||||||
use zbus;
|
use zbus;
|
||||||
|
|
||||||
@@ -15,7 +16,7 @@ use crate::modules::PopupButton;
|
|||||||
use crate::modules::{
|
use crate::modules::{
|
||||||
Module, ModuleInfo, ModuleParts, ModulePopup, ModuleUpdateEvent, WidgetContext,
|
Module, ModuleInfo, ModuleParts, ModulePopup, ModuleUpdateEvent, WidgetContext,
|
||||||
};
|
};
|
||||||
use crate::{await_sync, error, glib_recv, send_async, spawn, try_send};
|
use crate::{await_sync, error, send_async, try_send};
|
||||||
|
|
||||||
const DAY: i64 = 24 * 60 * 60;
|
const DAY: i64 = 24 * 60 * 60;
|
||||||
const HOUR: i64 = 60 * 60;
|
const HOUR: i64 = 60 * 60;
|
||||||
@@ -61,8 +62,8 @@ impl Module<gtk::Button> for UpowerModule {
|
|||||||
fn spawn_controller(
|
fn spawn_controller(
|
||||||
&self,
|
&self,
|
||||||
_info: &ModuleInfo,
|
_info: &ModuleInfo,
|
||||||
tx: mpsc::Sender<ModuleUpdateEvent<Self::SendMessage>>,
|
tx: Sender<ModuleUpdateEvent<Self::SendMessage>>,
|
||||||
_rx: mpsc::Receiver<Self::ReceiveMessage>,
|
_rx: Receiver<Self::ReceiveMessage>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
spawn(async move {
|
spawn(async move {
|
||||||
// await_sync due to strange "higher-ranked lifetime error"
|
// await_sync due to strange "higher-ranked lifetime error"
|
||||||
@@ -173,28 +174,29 @@ impl Module<gtk::Button> for UpowerModule {
|
|||||||
container.add(&label);
|
container.add(&label);
|
||||||
button.add(&container);
|
button.add(&container);
|
||||||
|
|
||||||
let tx = context.tx.clone();
|
|
||||||
button.connect_clicked(move |button| {
|
button.connect_clicked(move |button| {
|
||||||
try_send!(tx, ModuleUpdateEvent::TogglePopup(button.popup_id()));
|
try_send!(
|
||||||
|
context.tx,
|
||||||
|
ModuleUpdateEvent::TogglePopup(button.popup_id())
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
label.set_angle(info.bar_position.get_angle());
|
label.set_angle(info.bar_position.get_angle());
|
||||||
let format = self.format.clone();
|
let format = self.format.clone();
|
||||||
|
|
||||||
let rx = context.subscribe();
|
context
|
||||||
glib_recv!(rx, properties => {
|
.widget_rx
|
||||||
let format = format.replace("{percentage}", &properties.percentage.to_string());
|
.attach(None, move |properties: UpowerProperties| {
|
||||||
let icon_name = String::from("icon:") + &properties.icon_name;
|
let format = format.replace("{percentage}", &properties.percentage.to_string());
|
||||||
|
let icon_name = String::from("icon:") + &properties.icon_name;
|
||||||
ImageProvider::parse(&icon_name, &icon_theme, false, self.icon_size)
|
ImageProvider::parse(&icon_name, &icon_theme, false, self.icon_size)
|
||||||
.map(|provider| provider.load_into_image(icon.clone()));
|
.map(|provider| provider.load_into_image(icon.clone()));
|
||||||
|
label.set_markup(format.as_ref());
|
||||||
|
Continue(true)
|
||||||
|
});
|
||||||
|
|
||||||
label.set_markup(format.as_ref());
|
|
||||||
});
|
|
||||||
|
|
||||||
let rx = context.subscribe();
|
|
||||||
let popup = self
|
let popup = self
|
||||||
.into_popup(context.controller_tx, rx, info)
|
.into_popup(context.controller_tx, context.popup_rx, info)
|
||||||
.into_popup_parts(vec![&button]);
|
.into_popup_parts(vec![&button]);
|
||||||
|
|
||||||
Ok(ModuleParts::new(button, popup))
|
Ok(ModuleParts::new(button, popup))
|
||||||
@@ -202,8 +204,8 @@ impl Module<gtk::Button> for UpowerModule {
|
|||||||
|
|
||||||
fn into_popup(
|
fn into_popup(
|
||||||
self,
|
self,
|
||||||
_tx: mpsc::Sender<Self::ReceiveMessage>,
|
_tx: Sender<Self::ReceiveMessage>,
|
||||||
rx: broadcast::Receiver<Self::SendMessage>,
|
rx: glib::Receiver<Self::SendMessage>,
|
||||||
_info: &ModuleInfo,
|
_info: &ModuleInfo,
|
||||||
) -> Option<gtk::Box>
|
) -> Option<gtk::Box>
|
||||||
where
|
where
|
||||||
@@ -217,7 +219,7 @@ impl Module<gtk::Button> for UpowerModule {
|
|||||||
label.add_class("upower-details");
|
label.add_class("upower-details");
|
||||||
container.add(&label);
|
container.add(&label);
|
||||||
|
|
||||||
glib_recv!(rx, properties => {
|
rx.attach(None, move |properties| {
|
||||||
let state = u32_to_battery_state(properties.state);
|
let state = u32_to_battery_state(properties.state);
|
||||||
let format = match state {
|
let format = match state {
|
||||||
Ok(BatteryState::Charging | BatteryState::PendingCharge) => {
|
Ok(BatteryState::Charging | BatteryState::PendingCharge) => {
|
||||||
@@ -244,6 +246,7 @@ impl Module<gtk::Button> for UpowerModule {
|
|||||||
};
|
};
|
||||||
|
|
||||||
label.set_markup(&format);
|
label.set_markup(&format);
|
||||||
|
Continue(true)
|
||||||
});
|
});
|
||||||
|
|
||||||
container.show_all();
|
container.show_all();
|
||||||
|
|||||||
@@ -2,15 +2,16 @@ use crate::clients::compositor::{Compositor, Visibility, Workspace, WorkspaceUpd
|
|||||||
use crate::config::CommonConfig;
|
use crate::config::CommonConfig;
|
||||||
use crate::image::new_icon_button;
|
use crate::image::new_icon_button;
|
||||||
use crate::modules::{Module, ModuleInfo, ModuleParts, ModuleUpdateEvent, WidgetContext};
|
use crate::modules::{Module, ModuleInfo, ModuleParts, ModuleUpdateEvent, WidgetContext};
|
||||||
use crate::{glib_recv, send_async, spawn, try_send};
|
use crate::{send_async, try_send};
|
||||||
use color_eyre::{Report, Result};
|
use color_eyre::{Report, Result};
|
||||||
use gtk::prelude::*;
|
use gtk::prelude::*;
|
||||||
use gtk::{Button, IconTheme};
|
use gtk::{Button, IconTheme};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use std::cmp::Ordering;
|
use std::cmp::Ordering;
|
||||||
use std::collections::{HashMap, HashSet};
|
use std::collections::{HashMap, HashSet};
|
||||||
|
use tokio::spawn;
|
||||||
use tokio::sync::mpsc::{Receiver, Sender};
|
use tokio::sync::mpsc::{Receiver, Sender};
|
||||||
use tracing::{debug, trace, warn};
|
use tracing::trace;
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Clone, Copy, Eq, PartialEq)]
|
#[derive(Debug, Deserialize, Clone, Copy, Eq, PartialEq)]
|
||||||
#[serde(rename_all = "snake_case")]
|
#[serde(rename_all = "snake_case")]
|
||||||
@@ -97,10 +98,6 @@ fn create_button(
|
|||||||
style_context.add_class("focused");
|
style_context.add_class("focused");
|
||||||
}
|
}
|
||||||
|
|
||||||
if !visibility.is_visible() {
|
|
||||||
style_context.add_class("inactive")
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
{
|
||||||
let tx = tx.clone();
|
let tx = tx.clone();
|
||||||
let name = name.to_string();
|
let name = name.to_string();
|
||||||
@@ -162,10 +159,9 @@ impl Module<gtk::Box> for WorkspacesModule {
|
|||||||
client.subscribe_workspace_change()
|
client.subscribe_workspace_change()
|
||||||
};
|
};
|
||||||
|
|
||||||
trace!("Set up workspace subscription");
|
trace!("Set up Sway workspace subscription");
|
||||||
|
|
||||||
while let Ok(payload) = srx.recv().await {
|
while let Ok(payload) = srx.recv().await {
|
||||||
debug!("Received update: {payload:?}");
|
|
||||||
send_async!(tx, ModuleUpdateEvent::Update(payload));
|
send_async!(tx, ModuleUpdateEvent::Update(payload));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -209,7 +205,7 @@ impl Module<gtk::Box> for WorkspacesModule {
|
|||||||
// since it fires for every workspace subscriber
|
// since it fires for every workspace subscriber
|
||||||
let mut has_initialized = false;
|
let mut has_initialized = false;
|
||||||
|
|
||||||
glib_recv!(context.subscribe(), event => {
|
context.widget_rx.attach(None, move |event| {
|
||||||
match event {
|
match event {
|
||||||
WorkspaceUpdate::Init(workspaces) => {
|
WorkspaceUpdate::Init(workspaces) => {
|
||||||
if !has_initialized {
|
if !has_initialized {
|
||||||
@@ -352,8 +348,10 @@ impl Module<gtk::Box> for WorkspacesModule {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
WorkspaceUpdate::Unknown => warn!("Received unknown type workspace event")
|
WorkspaceUpdate::Update(_) => {}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Continue(true)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
62
src/popup.rs
62
src/popup.rs
@@ -1,27 +1,19 @@
|
|||||||
use glib::Propagation;
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use gtk::gdk::Monitor;
|
use gtk::gdk::Monitor;
|
||||||
use gtk::prelude::*;
|
use gtk::prelude::*;
|
||||||
use gtk::{ApplicationWindow, Orientation};
|
use gtk::{ApplicationWindow, Orientation};
|
||||||
use gtk_layer_shell::LayerShell;
|
|
||||||
use tracing::debug;
|
use tracing::debug;
|
||||||
|
|
||||||
use crate::config::BarPosition;
|
use crate::config::BarPosition;
|
||||||
use crate::gtk_helpers::{IronbarGtkExt, WidgetGeometry};
|
use crate::gtk_helpers::{IronbarGtkExt, WidgetGeometry};
|
||||||
use crate::modules::{ModuleInfo, ModulePopupParts, PopupButton};
|
use crate::modules::{ModuleInfo, ModulePopupParts, PopupButton};
|
||||||
use crate::Ironbar;
|
use crate::unique_id::get_unique_usize;
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct PopupCacheValue {
|
|
||||||
pub name: String,
|
|
||||||
pub content: ModulePopupParts,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Popup {
|
pub struct Popup {
|
||||||
pub window: ApplicationWindow,
|
pub window: ApplicationWindow,
|
||||||
pub cache: HashMap<usize, PopupCacheValue>,
|
pub cache: HashMap<usize, (String, ModulePopupParts)>,
|
||||||
monitor: Monitor,
|
monitor: Monitor,
|
||||||
pos: BarPosition,
|
pos: BarPosition,
|
||||||
current_widget: Option<usize>,
|
current_widget: Option<usize>,
|
||||||
@@ -39,38 +31,52 @@ impl Popup {
|
|||||||
.application(module_info.app)
|
.application(module_info.app)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
win.init_layer_shell();
|
gtk_layer_shell::init_for_window(&win);
|
||||||
win.set_monitor(module_info.monitor);
|
gtk_layer_shell::set_monitor(&win, module_info.monitor);
|
||||||
win.set_layer(gtk_layer_shell::Layer::Overlay);
|
gtk_layer_shell::set_layer(&win, gtk_layer_shell::Layer::Overlay);
|
||||||
win.set_namespace(env!("CARGO_PKG_NAME"));
|
gtk_layer_shell::set_namespace(&win, env!("CARGO_PKG_NAME"));
|
||||||
|
|
||||||
win.set_layer_shell_margin(
|
gtk_layer_shell::set_margin(
|
||||||
|
&win,
|
||||||
gtk_layer_shell::Edge::Top,
|
gtk_layer_shell::Edge::Top,
|
||||||
if pos == BarPosition::Top { gap } else { 0 },
|
if pos == BarPosition::Top { gap } else { 0 },
|
||||||
);
|
);
|
||||||
win.set_layer_shell_margin(
|
gtk_layer_shell::set_margin(
|
||||||
|
&win,
|
||||||
gtk_layer_shell::Edge::Bottom,
|
gtk_layer_shell::Edge::Bottom,
|
||||||
if pos == BarPosition::Bottom { gap } else { 0 },
|
if pos == BarPosition::Bottom { gap } else { 0 },
|
||||||
);
|
);
|
||||||
win.set_layer_shell_margin(
|
gtk_layer_shell::set_margin(
|
||||||
|
&win,
|
||||||
gtk_layer_shell::Edge::Left,
|
gtk_layer_shell::Edge::Left,
|
||||||
if pos == BarPosition::Left { gap } else { 0 },
|
if pos == BarPosition::Left { gap } else { 0 },
|
||||||
);
|
);
|
||||||
win.set_layer_shell_margin(
|
gtk_layer_shell::set_margin(
|
||||||
|
&win,
|
||||||
gtk_layer_shell::Edge::Right,
|
gtk_layer_shell::Edge::Right,
|
||||||
if pos == BarPosition::Right { gap } else { 0 },
|
if pos == BarPosition::Right { gap } else { 0 },
|
||||||
);
|
);
|
||||||
|
|
||||||
win.set_anchor(
|
gtk_layer_shell::set_anchor(
|
||||||
|
&win,
|
||||||
gtk_layer_shell::Edge::Top,
|
gtk_layer_shell::Edge::Top,
|
||||||
pos == BarPosition::Top || orientation == Orientation::Vertical,
|
pos == BarPosition::Top || orientation == Orientation::Vertical,
|
||||||
);
|
);
|
||||||
win.set_anchor(gtk_layer_shell::Edge::Bottom, pos == BarPosition::Bottom);
|
gtk_layer_shell::set_anchor(
|
||||||
win.set_anchor(
|
&win,
|
||||||
|
gtk_layer_shell::Edge::Bottom,
|
||||||
|
pos == BarPosition::Bottom,
|
||||||
|
);
|
||||||
|
gtk_layer_shell::set_anchor(
|
||||||
|
&win,
|
||||||
gtk_layer_shell::Edge::Left,
|
gtk_layer_shell::Edge::Left,
|
||||||
pos == BarPosition::Left || orientation == Orientation::Horizontal,
|
pos == BarPosition::Left || orientation == Orientation::Horizontal,
|
||||||
);
|
);
|
||||||
win.set_anchor(gtk_layer_shell::Edge::Right, pos == BarPosition::Right);
|
gtk_layer_shell::set_anchor(
|
||||||
|
&win,
|
||||||
|
gtk_layer_shell::Edge::Right,
|
||||||
|
pos == BarPosition::Right,
|
||||||
|
);
|
||||||
|
|
||||||
win.connect_leave_notify_event(move |win, ev| {
|
win.connect_leave_notify_event(move |win, ev| {
|
||||||
const THRESHOLD: f64 = 3.0;
|
const THRESHOLD: f64 = 3.0;
|
||||||
@@ -99,7 +105,7 @@ impl Popup {
|
|||||||
win.hide();
|
win.hide();
|
||||||
}
|
}
|
||||||
|
|
||||||
Propagation::Proceed
|
Inhibit(false)
|
||||||
});
|
});
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
@@ -115,17 +121,17 @@ impl Popup {
|
|||||||
debug!("Registered popup content for #{}", key);
|
debug!("Registered popup content for #{}", key);
|
||||||
|
|
||||||
for button in &content.buttons {
|
for button in &content.buttons {
|
||||||
let id = Ironbar::unique_id();
|
let id = get_unique_usize();
|
||||||
button.set_tag("popup-id", id);
|
button.set_tag("popup-id", id);
|
||||||
}
|
}
|
||||||
|
|
||||||
self.cache.insert(key, PopupCacheValue { name, content });
|
self.cache.insert(key, (name, content));
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn show(&mut self, widget_id: usize, button_id: usize) {
|
pub fn show(&mut self, widget_id: usize, button_id: usize) {
|
||||||
self.clear_window();
|
self.clear_window();
|
||||||
|
|
||||||
if let Some(PopupCacheValue { content, .. }) = self.cache.get(&widget_id) {
|
if let Some((_name, content)) = self.cache.get(&widget_id) {
|
||||||
self.current_widget = Some(widget_id);
|
self.current_widget = Some(widget_id);
|
||||||
|
|
||||||
content.container.style_context().add_class("popup");
|
content.container.style_context().add_class("popup");
|
||||||
@@ -149,7 +155,7 @@ impl Popup {
|
|||||||
pub fn show_at(&self, widget_id: usize, geometry: WidgetGeometry) {
|
pub fn show_at(&self, widget_id: usize, geometry: WidgetGeometry) {
|
||||||
self.clear_window();
|
self.clear_window();
|
||||||
|
|
||||||
if let Some(PopupCacheValue { content, .. }) = self.cache.get(&widget_id) {
|
if let Some((_name, content)) = self.cache.get(&widget_id) {
|
||||||
content.container.style_context().add_class("popup");
|
content.container.style_context().add_class("popup");
|
||||||
self.window.add(&content.container);
|
self.window.add(&content.container);
|
||||||
|
|
||||||
@@ -217,6 +223,6 @@ impl Popup {
|
|||||||
gtk_layer_shell::Edge::Top
|
gtk_layer_shell::Edge::Top
|
||||||
};
|
};
|
||||||
|
|
||||||
self.window.set_layer_shell_margin(edge, offset as i32);
|
gtk_layer_shell::set_margin(&self.window, edge, offset as i32);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -205,7 +205,7 @@ impl Script {
|
|||||||
Ok(output) => callback(output.0, output.1),
|
Ok(output) => callback(output.0, output.1),
|
||||||
Err(err) => error!("{err:?}"),
|
Err(err) => error!("{err:?}"),
|
||||||
},
|
},
|
||||||
ScriptMode::Watch => match self.spawn() {
|
ScriptMode::Watch => match self.spawn().await {
|
||||||
Ok(mut rx) => {
|
Ok(mut rx) => {
|
||||||
while let Some(msg) = rx.recv().await {
|
while let Some(msg) = rx.recv().await {
|
||||||
callback(msg, true);
|
callback(msg, true);
|
||||||
@@ -264,7 +264,7 @@ impl Script {
|
|||||||
/// Spawns a long-running process.
|
/// Spawns a long-running process.
|
||||||
/// Returns a `mpsc::Receiver` that sends a message
|
/// Returns a `mpsc::Receiver` that sends a message
|
||||||
/// every time a new line is written to `stdout` or `stderr`.
|
/// every time a new line is written to `stdout` or `stderr`.
|
||||||
pub fn spawn(&self) -> Result<mpsc::Receiver<OutputStream>> {
|
pub async fn spawn(&self) -> Result<mpsc::Receiver<OutputStream>> {
|
||||||
let mut handle = Command::new("/bin/sh")
|
let mut handle = Command::new("/bin/sh")
|
||||||
.args(["-c", &self.cmd])
|
.args(["-c", &self.cmd])
|
||||||
.stdout(Stdio::piped())
|
.stdout(Stdio::piped())
|
||||||
|
|||||||
50
src/style.rs
50
src/style.rs
@@ -1,13 +1,14 @@
|
|||||||
use crate::{glib_recv_mpsc, spawn, try_send};
|
use crate::send;
|
||||||
use color_eyre::{Help, Report};
|
use color_eyre::{Help, Report};
|
||||||
|
use glib::Continue;
|
||||||
use gtk::ffi::GTK_STYLE_PROVIDER_PRIORITY_USER;
|
use gtk::ffi::GTK_STYLE_PROVIDER_PRIORITY_USER;
|
||||||
use gtk::prelude::CssProviderExt;
|
use gtk::prelude::CssProviderExt;
|
||||||
use gtk::{gdk, gio, CssProvider, StyleContext};
|
use gtk::{gdk, gio, CssProvider, StyleContext};
|
||||||
use notify::event::ModifyKind;
|
use notify::event::{DataChange, ModifyKind};
|
||||||
use notify::{recommended_watcher, Event, EventKind, RecursiveMode, Result, Watcher};
|
use notify::{recommended_watcher, Event, EventKind, RecursiveMode, Result, Watcher};
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use tokio::sync::mpsc;
|
use tokio::spawn;
|
||||||
use tokio::time::sleep;
|
use tokio::time::sleep;
|
||||||
use tracing::{debug, error, info};
|
use tracing::{debug, error, info};
|
||||||
|
|
||||||
@@ -35,20 +36,14 @@ pub fn load_css(style_path: PathBuf) {
|
|||||||
GTK_STYLE_PROVIDER_PRIORITY_USER as u32,
|
GTK_STYLE_PROVIDER_PRIORITY_USER as u32,
|
||||||
);
|
);
|
||||||
|
|
||||||
let (tx, rx) = mpsc::channel(8);
|
let (tx, rx) = glib::MainContext::channel(glib::PRIORITY_DEFAULT);
|
||||||
|
|
||||||
spawn(async move {
|
spawn(async move {
|
||||||
let style_path2 = style_path.clone();
|
|
||||||
let mut watcher = recommended_watcher(move |res: Result<Event>| match res {
|
let mut watcher = recommended_watcher(move |res: Result<Event>| match res {
|
||||||
Ok(event) if matches!(event.kind, EventKind::Modify(ModifyKind::Data(_))) => {
|
Ok(event) if event.kind == EventKind::Modify(ModifyKind::Data(DataChange::Any)) => {
|
||||||
debug!("{event:?}");
|
debug!("{event:?}");
|
||||||
if event
|
if let Some(path) = event.paths.first() {
|
||||||
.paths
|
send!(tx, path.clone());
|
||||||
.first()
|
|
||||||
.map(|p| p == &style_path2)
|
|
||||||
.unwrap_or_default()
|
|
||||||
{
|
|
||||||
try_send!(tx, style_path2.clone());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(e) => error!("Error occurred when watching stylesheet: {:?}", e),
|
Err(e) => error!("Error occurred when watching stylesheet: {:?}", e),
|
||||||
@@ -56,10 +51,8 @@ pub fn load_css(style_path: PathBuf) {
|
|||||||
})
|
})
|
||||||
.expect("Failed to create CSS file watcher");
|
.expect("Failed to create CSS file watcher");
|
||||||
|
|
||||||
let dir_path = style_path.parent().expect("to exist");
|
|
||||||
|
|
||||||
watcher
|
watcher
|
||||||
.watch(dir_path, RecursiveMode::NonRecursive)
|
.watch(&style_path, RecursiveMode::NonRecursive)
|
||||||
.expect("Failed to start CSS file watcher");
|
.expect("Failed to start CSS file watcher");
|
||||||
debug!("Installed CSS file watcher on '{}'", style_path.display());
|
debug!("Installed CSS file watcher on '{}'", style_path.display());
|
||||||
|
|
||||||
@@ -69,14 +62,19 @@ pub fn load_css(style_path: PathBuf) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
glib_recv_mpsc!(rx, path => {
|
{
|
||||||
info!("Reloading CSS");
|
rx.attach(None, move |path| {
|
||||||
if let Err(err) = provider.load_from_file(&gio::File::for_path(path)) {
|
info!("Reloading CSS");
|
||||||
error!("{:?}", Report::new(err)
|
if let Err(err) = provider
|
||||||
.wrap_err("Failed to load CSS")
|
.load_from_file(&gio::File::for_path(path)) {
|
||||||
.suggestion("Check the CSS file for errors")
|
error!("{:?}", Report::new(err)
|
||||||
.suggestion("GTK CSS uses a subset of the full CSS spec and many properties are not available. Ensure you are not using any unsupported property.")
|
.wrap_err("Failed to load CSS")
|
||||||
);
|
.suggestion("Check the CSS file for errors")
|
||||||
}
|
.suggestion("GTK CSS uses a subset of the full CSS spec and many properties are not available. Ensure you are not using any unsupported property.")
|
||||||
});
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Continue(true)
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
9
src/unique_id.rs
Normal file
9
src/unique_id.rs
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||||
|
|
||||||
|
static COUNTER: AtomicUsize = AtomicUsize::new(1);
|
||||||
|
|
||||||
|
/// Gets a `usize` ID value that is unique to the entire Ironbar instance.
|
||||||
|
/// This is just an `AtomicUsize` that increments every time this function is called.
|
||||||
|
pub fn get_unique_usize() -> usize {
|
||||||
|
COUNTER.fetch_add(1, Ordering::Relaxed)
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user