Compare commits

...
Sign in to create a new pull request.

48 commits

Author SHA1 Message Date
80404504ed fix(wayland): this may fix it 2025-02-22 16:29:29 +01:00
43c8316380 fix(wayland): this may fix it 2025-02-22 16:27:22 +01:00
Daniel Eklöf
fc24ea225d
changelog: fix ref (again) for #405 - the issue number is #404 2025-01-01 13:57:32 +01:00
Daniel Eklöf
e1f7c0292f
changelog: fix ref for #405 2025-01-01 13:52:52 +01:00
vova
d746d12f6a
add niri-workspaces and niri-language modules 2025-01-01 13:51:25 +01:00
Ben Boeckel
61d082c802
typos: fix some typos 2025-01-01 13:49:32 +01:00
Ben Boeckel
57711f0dbe mpd: support the single flag
This flag indicates that `mpd` will automatically stop after the
current song is played.
2024-12-28 21:04:54 +01:00
Alexey Yerin
b15714b38a pipewire: Improve handling of node switching
When switching to a node that has a missing property, yambar didn't
reset its internal state to the default value, causing outdated
information to be displayed.
2024-11-27 00:03:12 +03:00
Daniel Eklöf
3e0083c9f2
module/removables: no need to open+fdopen, just do fopen() 2024-10-23 09:40:06 +02:00
Daniel Eklöf
a367895dc6
Open sockets, files etc with FD_CLOEXEC 2024-10-23 09:36:59 +02:00
Mathias Stærk
650d1f13f9
docs: fix typo in example 2024-10-08 15:42:03 +02:00
Daniel Eklöf
e1b6a78f22
doc: particles: remove trailing spaces 2024-10-02 08:11:02 +02:00
Daniel Eklöf
20d48a753b
particle/map: code style 2024-10-02 08:10:53 +02:00
Daniel Eklöf
37ecc251a4
changelog: line-wrap 2024-10-02 08:10:30 +02:00
bagnaram
4826a52306
string like operation 2024-09-16 10:39:12 -05:00
Daniel Eklöf
0f47cbb889
changelog: i3/sway: output tag 2024-09-07 08:35:58 +02:00
Daniel Eklöf
c3f7fe013d
doc: i3/sway: add 'output' to tag list 2024-09-07 08:35:39 +02:00
Daniel Eklöf
b81e41c3c4
module/i3: add 'output' tag
This allows bars to render workspaces differently, depending on which
output the workspace is on:

    - map:
      default: ...
      conditions:
        output == DP-1:
          ...
2024-09-05 11:56:10 +02:00
Daniel Eklöf
060586dbbe
tag: remove the :b formatter
Superseded by /N. Removing since a) it's no longer needed, and b) its
name is not consistent with the other kb/mb/gb formatters.
2024-09-05 08:23:47 +02:00
Daniel Eklöf
c80bae7604
changelog: /N tag formatter 2024-09-05 08:20:59 +02:00
Zhong Jianxin
311c481bfe
tag: add '/N' formatter 2024-09-05 08:19:28 +02:00
Zhong Jianxin
9498d7e445
tag: combine FMT_*BYTE into one FMT_DIVIDE 2024-09-05 08:19:28 +02:00
Daniel Eklöf
2d651d1c0e
changelog: bar position in multi-monitor setups, with location=bottom 2024-09-05 08:16:25 +02:00
fraktal
1b2dee55ef fix bar Y position in case of multi-monitor setups with mixed resolutions 2024-09-04 15:33:25 +02:00
Daniel Eklöf
700bf5b28c
tag: add 'b' formatter
Divides the tag's decimal value by 8.

Closes #392
2024-08-20 14:34:45 +02:00
Daniel Eklöf
f8ba887dcd
readme: repology: use four columns 2024-08-20 09:11:17 +02:00
Daniel Eklöf
887e770202
doc: network: update example
Only display Ethernet and WLAN devices (not loopback, bridges etc).
2024-08-20 07:40:09 +02:00
Daniel Eklöf
54902f46ab
module: network: hardcode type to "wlan" when we see NL80211_CMD_NEW_INTERFACE
Wlan interfaces apparently report themselves as ARPHRD_ETHER in their
ifinfomsg struct (despite there being a ARPHRD_IEEE80211 type...).

"Fix" by hardcoding the type to "wlan" when we receive a
NL80211_CMD_NEW_INTERFACE message.
2024-08-20 07:40:08 +02:00
Daniel Eklöf
699c563051
module: network: add 'kind' tag
The tag maps to the IFLA_INFO_KIND (part of the IFLA_LINKINFO)
netlink attribute.

This attribute is only available on virtual interfaces. Examples of
valid values are:

* bond
* bridge
* gre
* tun
* veth
2024-08-20 07:40:08 +02:00
Daniel Eklöf
a5ae61b5df
module: network: add 'type` tag
This tag maps to the ifinfomsg->ifi_type member, which is set to one
of the ARPHRD_xyz values, defined in linux/if_arp.h.

There's a *ton* of them, and we can't possibly add a string mapping
for _all_ of them, so for now, set to one of:

* loopback
* ether
* wlan
* ARPHRD_NNN, where N is a number
2024-08-20 07:39:59 +02:00
Daniel Eklöf
568eb1140f
modules/mpd: fix reconnect when we're not using inotify
When we're not able to use inotify, we rely on polling. However, we
never detected poll() timeouts, which meant we never re-attempted to
reconnect to MPD.

Maybe #394
2024-08-20 07:34:46 +02:00
Daniel Eklöf
3e0a65f185
meson: fix misdetection of memfd_create() 2024-08-07 17:27:26 +02:00
Daniel Eklöf
1a323c6d21
log: respect the NO_COLOR environment variable
http://no-color.org/
2024-07-18 08:31:46 +02:00
Daniel Eklöf
739dc30323
changelog: env var substitution: fix issue reference
Reference the issue, not the PR.
2024-06-09 10:12:32 +02:00
Daniel Eklöf
8422e7e0b1
doc: yambar(5): remove trailing whitespace 2024-06-09 10:12:23 +02:00
Daniel Eklöf
9cc5e0f7a7
module/network: plug memory leak
Free the 'ifaces' list, not just its contents.
2024-06-09 10:08:38 +02:00
Daniel Eklöf
3431d5fc75
yml: replace_env_variables(): const:ify function variables 2024-06-09 10:05:21 +02:00
Tomas Slusny
20659d3350 Add support for environment variable references
The format is
key: ${env_variable}

Closes #96

Signed-off-by: Tomas Slusny <slusnucky@gmail.com>
2024-06-09 01:11:34 +02:00
Daniel Eklöf
0bea49b75e
module/river: return empty particle list when river is not running
Closes #384
2024-05-20 09:33:45 +02:00
Daniel Eklöf
70efd7d15c
doc: yambar-particles: document the hard-coded spacing of short-form lists
Closes #385
2024-05-20 09:21:29 +02:00
Daniel Eklöf
b8a93a2673
changelog: i3/sway crash fix for output being turned on/off 2024-05-20 07:45:49 +02:00
QuincePie
a467f56677
i3: Handle FALLBACK output for workspaces.
sway moves the workspace to fallback_output when there is no output. For example: when all the screens are off. This commit adds an ignore for the fallback output.
2024-05-20 07:44:50 +02:00
Delgan
b3313cefc6 Fix remaining typos in the codebase (and update CI checks) 2024-05-02 16:28:51 +00:00
betazoid
00234696fe
Add examples/river-minimal.yml 2024-04-30 08:57:36 +02:00
Birger Schacht
3a7455913f
fix: typo
Probaly -> Probably
2024-04-30 08:56:18 +02:00
Sertonix
547cef5afb network: fix missing break in switch statement
This can cause the first character of the string to be read as an iface state.

Fixes https://codeberg.org/dnkl/yambar/issues/377
2024-04-22 16:23:16 +02:00
Daniel Eklöf
6f3952819f
changelog: add new 'unreleased' section 2024-04-17 10:42:56 +02:00
Daniel Eklöf
f148c68736
Merge branch 'releases/1.11' 2024-04-17 10:42:35 +02:00
54 changed files with 1477 additions and 215 deletions

View file

@ -12,7 +12,7 @@ steps:
- python3 -m venv codespell-venv
- source codespell-venv/bin/activate
- pip install codespell
- codespell README.md CHANGELOG.md *.c *.h doc/*.scd
- codespell README.md CHANGELOG.md *.c *.h doc/*.scd bar decorations modules particles examples
- deactivate
- name: subprojects

View file

@ -1,5 +1,6 @@
# Changelog
* [Unreleased](#unreleased)
* [1.11.0](#1-11-0)
* [1.10.0](#1-10-0)
* [1.9.0](#1-9-0)
@ -11,6 +12,60 @@
* [1.5.0](#1-5-0)
## Unreleased
### Added
* environment variable substitution in config files ([#96][96]).
* Log output now respects the [`NO_COLOR`](http://no-color.org/)
environment variable.
* network: `type` tag ([#380][380]).
* network: `type` and `kind` tags ([#380][380]).
* tags: `/<N>` tag formatter: divides the tag's decimal value with `N`
([#392][392]).
* i3/sway: `output` tag, reflecting the output (monitor) a workspace
is on.
* Added "string like" `~~` operator to Map particle. Allows glob-style
matching on strings using `*` and `?` characters ([#400][400]).
* Added "single" mode flag to the `mpd` module ([#428][428]).
* niri: add a new module for niri-workspaces and niri-language
([#404][404]).
[96]: https://codeberg.org/dnkl/yambar/issues/96
[380]: https://codeberg.org/dnkl/yambar/issues/380
[392]: https://codeberg.org/dnkl/yambar/issues/392
[400]: https://codeberg.org/dnkl/yambar/pulls/400
[428]: https://codeberg.org/dnkl/yambar/pulls/428
[404]: https://codeberg.org/dnkl/yambar/issues/404
### Changed
* `river`: expand to an empty list of particles when river is not
running ([#384][384]).
[384]: https://codeberg.org/dnkl/yambar/issues/384
### Deprecated
### Removed
### Fixed
* network: fix missing break in switch statement ([#377][377]).
* i3/sway: crash when output is turned off an on ([#300][300]).
* mpd: yambar never attempting to reconnect after MPD closed the
connection (for example, when MPD is restarted).
* Bar positioning on multi-monitor setups, when `location=bottom`.
* pipewire: Improve handling of node switching ([#424][424]).
[377]: https://codeberg.org/dnkl/yambar/issues/377
[300]: https://codeberg.org/dnkl/yambar/issues/300
[424]: https://codeberg.org/dnkl/yambar/pulls/424
### Security
### Contributors
## 1.11.0
### Added

View file

@ -2,7 +2,7 @@
# Yambar
[![Packaging status](https://repology.org/badge/vertical-allrepos/yambar.svg)](https://repology.org/project/yambar/versions)
[![Packaging status](https://repology.org/badge/vertical-allrepos/yambar.svg?columns=4)](https://repology.org/project/yambar/versions)
## Index

View file

@ -28,7 +28,7 @@
#define max(x, y) ((x) > (y) ? (x) : (y))
/*
* Calculate total width of left/center/rigth groups.
* Calculate total width of left/center/right groups.
* Note: begin_expose() must have been called
*/
static void

View file

@ -699,6 +699,7 @@ handle_global_remove(void *data, struct wl_registry *registry, uint32_t name)
tll_foreach(backend->monitors, it)
{
struct monitor *mon = &it->item;
/*
if (mon->wl_name == name) {
LOG_INFO("%s disconnected/disabled", mon->name);
@ -711,6 +712,7 @@ handle_global_remove(void *data, struct wl_registry *registry, uint32_t name)
tll_remove(backend->monitors, it);
return;
}
*/
}
LOG_WARN("unknown global removed: 0x%08x", name);

View file

@ -101,7 +101,7 @@ setup(struct bar *_bar)
backend->x = mon->x;
backend->y = mon->y;
bar->width = mon->width;
backend->y += bar->location == BAR_TOP ? 0 : screen->height_in_pixels - bar->height_with_border;
backend->y += bar->location == BAR_TOP ? 0 : mon->height - bar->height_with_border;
found_monitor = true;
@ -369,7 +369,7 @@ refresh(const struct bar *_bar)
/* Send an event to handle refresh from main thread */
/* Note: docs say that all X11 events are 32 bytes, reglardless of
/* Note: docs say that all X11 events are 32 bytes, regardless of
* the size of the event structure */
xcb_expose_event_t *evt = calloc(32, 1);

View file

@ -44,6 +44,12 @@ endif
if plugin_network_enabled
plugin_pages += ['yambar-modules-network.5.scd']
endif
if plugin_niri_language_enabled
plugin_pages += ['yambar-modules-niri-language.5.scd']
endif
if plugin_niri_workspaces_enabled
plugin_pages += ['yambar-modules-niri-workspaces.5.scd']
endif
if plugin_pipewire_enabled
plugin_pages += ['yambar-modules-pipewire.5.scd']
endif

View file

@ -137,7 +137,7 @@ content:
# STACK
This particles combines multiple decorations.
This particle combines multiple decorations.
## CONFIGURATION

View file

@ -17,8 +17,8 @@ currently present in the machine.
for the machine
| is_disk
: boolean
: whether or not the device is a disk (e.g. sda, sdb) or a partition
(e.g. sda1, sda2, ...). "Total" is advertised as a disk.
: whether or not the device is a disk (e.g., sda, sdb) or a partition
(e.g., sda1, sda2, ...). "Total" is advertised as a disk.
| read_speed
: int
: bytes read, in bytes/s

View file

@ -26,6 +26,9 @@ with the _application_ and _title_ tags to replace the X11-only
| name
: string
: The workspace name
| output
: string
: The output (monitor) the workspace is on
| visible
: bool
: True if the workspace is currently visible (on any output)

View file

@ -20,6 +20,9 @@ mpd - This module provides MPD status such as currently playing artist/album/son
| consume
: bool
: True if the *consume* flag is set
| single
: bool
: True if the *single* flag is set
| volume
: range
: Volume of MPD in percentage

View file

@ -21,6 +21,16 @@ address per network interface.
| name
: string
: Network interface name
| type
: string
: Interface type (*ether*, *wlan*, *loopback*, or *ARPHRD_NNN*, where
*N* is a number).
| kind
: string
: Interface kind. Empty for non-virtual interfaces. For virtual
interfaces, this value is taken from the _IFLA\_INFO\_KIND_ netlink
attribute. Examples of valid values are *bond*, *bridge*, *gre*, *tun*
and *veth*.
| index
: int
: Network interface index
@ -91,17 +101,23 @@ address per network interface.
# EXAMPLES
Display all Ethernet (including WLAN) devices. This excludes loopback,
bridges etc.
```
bar:
left:
- network:
content:
map:
default:
string: {text: "{name}: {state} ({ipv4})"}
conditions:
ipv4 == "":
string: {text: "{name}: {state}"}
type == ether || type == wlan:
map:
default:
string: {text: "{name}: {state} ({ipv4})"}
conditions:
ipv4 == "":
string: {text: "{name}: {state}"}
```
# SEE ALSO

View file

@ -0,0 +1,34 @@
yambar-modules-niri-language(5)
# NAME
niri-language - This module provides information about niri's currently
selected language.
# TAGS
[[ *Name*
:[ *Type*
:< *Description*
| language
: string
: The currently selected language.
# CONFIGURATION
No additional attributes supported, only the generic ones (see
*GENERIC CONFIGURATION* in *yambar-modules*(5))
# EXAMPLES
```
bar:
left:
- niri-language:
content:
string: {text: "{language}"}
```
# SEE ALSO
*yambar-modules*(5), *yambar-particles*(5), *yambar-tags*(5), *yambar-decorations*(5)

View file

@ -0,0 +1,60 @@
yambar-modules-niri-workspaces(5)
# NAME
niri-workspaces - This module provides information about niri workspaces.
# DESCRIPTION
This module provides a map of each workspace present in niri.
Each workspace has its _id_, _name_, and its status (_focused_,
_active_, _empty_). The workspaces are sorted by their ids.
This module will *only* track the monitor where yambar was launched.
If you have a multi monitor setup, please launch yambar on each
individual monitor to track its workspaces.
# TAGS
[[ *Name*
:[ *Type*
:< *Description*
| id
: int
: The workspace id.
| name
: string
: The name of the workspace.
| active
: bool
: True if the workspace is currently visible on the current output.
| focused
: bool
: True if the workspace is currently focused.
| empty
: bool
: True if the workspace contains no window.
# CONFIGURATION
No additional attributes supported, only the generic ones (see
*GENERIC CONFIGURATION* in *yambar-modules*(5))
# EXAMPLES
```
bar:
left:
- niri-workspaces:
content:
map:
default: {string: {text: "| {id}"}}
conditions:
active: {string: {text: "-> {id}"}}
~empty: {string: {text: "@ {id}"}}
```
# SEE ALSO
*yambar-modules*(5), *yambar-particles*(5), *yambar-tags*(5), *yambar-decorations*(5)

View file

@ -19,10 +19,10 @@ pipewire - Monitors pipewire for volume, mute/unmute, device change
: Current device description
| form_factor
: string
: Current device form factor (headset, speaker, mic, etc)
: Current device form factor (headset, speaker, mic, etc.)
| bus
: string
: Current device bus (bluetooth, alsa, etc)
: Current device bus (bluetooth, alsa, etc.)
| icon
: string
: Current device icon name

View file

@ -16,7 +16,7 @@ configurable amount of time.
In continuous mode, the script is executed once. It will typically run
in a loop, sending an updated tag set whenever it needs, or wants
to. The last tag set is used (displayed) by yambar until a new tag set
is received. This mode is intended to be used by scripts that depends
is received. This mode is intended to be used by scripts that depend
on non-polling methods to update their state.
Tag sets, or _transactions_, are separated by an empty line

View file

@ -174,6 +174,10 @@ Available modules have their own pages:
*yambar-modules-sway*(5)
*yambar-modules-niri-language*(5)
*yambar-modules-niri-workspaces*(5)
*yambar-modules-xkb*(5)
*yambar-modules-xwindow*(5)

View file

@ -155,7 +155,7 @@ content:
This particle is a list (or sequence, if you like) of other
particles. It can be used to render e.g. _string_ particles with
different font and/or color formatting. Or ay other particle
different font and/or color formatting. Or any other particle
combinations.
But note that this means you *cannot* set any attributes on the _list_
@ -214,6 +214,11 @@ content:
- string: ...
```
Note that the short form has a hard-coded *right-spacing* of 2. This
cannot be changed. If you want a different spacing, you must use an
explicit list particle (i.e. the long form).
# MAP
This particle maps the values of a specific tag to different
@ -260,6 +265,26 @@ To match for empty strings, use ' "" ':
<tag> == ""
```
String glob matching
To perform string matching using globbing with "\*" & "?" characters:
\* Match any zero or more characters. ? Match exactly any one
character.
```
<tag> ~~ "hello*"
```
Will match any string starting with "hello", including "hello",
"hello1", "hello123", etc.
```
<tag> ~~ "hello?"
```
Will match any string starting with "hello" followed by any single
character, including "hello1", "hello-", but not "hello".
Furthermore, you may use the boolean operators:
[- &&
@ -451,7 +476,7 @@ itself when needed.
```
content:
progres-bar:
progress-bar:
tag: tag_name
length: 20
start: {string: {text: ├}}

View file

@ -86,11 +86,15 @@ be used.
: format
: Range tags
: Renders a range tag's value as a percentage value
| /N
: format
: All tag types
: Renders a tag's value (in decimal) divided by N
| kb, mb, gb
: format
: All tag types
: Renders a tag's value (in decimal) divided by 1000, 1000^2 or
1000^3. Note: no unit suffix is appended)
1000^3. Note: no unit suffix is appended
| kib, mib, gib
: format
: All tag types

View file

@ -25,7 +25,7 @@ yambar - modular status panel for X11 and Wayland
*-p*,*--print-pid*=_FILE_|_FD_
Print PID to this file, or FD, when successfully started. The file
(or FD) is closed immediately after writing the PID. When a _FILE_
as been specified, the file is unlinked exit.
as been specified, the file is unlinked upon exiting.
*-d*,*--log-level*={*info*,*warning*,*error*,*none*}
Log level, used both for log output on stderr as well as

View file

@ -23,6 +23,11 @@ types that are frequently used:
- 000000ff: black, no transparency
- 00ff00ff: green, no transparency
- ff000099: red, semi-transparent
- *environment reference*: a string that contains format ${VAR}. This will be
replaced by the value of the environment variable VAR. Example:
- ${HOME}
- ${HOME}/.config/yambar
- ENV is ${ENV}, ENV2 is ${ENV2}
# FORMAT
[[ *Name*

View file

@ -0,0 +1,69 @@
bg_default: &bg_default {stack: [{background: {color: 81A1C1ff}}, {underline: {size: 4, color: D8DEE9ff}}]}
bar:
height: 32
location: top
background: 000000ff
font: NotoSans:pixelsize=16
right:
- clock:
content:
- string: {text: , font: "Font Awesome 6 Free:style=solid:size=12"}
- string: {text: "{date}", right-margin: 5}
- string: {text: , font: "Font Awesome 6 Free:style=solid:size=12"}
- string: {text: "{time} "}
left:
- river:
anchors:
- base: &river_base
left-margin: 10
right-margin: 13
default: {string: {text: }}
conditions:
id == 1: {string: {text: 1}}
id == 2: {string: {text: 2}}
id == 3: {string: {text: 3}}
id == 4: {string: {text: 4}}
id == 5: {string: {text: 5}}
content:
map:
on-click:
left: sh -c "riverctl set-focused-tags $((1 << ({id} - 1)))"
right: sh -c "riverctl toggle-focused-tags $((1 << ({id} -1)))"
middle: sh -c "riverctl toggle-view-tags $((1 << ({id} -1)))"
conditions:
state == urgent:
map:
<<: *river_base
deco: {background: {color: D08770ff}}
state == focused:
map:
<<: *river_base
deco: *bg_default
state == visible && ~occupied:
map:
<<: *river_base
state == visible && occupied:
map:
<<: *river_base
deco: *bg_default
state == unfocused:
map:
<<: *river_base
state == invisible && ~occupied: {empty: {}}
state == invisible && occupied:
map:
<<: *river_base
deco: {underline: {size: 3, color: ea6962ff}}
center:
- foreign-toplevel:
content:
map:
conditions:
~activated: {empty: {}}
activated:
- string: {text: " {app-id}", foreground: ffa0a0ff}
- string: {text: ": {title}"}

View file

@ -19,7 +19,7 @@
#
# Now the fun part
#
# Exemple configuration:
# Example configuration:
#
# - script:
# path: /absolute/path/to/dwl-tags.sh

View file

@ -12,7 +12,7 @@
# {aur} int number of aur packages
# {pkg} int sum of both
#
# Exemples configuration:
# Examples configuration:
# - script:
# path: /absolute/path/to/pacman.sh
# args: []

12
log.c
View file

@ -39,9 +39,15 @@ log_init(enum log_colorize _colorize, bool _do_syslog, enum log_facility syslog_
[LOG_FACILITY_DAEMON] = LOG_DAEMON,
};
colorize = _colorize == LOG_COLORIZE_NEVER ? false
: _colorize == LOG_COLORIZE_ALWAYS ? true
: isatty(STDERR_FILENO);
/* Don't use colors if NO_COLOR is defined and not empty */
const char *no_color_str = getenv("NO_COLOR");
const bool no_color = no_color_str != NULL && no_color_str[0] != '\0';
colorize = _colorize == LOG_COLORIZE_NEVER
? false
: _colorize == LOG_COLORIZE_ALWAYS
? true
: !no_color && isatty(STDERR_FILENO);
do_syslog = _do_syslog;
log_level = _log_level;

2
main.c
View file

@ -87,7 +87,7 @@ get_config_path(void)
static struct bar *
load_bar(const char *config_path, enum bar_backend backend)
{
FILE *conf_file = fopen(config_path, "r");
FILE *conf_file = fopen(config_path, "re");
if (conf_file == NULL) {
LOG_ERRNO("%s: failed to open", config_path);
return NULL;

View file

@ -12,7 +12,9 @@ plugs_as_libs = get_option('core-plugins-as-shared-libraries')
cc = meson.get_compiler('c')
if cc.has_function('memfd_create')
if cc.has_function('memfd_create',
args: ['-D_GNU_SOURCE=200809L'],
prefix: '#include <sys/mman.h>')
add_project_arguments('-DMEMFD_CREATE', language: 'c')
endif
@ -187,6 +189,8 @@ summary(
'River': plugin_river_enabled,
'Script': plugin_script_enabled,
'Sway XKB keyboard': plugin_sway_xkb_enabled,
'Niri language': plugin_niri_language_enabled,
'Niri workspaces': plugin_niri_workspaces_enabled,
'XKB keyboard (for X11)': plugin_xkb_enabled,
'XWindow (window tracking for X11)': plugin_xwindow_enabled,
},

View file

@ -44,6 +44,10 @@ option('plugin-script', type: 'feature', value: 'auto',
description: 'Script support')
option('plugin-sway-xkb', type: 'feature', value: 'auto',
description: 'keyboard support for Sway')
option('plugin-niri-language', type: 'feature', value: 'auto',
description: 'language support for Niri')
option('plugin-niri-workspaces', type: 'feature', value: 'auto',
description: 'workspaces support for Niri')
option('plugin-xkb', type: 'feature', value: 'auto',
description: 'keyboard support for X11')
option('plugin-xwindow', type: 'feature', value: 'auto',

View file

@ -112,13 +112,13 @@ readint_from_fd(int fd)
static int
initialize(struct private *m)
{
int backlight_fd = open("/sys/class/backlight", O_RDONLY);
int backlight_fd = open("/sys/class/backlight", O_RDONLY | O_CLOEXEC);
if (backlight_fd == -1) {
LOG_ERRNO("/sys/class/backlight");
return -1;
}
int base_dir_fd = openat(backlight_fd, m->device, O_RDONLY);
int base_dir_fd = openat(backlight_fd, m->device, O_RDONLY | O_CLOEXEC);
close(backlight_fd);
if (base_dir_fd == -1) {
@ -126,7 +126,7 @@ initialize(struct private *m)
return -1;
}
int max_fd = openat(base_dir_fd, "max_brightness", O_RDONLY);
int max_fd = openat(base_dir_fd, "max_brightness", O_RDONLY | O_CLOEXEC);
if (max_fd == -1) {
LOG_ERRNO("/sys/class/backlight/%s/max_brightness", m->device);
close(base_dir_fd);
@ -136,7 +136,7 @@ initialize(struct private *m)
m->max_brightness = readint_from_fd(max_fd);
close(max_fd);
int current_fd = openat(base_dir_fd, "brightness", O_RDONLY);
int current_fd = openat(base_dir_fd, "brightness", O_RDONLY | O_CLOEXEC);
close(base_dir_fd);
if (current_fd == -1) {

View file

@ -259,13 +259,13 @@ initialize(struct private *m)
{
char line_buf[512];
int pw_fd = open("/sys/class/power_supply", O_RDONLY);
int pw_fd = open("/sys/class/power_supply", O_RDONLY | O_CLOEXEC);
if (pw_fd < 0) {
LOG_ERRNO("/sys/class/power_supply");
return false;
}
int base_dir_fd = openat(pw_fd, m->battery, O_RDONLY);
int base_dir_fd = openat(pw_fd, m->battery, O_RDONLY | O_CLOEXEC);
close(pw_fd);
if (base_dir_fd < 0) {
@ -274,7 +274,7 @@ initialize(struct private *m)
}
{
int fd = openat(base_dir_fd, "manufacturer", O_RDONLY);
int fd = openat(base_dir_fd, "manufacturer", O_RDONLY | O_CLOEXEC);
if (fd == -1) {
LOG_WARN("/sys/class/power_supply/%s/manufacturer: %s", m->battery, strerror(errno));
m->manufacturer = NULL;
@ -285,7 +285,7 @@ initialize(struct private *m)
}
{
int fd = openat(base_dir_fd, "model_name", O_RDONLY);
int fd = openat(base_dir_fd, "model_name", O_RDONLY | O_CLOEXEC);
if (fd == -1) {
LOG_WARN("/sys/class/power_supply/%s/model_name: %s", m->battery, strerror(errno));
m->model = NULL;
@ -298,7 +298,7 @@ initialize(struct private *m)
if (faccessat(base_dir_fd, "energy_full_design", O_RDONLY, 0) == 0
&& faccessat(base_dir_fd, "energy_full", O_RDONLY, 0) == 0) {
{
int fd = openat(base_dir_fd, "energy_full_design", O_RDONLY);
int fd = openat(base_dir_fd, "energy_full_design", O_RDONLY | O_CLOEXEC);
if (fd == -1) {
LOG_ERRNO("/sys/class/power_supply/%s/energy_full_design", m->battery);
goto err;
@ -309,7 +309,7 @@ initialize(struct private *m)
}
{
int fd = openat(base_dir_fd, "energy_full", O_RDONLY);
int fd = openat(base_dir_fd, "energy_full", O_RDONLY | O_CLOEXEC);
if (fd == -1) {
LOG_ERRNO("/sys/class/power_supply/%s/energy_full", m->battery);
goto err;
@ -325,7 +325,7 @@ initialize(struct private *m)
if (faccessat(base_dir_fd, "charge_full_design", O_RDONLY, 0) == 0
&& faccessat(base_dir_fd, "charge_full", O_RDONLY, 0) == 0) {
{
int fd = openat(base_dir_fd, "charge_full_design", O_RDONLY);
int fd = openat(base_dir_fd, "charge_full_design", O_RDONLY | O_CLOEXEC);
if (fd == -1) {
LOG_ERRNO("/sys/class/power_supply/%s/charge_full_design", m->battery);
goto err;
@ -336,7 +336,7 @@ initialize(struct private *m)
}
{
int fd = openat(base_dir_fd, "charge_full", O_RDONLY);
int fd = openat(base_dir_fd, "charge_full", O_RDONLY | O_CLOEXEC);
if (fd == -1) {
LOG_ERRNO("/sys/class/power_supply/%s/charge_full", m->battery);
goto err;
@ -362,13 +362,13 @@ update_status(struct module *mod)
{
struct private *m = mod->private;
int pw_fd = open("/sys/class/power_supply", O_RDONLY);
int pw_fd = open("/sys/class/power_supply", O_RDONLY | O_CLOEXEC);
if (pw_fd < 0) {
LOG_ERRNO("/sys/class/power_supply");
return false;
}
int base_dir_fd = openat(pw_fd, m->battery, O_RDONLY);
int base_dir_fd = openat(pw_fd, m->battery, O_RDONLY | O_CLOEXEC);
close(pw_fd);
if (base_dir_fd < 0) {
@ -376,14 +376,14 @@ update_status(struct module *mod)
return false;
}
int status_fd = openat(base_dir_fd, "status", O_RDONLY);
int status_fd = openat(base_dir_fd, "status", O_RDONLY | O_CLOEXEC);
if (status_fd < 0) {
LOG_ERRNO("/sys/class/power_supply/%s/status", m->battery);
close(base_dir_fd);
return false;
}
int capacity_fd = openat(base_dir_fd, "capacity", O_RDONLY);
int capacity_fd = openat(base_dir_fd, "capacity", O_RDONLY | O_CLOEXEC);
if (capacity_fd < 0) {
LOG_ERRNO("/sys/class/power_supply/%s/capacity", m->battery);
close(status_fd);
@ -391,12 +391,12 @@ update_status(struct module *mod)
return false;
}
int energy_fd = openat(base_dir_fd, "energy_now", O_RDONLY);
int power_fd = openat(base_dir_fd, "power_now", O_RDONLY);
int charge_fd = openat(base_dir_fd, "charge_now", O_RDONLY);
int current_fd = openat(base_dir_fd, "current_now", O_RDONLY);
int time_to_empty_fd = openat(base_dir_fd, "time_to_empty_now", O_RDONLY);
int time_to_full_fd = openat(base_dir_fd, "time_to_full_now", O_RDONLY);
int energy_fd = openat(base_dir_fd, "energy_now", O_RDONLY | O_CLOEXEC);
int power_fd = openat(base_dir_fd, "power_now", O_RDONLY | O_CLOEXEC);
int charge_fd = openat(base_dir_fd, "charge_now", O_RDONLY | O_CLOEXEC);
int current_fd = openat(base_dir_fd, "current_now", O_RDONLY | O_CLOEXEC);
int time_to_empty_fd = openat(base_dir_fd, "time_to_empty_now", O_RDONLY | O_CLOEXEC);
int time_to_full_fd = openat(base_dir_fd, "time_to_full_now", O_RDONLY | O_CLOEXEC);
long capacity = readint_from_fd(capacity_fd);
long energy = energy_fd >= 0 ? readint_from_fd(energy_fd) : -1;

View file

@ -124,7 +124,7 @@ refresh_cpu_stats(struct cpu_stats *cpu_stats, size_t core_count)
size_t len = 0;
ssize_t read;
fp = fopen("/proc/stat", "r");
fp = fopen("/proc/stat", "re");
if (NULL == fp) {
LOG_ERRNO("unable to open /proc/stat");
return;

View file

@ -105,7 +105,7 @@ refresh_device_stats(struct private *m)
size_t len = 0;
ssize_t read;
fp = fopen("/proc/diskstats", "r");
fp = fopen("/proc/diskstats", "re");
if (NULL == fp) {
LOG_ERRNO("unable to open /proc/diskstats");
return;
@ -129,7 +129,7 @@ refresh_device_stats(struct private *m)
while ((read = getline(&line, &len, fp)) != -1) {
/*
* For an explanation of the fields bellow, see
* For an explanation of the fields below, see
* https://www.kernel.org/doc/Documentation/ABI/testing/procfs-diskstats
*/
uint8_t major_number = 0;

View file

@ -231,7 +231,7 @@ process_line(char *line, struct module *module)
/* No need to check error IMHO */
*target = strtoul(string, NULL, 10);
/* Populate informations */
/* Populate information */
if (index == 6) {
for (size_t id = 1; id <= private->number_of_tags; ++id) {
uint32_t mask = 1 << (id - 1);
@ -330,7 +330,7 @@ run_init(int *inotify_fd, int *inotify_wd, FILE **file, char *dwl_info_filename)
return 1;
}
*file = fopen(dwl_info_filename, "r");
*file = fopen(dwl_info_filename, "re");
if (*file == NULL) {
inotify_rm_watch(*inotify_fd, *inotify_wd);
close(*inotify_fd);

View file

@ -514,7 +514,6 @@ handle_workspace_event(int sock, int type, const struct json_object *json, void
else if (is_move) {
struct workspace *w = workspace_lookup(m, current_id);
assert(w != NULL);
struct json_object *_current_output;
if (!json_object_object_get_ex(current, "output", &_current_output)) {
@ -522,16 +521,22 @@ handle_workspace_event(int sock, int type, const struct json_object *json, void
mtx_unlock(&mod->lock);
return false;
}
const char *current_output_string = json_object_get_string(_current_output);
free(w->output);
w->output = strdup(json_object_get_string(_current_output));
/* Ignore fallback_output ("For when there's no connected outputs") */
if (strcmp(current_output_string, "FALLBACK") != 0) {
/*
* If the moved workspace was focused, schedule a full update because
* visibility for other workspaces may have changed.
*/
if (w->focused) {
i3_send_pkg(sock, I3_IPC_MESSAGE_TYPE_GET_WORKSPACES, NULL);
assert(w != NULL);
free(w->output);
w->output = strdup(current_output_string);
/*
* If the moved workspace was focused, schedule a full update because
* visibility for other workspaces may have changed.
*/
if (w->focused) {
i3_send_pkg(sock, I3_IPC_MESSAGE_TYPE_GET_WORKSPACES, NULL);
}
}
}
@ -659,7 +664,7 @@ handle_window_event(int sock, int type, const struct json_object *json, void *_m
char path[64];
snprintf(path, sizeof(path), "/proc/%u/comm", ws->window.pid);
int fd = open(path, O_RDONLY);
int fd = open(path, O_RDONLY | O_CLOEXEC);
if (fd == -1) {
/* Application may simply have terminated */
free(ws->window.application);
@ -871,6 +876,7 @@ content(struct module *mod)
struct tag_set tags = {
.tags = (struct tag *[]){
tag_new_string(mod, "name", name),
tag_new_string(mod, "output", ws->output),
tag_new_bool(mod, "visible", ws->visible),
tag_new_bool(mod, "focused", ws->focused),
tag_new_bool(mod, "urgent", ws->urgent),
@ -882,7 +888,7 @@ content(struct module *mod)
tag_new_string(mod, "mode", m->mode),
},
.count = 9,
.count = 10,
};
if (ws->focused) {

View file

@ -54,7 +54,7 @@ get_mem_stats(uint64_t *mem_free, uint64_t *mem_total)
size_t len = 0;
ssize_t read = 0;
fp = fopen("/proc/meminfo", "r");
fp = fopen("/proc/meminfo", "re");
if (NULL == fp) {
LOG_ERRNO("unable to open /proc/meminfo");
return false;

View file

@ -45,6 +45,12 @@ plugin_script_enabled = get_option('plugin-script').allowed()
json_sway_xkb = dependency('json-c', required: get_option('plugin-sway-xkb'))
plugin_sway_xkb_enabled = json_sway_xkb.found()
json_niri_language = dependency('json-c', required: get_option('plugin-niri-language'))
plugin_niri_language_enabled = json_niri_language.found()
json_niri_workspaces = dependency('json-c', required: get_option('plugin-niri-workspaces'))
plugin_niri_workspaces_enabled = json_niri_workspaces.found()
xcb_xkb = dependency('xcb-xkb', required: get_option('plugin-xkb'))
plugin_xkb_enabled = backend_x11 and xcb_xkb.found()
@ -121,6 +127,14 @@ if plugin_sway_xkb_enabled
mod_data += {'sway-xkb': [['i3-common.c', 'i3-common.h'], [dynlist, json_sway_xkb]]}
endif
if plugin_niri_language_enabled
mod_data += {'niri-language': [['niri-common.c', 'niri-common.h'], [dynlist, json_niri_language]]}
endif
if plugin_niri_workspaces_enabled
mod_data += {'niri-workspaces': [['niri-common.c', 'niri-common.h'], [dynlist, json_niri_workspaces]]}
endif
if plugin_xkb_enabled
mod_data += {'xkb': [[], [xcb_stuff, xcb_xkb]]}
endif

View file

@ -39,6 +39,7 @@ struct private
bool repeat;
bool random;
bool consume;
bool single;
int volume;
char *album;
char *artist;
@ -176,6 +177,7 @@ content(struct module *mod)
tag_new_bool(mod, "repeat", m->repeat),
tag_new_bool(mod, "random", m->random),
tag_new_bool(mod, "consume", m->consume),
tag_new_bool(mod, "single", m->single),
tag_new_int_range(mod, "volume", m->volume, 0, 100),
tag_new_string(mod, "album", m->album),
tag_new_string(mod, "artist", m->artist),
@ -187,7 +189,7 @@ content(struct module *mod)
tag_new_int_realtime(
mod, "elapsed", elapsed, 0, m->duration, realtime),
},
.count = 13,
.count = 14,
};
mtx_unlock(&mod->lock);
@ -336,6 +338,7 @@ update_status(struct module *mod)
m->repeat = mpd_status_get_repeat(status);
m->random = mpd_status_get_random(status);
m->consume = mpd_status_get_consume(status);
m->single = mpd_status_get_single_state(status) == MPD_SINGLE_ONESHOT;
m->volume = mpd_status_get_volume(status);
m->duration = mpd_status_get_total_time(status) * 1000;
m->elapsed.value = mpd_status_get_elapsed_ms(status);
@ -437,7 +440,7 @@ run(struct module *mod)
*/
while (!aborted) {
struct pollfd fds[] = {{.fd = mod->abort_fd, .events = POLLIN}};
int res = poll(fds, sizeof(fds) / sizeof(fds[0]), 10 * 1000);
int res = poll(fds, sizeof(fds) / sizeof(fds[0]), 2 * 1000);
if (res < 0) {
if (errno == EINTR)
@ -448,10 +451,16 @@ run(struct module *mod)
break;
}
if (res == 1) {
if (res == 0) {
ret = 0;
break;
}
else if (res == 1) {
assert(fds[0].revents & POLLIN);
aborted = true;
}
}
}

View file

@ -17,6 +17,7 @@
#include <arpa/inet.h>
#include <linux/genetlink.h>
#include <linux/if.h>
#include <linux/if_arp.h>
#include <linux/netlink.h>
#include <linux/nl80211.h>
#include <linux/rtnetlink.h>
@ -24,7 +25,7 @@
#include <tllist.h>
#define LOG_MODULE "network"
#define LOG_ENABLE_DBG 0
#define LOG_ENABLE_DBG 1
#include "../bar/bar.h"
#include "../config-verify.h"
#include "../config.h"
@ -52,6 +53,8 @@ struct af_addr {
struct iface {
char *name;
char *type; /* ARPHRD_NNN */
char *kind; /* IFLA_LINKINFO::IFLA_INFO_KIND */
uint32_t get_stats_seq_nr;
@ -104,6 +107,8 @@ free_iface(struct iface iface)
{
tll_free(iface.addrs);
free(iface.ssid);
free(iface.kind);
free(iface.type);
free(iface.name);
}
@ -119,7 +124,10 @@ destroy(struct module *mod)
if (m->urandom_fd >= 0)
close(m->urandom_fd);
tll_foreach(m->ifaces, it) free_iface(it->item);
tll_foreach(m->ifaces, it) {
free_iface(it->item);
tll_remove(m->ifaces, it);
}
free(m);
module_default_destroy(mod);
@ -204,6 +212,8 @@ content(struct module *mod)
struct tag_set tags = {
.tags = (struct tag *[]){
tag_new_string(mod, "name", iface->name),
tag_new_string(mod, "type", iface->type),
tag_new_string(mod, "kind", iface->kind),
tag_new_int(mod, "index", iface->index),
tag_new_bool(mod, "carrier", iface->carrier),
tag_new_string(mod, "state", state),
@ -218,7 +228,7 @@ content(struct module *mod)
tag_new_float(mod, "dl-speed", iface->dl_speed),
tag_new_float(mod, "ul-speed", iface->ul_speed),
},
.count = 14,
.count = 16,
};
exposables[idx++] = m->label->instantiate(m->label, &tags);
tag_set_destroy(&tags);
@ -546,6 +556,79 @@ send_nl80211_get_scan(struct private *m)
return true;
}
static bool
foreach_nlattr(struct module *mod, struct iface *iface, const struct genlmsghdr *genl, size_t len,
bool (*cb)(struct module *mod, struct iface *iface, uint16_t type, bool nested, const void *payload,
size_t len, void *ctx),
void *ctx)
{
const uint8_t *raw = (const uint8_t *)genl + GENL_HDRLEN;
const uint8_t *end = (const uint8_t *)genl + len;
for (const struct nlattr *attr = (const struct nlattr *)raw; raw < end;
raw += NLA_ALIGN(attr->nla_len), attr = (const struct nlattr *)raw) {
uint16_t type = attr->nla_type & NLA_TYPE_MASK;
bool nested = (attr->nla_type & NLA_F_NESTED) != 0;
;
const void *payload = raw + NLA_HDRLEN;
if (!cb(mod, iface, type, nested, payload, attr->nla_len - NLA_HDRLEN, ctx))
return false;
}
return true;
}
static bool
foreach_nlattr_nested(struct module *mod, struct iface *iface, const void *parent_payload, size_t len,
bool (*cb)(struct module *mod, struct iface *iface, uint16_t type, bool nested,
const void *payload, size_t len, void *ctx),
void *ctx)
{
const uint8_t *raw = parent_payload;
const uint8_t *end = parent_payload + len;
for (const struct nlattr *attr = (const struct nlattr *)raw; raw < end;
raw += NLA_ALIGN(attr->nla_len), attr = (const struct nlattr *)raw) {
uint16_t type = attr->nla_type & NLA_TYPE_MASK;
bool nested = (attr->nla_type & NLA_F_NESTED) != 0;
const void *payload = raw + NLA_HDRLEN;
if (!cb(mod, iface, type, nested, payload, attr->nla_len - NLA_HDRLEN, ctx))
return false;
}
return true;
}
static bool
parse_linkinfo(struct module *mod, struct iface *iface, uint16_t type,
bool nested, const void *payload, size_t len, void *_void)
{
switch (type) {
case IFLA_INFO_KIND: {
const char *kind = payload;
free(iface->kind);
iface->kind = strndup(kind, len);
LOG_DBG("%s: IFLA_INFO_KIND: %s", iface->name, iface->kind);
break;
}
case IFLA_INFO_DATA:
//LOG_DBG("%s: IFLA_INFO_DATA", iface->name);
break;
default:
LOG_WARN("unrecognized IFLA_LINKINFO attribute: "
"type=%hu, nested=%d, len=%zu",
type, nested, len);
break;
}
return true;
}
static void
handle_link(struct module *mod, uint16_t type, const struct ifinfomsg *msg, size_t len)
{
@ -578,9 +661,31 @@ handle_link(struct module *mod, uint16_t type, const struct ifinfomsg *msg, size
}
if (iface == NULL) {
char *type = NULL;
switch (msg->ifi_type) {
case ARPHRD_ETHER:
type = strdup("ether");
break;
case ARPHRD_LOOPBACK:
type = strdup("loopback");
break;
case ARPHRD_IEEE80211:
type = strdup("wlan");
break;
default:
if (asprintf(&type, "ARPHRD_%hu", msg->ifi_type) < 0)
type = strdup("unknown");
break;
}
mtx_lock(&mod->lock);
tll_push_back(m->ifaces, ((struct iface){
.index = msg->ifi_index,
.type = type,
.state = IF_OPER_DOWN,
.addrs = tll_init(),
}));
@ -593,8 +698,10 @@ handle_link(struct module *mod, uint16_t type, const struct ifinfomsg *msg, size
case IFLA_IFNAME:
mtx_lock(&mod->lock);
iface->name = strdup((const char *)RTA_DATA(attr));
LOG_DBG("%s: index=%d", iface->name, iface->index);
LOG_DBG("%s: index=%d, type=%s", iface->name, iface->index, iface->type);
mtx_unlock(&mod->lock);
break;
case IFLA_OPERSTATE: {
uint8_t operstate = *(const uint8_t *)RTA_DATA(attr);
if (iface->state == operstate)
@ -629,7 +736,8 @@ handle_link(struct module *mod, uint16_t type, const struct ifinfomsg *msg, size
if (memcmp(iface->mac, mac, sizeof(iface->mac)) == 0)
break;
LOG_DBG("%s: IFLA_ADDRESS: %02x:%02x:%02x:%02x:%02x:%02x", iface->name, mac[0], mac[1], mac[2], mac[3],
LOG_DBG("%s: IFLA_ADDRESS: %02x:%02x:%02x:%02x:%02x:%02x",
iface->name, mac[0], mac[1], mac[2], mac[3],
mac[4], mac[5]);
mtx_lock(&mod->lock);
@ -637,6 +745,13 @@ handle_link(struct module *mod, uint16_t type, const struct ifinfomsg *msg, size
mtx_unlock(&mod->lock);
break;
}
case IFLA_LINKINFO: {
foreach_nlattr_nested(
mod, iface, RTA_DATA(attr), RTA_PAYLOAD(attr),
&parse_linkinfo, NULL);
break;
}
}
}
@ -669,7 +784,7 @@ handle_address(struct module *mod, uint16_t type, const struct ifaddrmsg *msg, s
}
if (iface == NULL) {
LOG_ERR("failed to find network interface with index %d. Probaly a yambar bug", msg->ifa_index);
LOG_ERR("failed to find network interface with index %d. Probably a yambar bug", msg->ifa_index);
return;
}
@ -719,51 +834,6 @@ handle_address(struct module *mod, uint16_t type, const struct ifaddrmsg *msg, s
mod->bar->refresh(mod->bar);
}
static bool
foreach_nlattr(struct module *mod, struct iface *iface, const struct genlmsghdr *genl, size_t len,
bool (*cb)(struct module *mod, struct iface *iface, uint16_t type, bool nested, const void *payload,
size_t len, void *ctx),
void *ctx)
{
const uint8_t *raw = (const uint8_t *)genl + GENL_HDRLEN;
const uint8_t *end = (const uint8_t *)genl + len;
for (const struct nlattr *attr = (const struct nlattr *)raw; raw < end;
raw += NLA_ALIGN(attr->nla_len), attr = (const struct nlattr *)raw) {
uint16_t type = attr->nla_type & NLA_TYPE_MASK;
bool nested = (attr->nla_type & NLA_F_NESTED) != 0;
;
const void *payload = raw + NLA_HDRLEN;
if (!cb(mod, iface, type, nested, payload, attr->nla_len - NLA_HDRLEN, ctx))
return false;
}
return true;
}
static bool
foreach_nlattr_nested(struct module *mod, struct iface *iface, const void *parent_payload, size_t len,
bool (*cb)(struct module *mod, struct iface *iface, uint16_t type, bool nested,
const void *payload, size_t len, void *ctx),
void *ctx)
{
const uint8_t *raw = parent_payload;
const uint8_t *end = parent_payload + len;
for (const struct nlattr *attr = (const struct nlattr *)raw; raw < end;
raw += NLA_ALIGN(attr->nla_len), attr = (const struct nlattr *)raw) {
uint16_t type = attr->nla_type & NLA_TYPE_MASK;
bool nested = (attr->nla_type & NLA_F_NESTED) != 0;
const void *payload = raw + NLA_HDRLEN;
if (!cb(mod, iface, type, nested, payload, attr->nla_len - NLA_HDRLEN, ctx))
return false;
}
return true;
}
struct mcast_group {
uint32_t id;
const char *name;
@ -1300,6 +1370,8 @@ parse_genl_reply(struct module *mod, const struct nlmsghdr *hdr, size_t len)
continue;
LOG_DBG("%s: got interface information", iface->name);
free(iface->type);
iface->type = strdup("wlan");
foreach_nlattr(mod, iface, genl, msg_size, &handle_nl80211_new_interface, NULL);
break;
@ -1504,7 +1576,7 @@ out:
static struct module *
network_new(struct particle *label, int poll_interval, int left_spacing, int right_spacing)
{
int urandom_fd = open("/dev/urandom", O_RDONLY);
int urandom_fd = open("/dev/urandom", O_RDONLY | O_CLOEXEC);
if (urandom_fd < 0) {
LOG_ERRNO("failed to open /dev/urandom");
return NULL;

377
modules/niri-common.c Normal file
View file

@ -0,0 +1,377 @@
#include <errno.h>
#include <json-c/json.h>
#include <poll.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/eventfd.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/un.h>
#include <threads.h>
#include <unistd.h>
#include "../log.h"
#include "niri-common.h"
#define LOG_MODULE "niri:common"
#define LOG_ENABLE_DBG 0
static struct niri_socket instance = {
.fd = -1,
.abort_fd = -1,
};
static void
workspace_free(struct niri_workspace *workspace)
{
free(workspace->name);
free(workspace);
}
static void
parser(char *response)
{
enum json_tokener_error error = json_tokener_success;
struct json_object *json = json_tokener_parse_verbose(response, &error);
if (error != json_tokener_success) {
LOG_WARN("failed to parse niri socket's response");
return;
}
enum niri_event events = 0;
struct json_object_iterator it = json_object_iter_begin(json);
struct json_object_iterator end = json_object_iter_end(json);
while (!json_object_iter_equal(&it, &end)) {
char const *key = json_object_iter_peek_name(&it);
// "WorkspacesChanged": {
// "workspaces": [
// {
// "id": 3,
// "idx": 1,
// "name": null,
// "output": "DP-4",
// "is_active": true,
// "is_focused": true,
// "active_window_id": 24
// },
// ...
// ]
// }
if (strcmp(key, "WorkspacesChanged") == 0) {
mtx_lock(&instance.mtx);
tll_foreach(instance.workspaces, it) { tll_remove_and_free(instance.workspaces, it, workspace_free); }
mtx_unlock(&instance.mtx);
json_object *obj = json_object_iter_peek_value(&it);
json_object *workspaces = json_object_object_get(obj, "workspaces");
size_t length = json_object_array_length(workspaces);
for (size_t i = 0; i < length; ++i) {
json_object *ws_obj = json_object_array_get_idx(workspaces, i);
// only add workspaces on the current yambar's monitor
struct json_object *output = json_object_object_get(ws_obj, "output");
if (strcmp(instance.monitor, json_object_get_string(output)) != 0)
continue;
struct niri_workspace *ws = calloc(1, sizeof(*ws));
ws->idx = json_object_get_int(json_object_object_get(ws_obj, "idx"));
ws->id = json_object_get_int(json_object_object_get(ws_obj, "id"));
ws->active = json_object_get_boolean(json_object_object_get(ws_obj, "is_active"));
ws->focused = json_object_get_boolean(json_object_object_get(ws_obj, "is_focused"));
ws->empty = json_object_get_int(json_object_object_get(ws_obj, "active_window_id")) == 0;
char const *name = json_object_get_string(json_object_object_get(ws_obj, "name"));
if (name)
ws->name = strdup(name);
mtx_lock(&instance.mtx);
bool inserted = false;
tll_foreach(instance.workspaces, it)
{
if (it->item->idx > ws->idx) {
tll_insert_before(instance.workspaces, it, ws);
inserted = true;
break;
}
}
if (!inserted)
tll_push_back(instance.workspaces, ws);
mtx_unlock(&instance.mtx);
events |= workspaces_changed;
}
}
// "WorkspaceActivated": {
// "id": 7,
// "focused":true
// }
else if (strcmp(key, "WorkspaceActivated") == 0) {
json_object *obj = json_object_iter_peek_value(&it);
int id = json_object_get_int(json_object_object_get(obj, "id"));
mtx_lock(&instance.mtx);
tll_foreach(instance.workspaces, it)
{
bool b = it->item->id == id;
it->item->focused = b;
it->item->active = b;
}
mtx_unlock(&instance.mtx);
events |= workspace_activated;
}
// "WorkspaceActiveWindowChanged": {
// "workspace_id": 3,
// "active_window_id": 8
// }
else if (strcmp(key, "WorkspaceActiveWindowChanged") == 0) {
json_object *obj = json_object_iter_peek_value(&it);
int id = json_object_get_int(json_object_object_get(obj, "id"));
bool empty = json_object_get_int(json_object_object_get(obj, "active_window_id")) == 0;
mtx_lock(&instance.mtx);
tll_foreach(instance.workspaces, it)
{
if (it->item->id == id) {
it->item->empty = empty;
break;
}
}
mtx_unlock(&instance.mtx);
events |= workspace_active_window_changed;
}
//
// "KeyboardLayoutsChanged": {
// "keyboard_layouts": {
// "names": [
// "English (US)",
// "Russian"
// ],
// "current_idx": 0
// }
// }
else if (strcmp(key, "KeyboardLayoutsChanged") == 0) {
tll_foreach(instance.keyboard_layouts, it) { tll_remove_and_free(instance.keyboard_layouts, it, free); }
json_object *obj = json_object_iter_peek_value(&it);
json_object *kb_layouts = json_object_object_get(obj, "keyboard_layouts");
instance.keyboard_layout_index = json_object_get_int(json_object_object_get(kb_layouts, "current_idx"));
json_object *names = json_object_object_get(kb_layouts, "names");
size_t names_length = json_object_array_length(names);
for (size_t i = 0; i < names_length; ++i) {
char const *name = json_object_get_string(json_object_array_get_idx(names, i));
tll_push_back(instance.keyboard_layouts, strdup(name));
}
events |= keyboard_layouts_changed;
}
// "KeyboardLayoutSwitched": {
// "idx": 1
// }
else if (strcmp(key, "KeyboardLayoutSwitched") == 0) {
json_object *obj = json_object_iter_peek_value(&it);
instance.keyboard_layout_index = json_object_get_int(json_object_object_get(obj, "idx"));
events |= keyboard_layouts_switched;
}
json_object_iter_next(&it);
}
json_object_put(json);
mtx_lock(&instance.mtx);
tll_foreach(instance.subscribers, it)
{
if (it->item->events & events)
if (write(it->item->fd, &(uint64_t){1}, sizeof(uint64_t)) == -1)
LOG_ERRNO("failed to write");
}
mtx_unlock(&instance.mtx);
}
static int
run(void *userdata)
{
static char msg[] = "\"EventStream\"\n";
static char expected[] = "{\"Ok\":\"Handled\"}";
if (write(instance.fd, msg, sizeof(msg) / sizeof(msg[0])) == -1) {
LOG_ERRNO("failed to sent message to niri socket");
return thrd_error;
}
static char buffer[8192];
if (read(instance.fd, buffer, sizeof(buffer) / sizeof(buffer[0]) - 1) == -1) {
LOG_ERRNO("failed to read response of niri socket");
return thrd_error;
}
char *saveptr;
char *response = strtok_r(buffer, "\n", &saveptr);
if (response == NULL || strcmp(expected, response) != 0) {
// unexpected first response, something went wrong
LOG_ERR("unexpected response of niri socket");
return thrd_error;
}
while ((response = strtok_r(NULL, "\n", &saveptr)) != NULL)
parser(response);
while (true) {
struct pollfd fds[] = {
(struct pollfd){.fd = instance.abort_fd, .events = POLLIN},
(struct pollfd){.fd = instance.fd, .events = POLLIN},
};
if (poll(fds, sizeof(fds) / sizeof(fds[0]), -1) == -1) {
if (errno == EINTR)
continue;
LOG_ERRNO("failed to poll");
break;
}
if (fds[0].revents & POLLIN)
break;
static char buffer[8192];
ssize_t length = read(fds[1].fd, buffer, sizeof(buffer) / sizeof(buffer[0]));
if (length == 0)
break;
if (length == -1) {
if (errno == EAGAIN || errno == EINTR)
continue;
LOG_ERRNO("unable to read niri socket");
break;
}
buffer[length] = '\0';
saveptr = NULL;
response = strtok_r(buffer, "\n", &saveptr);
do {
parser(response);
} while ((response = strtok_r(NULL, "\n", &saveptr)) != NULL);
}
return thrd_success;
}
struct niri_socket *
niri_socket_open(char const *monitor)
{
if (instance.fd >= 0)
return &instance;
char const *path = getenv("NIRI_SOCKET");
if (path == NULL) {
LOG_ERR("NIRI_SOCKET is empty. Is niri running?");
return NULL;
}
if ((instance.fd = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0)) == -1) {
LOG_ERRNO("failed to create socket");
goto error;
}
struct sockaddr_un addr;
addr.sun_family = AF_UNIX;
addr.sun_path[sizeof(addr.sun_path) - 1] = '\0';
strncpy(addr.sun_path, path, sizeof(addr.sun_path) - 1);
if (connect(instance.fd, (struct sockaddr *)&addr, sizeof(addr)) == -1) {
LOG_ERRNO("failed to connect to niri socket");
goto error;
}
if ((instance.abort_fd = eventfd(0, EFD_CLOEXEC)) == -1) {
LOG_ERRNO("failed to create abort_fd");
goto error;
}
if (mtx_init(&instance.mtx, mtx_plain) != thrd_success) {
LOG_ERR("failed to initialize mutex");
goto error;
}
if (thrd_create(&instance.thrd, run, NULL) != thrd_success) {
LOG_ERR("failed to create thread");
mtx_destroy(&instance.mtx);
goto error;
}
instance.monitor = monitor;
return &instance;
error:
if (instance.fd >= 0)
close(instance.fd);
if (instance.abort_fd >= 0)
close(instance.abort_fd);
instance.fd = -1;
instance.abort_fd = -1;
instance.monitor = NULL;
return NULL;
}
static void
socket_close(void)
{
if (write(instance.abort_fd, &(uint64_t){1}, sizeof(uint64_t)) != sizeof(uint64_t))
LOG_ERRNO("failed to write to abort_fd");
thrd_join(instance.thrd, NULL);
close(instance.abort_fd);
close(instance.fd);
instance.abort_fd = -1;
instance.fd = -1;
mtx_destroy(&instance.mtx);
tll_free_and_free(instance.subscribers, free);
tll_free_and_free(instance.workspaces, workspace_free);
tll_free_and_free(instance.keyboard_layouts, free);
}
void
niri_socket_close(void)
{
static once_flag flag = ONCE_FLAG_INIT;
call_once(&flag, socket_close);
}
int
niri_socket_subscribe(enum niri_event events)
{
int fd = eventfd(0, EFD_CLOEXEC);
if (fd == -1) {
LOG_ERRNO("failed to create eventfd");
return -1;
}
struct niri_subscriber *subscriber = calloc(1, sizeof(*subscriber));
subscriber->events = events;
subscriber->fd = fd;
mtx_lock(&instance.mtx);
tll_push_back(instance.subscribers, subscriber);
mtx_unlock(&instance.mtx);
return subscriber->fd;
}

45
modules/niri-common.h Normal file
View file

@ -0,0 +1,45 @@
#pragma once
#include <stdbool.h>
#include <threads.h>
#include <tllist.h>
enum niri_event {
workspaces_changed = (1 << 0),
workspace_activated = (1 << 1),
workspace_active_window_changed = (1 << 2),
keyboard_layouts_changed = (1 << 3),
keyboard_layouts_switched = (1 << 4),
};
struct niri_subscriber {
int events;
int fd;
};
struct niri_workspace {
int id;
int idx;
char *name;
bool active;
bool focused;
bool empty;
};
struct niri_socket {
char const *monitor;
int abort_fd;
int fd;
tll(struct niri_subscriber *) subscribers;
tll(struct niri_workspace *) workspaces;
tll(char *) keyboard_layouts;
size_t keyboard_layout_index;
thrd_t thrd;
mtx_t mtx;
};
struct niri_socket *niri_socket_open(char const *monitor);
void niri_socket_close(void);
int niri_socket_subscribe(enum niri_event events);

160
modules/niri-language.c Normal file
View file

@ -0,0 +1,160 @@
#include <errno.h>
#include <json-c/json.h>
#include <poll.h>
#include <string.h>
#include <unistd.h>
#define LOG_MODULE "niri-language"
#define LOG_ENABLE_DBG 0
#include "niri-common.h"
#include "../log.h"
#include "../particles/dynlist.h"
#include "../plugin.h"
struct private
{
struct particle *label;
struct niri_socket *niri;
};
static void
destroy(struct module *module)
{
struct private *private = module->private;
private->label->destroy(private->label);
free(private);
module_default_destroy(module);
}
static const char *
description(const struct module *module)
{
return "niri-lang";
}
static struct exposable *
content(struct module *module)
{
const struct private *private = module->private;
if (private->niri == NULL)
return dynlist_exposable_new(&((struct exposable *){0}), 0, 0, 0);
mtx_lock(&module->lock);
mtx_lock(&private->niri->mtx);
char *name = "???";
size_t i = 0;
tll_foreach(private->niri->keyboard_layouts, it)
{
if (i++ == private->niri->keyboard_layout_index)
name = it->item;
}
struct tag_set tags = {
.tags = (struct tag *[]){tag_new_string(module, "language", name)},
.count = 1,
};
struct exposable *exposable = private->label->instantiate(private->label, &tags);
tag_set_destroy(&tags);
mtx_unlock(&private->niri->mtx);
mtx_unlock(&module->lock);
return exposable;
}
static int
run(struct module *module)
{
struct private *private = module->private;
/* Ugly, but I didn't find better way for waiting
* the monitor's name to be set */
char const *monitor;
do {
monitor = module->bar->output_name(module->bar);
usleep(50);
} while (monitor == NULL);
private->niri = niri_socket_open(monitor);
if (private->niri == NULL)
return 1;
int fd = niri_socket_subscribe(keyboard_layouts_changed | keyboard_layouts_switched);
if (fd == -1) {
niri_socket_close();
return 1;
}
module->bar->refresh(module->bar);
while (true) {
struct pollfd fds[] = {
(struct pollfd){.fd = module->abort_fd, .events = POLLIN},
(struct pollfd){.fd = fd, .events = POLLIN},
};
if (poll(fds, sizeof(fds) / sizeof(fds[0]), -1) == -1) {
if (errno == EINTR)
continue;
LOG_ERRNO("failed to poll");
break;
}
if (fds[0].revents & POLLIN)
break;
if (read(fds[1].fd, &(uint64_t){0}, sizeof(uint64_t)) == -1)
LOG_ERRNO("failed to read from eventfd");
module->bar->refresh(module->bar);
}
niri_socket_close();
return 0;
}
static struct module *
niri_language_new(struct particle *label)
{
struct private *private = calloc(1, sizeof(struct private));
private->label = label;
struct module *module = module_common_new();
module->private = private;
module->run = &run;
module->destroy = &destroy;
module->content = &content;
module->description = &description;
return module;
}
static struct module *
from_conf(struct yml_node const *node, struct conf_inherit inherited)
{
struct yml_node const *content = yml_get_value(node, "content");
return niri_language_new(conf_to_particle(content, inherited));
}
static bool
verify_conf(keychain_t *chain, const struct yml_node *node)
{
static struct attr_info const attrs[] = {
MODULE_COMMON_ATTRS,
};
return conf_verify_dict(chain, node, attrs);
}
const struct module_iface module_niri_language_iface = {
.verify_conf = &verify_conf,
.from_conf = &from_conf,
};
#if defined(CORE_PLUGINS_AS_SHARED_LIBRARIES)
extern const struct module_iface iface __attribute__((weak, alias("module_niri_language_iface")));
#endif

163
modules/niri-workspaces.c Normal file
View file

@ -0,0 +1,163 @@
#include <errno.h>
#include <poll.h>
#include <string.h>
#include <unistd.h>
#define LOG_MODULE "niri-workspaces"
#define LOG_ENABLE_DBG 0
#include "niri-common.h"
#include "../log.h"
#include "../particles/dynlist.h"
#include "../plugin.h"
struct private
{
struct particle *label;
struct niri_socket *niri;
};
static void
destroy(struct module *module)
{
struct private *private = module->private;
private->label->destroy(private->label);
free(private);
module_default_destroy(module);
}
static const char *
description(const struct module *module)
{
return "niri-ws";
}
static struct exposable *
content(struct module *module)
{
struct private const *private = module->private;
if (private->niri == NULL)
return dynlist_exposable_new(&((struct exposable *){0}), 0, 0, 0);
mtx_lock(&module->lock);
mtx_lock(&private->niri->mtx);
size_t i = 0;
struct exposable *exposable[tll_length(private->niri->workspaces)];
tll_foreach(private->niri->workspaces, it)
{
struct tag_set tags = {
.tags = (struct tag*[]){
tag_new_int(module, "id", it->item->idx),
tag_new_string(module, "name", it->item->name),
tag_new_bool(module, "active", it->item->active),
tag_new_bool(module, "focused", it->item->focused),
tag_new_bool(module, "empty", it->item->empty),
},
.count = 5,
};
exposable[i++] = private->label->instantiate(private->label, &tags);
tag_set_destroy(&tags);
}
mtx_unlock(&private->niri->mtx);
mtx_unlock(&module->lock);
return dynlist_exposable_new(exposable, i, 0, 0);
}
static int
run(struct module *module)
{
struct private *private = module->private;
/* Ugly, but I didn't find better way for waiting
* the monitor's name to be set */
char const *monitor;
do {
monitor = module->bar->output_name(module->bar);
usleep(50);
} while (monitor == NULL);
private->niri = niri_socket_open(monitor);
if (private->niri == NULL)
return 1;
int fd = niri_socket_subscribe(workspaces_changed | workspace_activated | workspace_active_window_changed);
if (fd == -1) {
niri_socket_close();
return 1;
}
module->bar->refresh(module->bar);
while (true) {
struct pollfd fds[] = {
(struct pollfd){.fd = module->abort_fd, .events = POLLIN},
(struct pollfd){.fd = fd, .events = POLLIN},
};
if (poll(fds, sizeof(fds) / sizeof(fds[0]), -1) == -1) {
if (errno == EINTR)
continue;
LOG_ERRNO("failed to poll");
break;
}
if (fds[0].revents & POLLIN)
break;
if (read(fds[1].fd, &(uint64_t){0}, sizeof(uint64_t)) == -1)
LOG_ERRNO("failed to read from eventfd");
module->bar->refresh(module->bar);
}
niri_socket_close();
return 0;
}
static struct module *
niri_workspaces_new(struct particle *label)
{
struct private *private = calloc(1, sizeof(struct private));
private->label = label;
struct module *module = module_common_new();
module->private = private;
module->run = &run;
module->destroy = &destroy;
module->content = &content;
module->description = &description;
return module;
}
static struct module *
from_conf(struct yml_node const *node, struct conf_inherit inherited)
{
struct yml_node const *content = yml_get_value(node, "content");
return niri_workspaces_new(conf_to_particle(content, inherited));
}
static bool
verify_conf(keychain_t *chain, const struct yml_node *node)
{
static struct attr_info const attrs[] = {
MODULE_COMMON_ATTRS,
};
return conf_verify_dict(chain, node, attrs);
}
const struct module_iface module_niri_workspaces_iface = {
.verify_conf = &verify_conf,
.from_conf = &from_conf,
};
#if defined(CORE_PLUGINS_AS_SHARED_LIBRARIES)
extern const struct module_iface iface __attribute__((weak, alias("module_niri_workspaces_iface")));
#endif

View file

@ -33,7 +33,7 @@ struct output_informations {
uint32_t device_id;
uint32_t card_profile_device_id;
/* informations */
/* information */
bool muted;
uint16_t linear_volume; /* classic volume */
uint16_t cubic_volume; /* volume a la pulseaudio */
@ -45,6 +45,16 @@ struct output_informations {
};
static struct output_informations const output_informations_null;
static void
output_informations_destroy(struct output_informations *output_informations)
{
free(output_informations->name);
free(output_informations->description);
free(output_informations->icon);
free(output_informations->form_factor);
free(output_informations->bus);
}
struct data;
struct private
{
@ -213,18 +223,23 @@ node_find_route(struct data *data, bool is_sink)
static void
node_unhook_binded_node(struct data *data, bool is_sink)
{
struct private *private = data->module->private;
struct node **target_node = NULL;
struct spa_hook *target_listener = NULL;
void **target_proxy = NULL;
struct output_informations *output_informations = NULL;
if (is_sink) {
target_node = &data->binded_sink;
target_listener = &data->node_sink_listener;
target_proxy = &data->node_sink;
output_informations = &private->sink_informations;
} else {
target_node = &data->binded_source;
target_listener = &data->node_source_listener;
target_proxy = &data->node_source;
output_informations = &private->source_informations;
}
if (*target_node == NULL)
@ -235,6 +250,9 @@ node_unhook_binded_node(struct data *data, bool is_sink)
*target_node = NULL;
*target_proxy = NULL;
output_informations_destroy(output_informations);
*output_informations = output_informations_null;
}
static void
@ -333,7 +351,7 @@ device_events_param(void *userdata, int seq, uint32_t id, uint32_t index, uint32
X_FREE_SET(route->icon_name, X_STRDUP(data.icon_name));
route->direction = data.direction;
/* set missing informations if possible */
/* set missing information if possible */
struct private *private = device->data->module->private;
struct node *binded_node = NULL;
struct output_informations *output_informations = NULL;
@ -350,7 +368,7 @@ device_events_param(void *userdata, int seq, uint32_t id, uint32_t index, uint32
if (binded_node == NULL)
return;
/* Node's device is the the same as route's device */
/* Node's device is the same as route's device */
if (output_informations->device_id != route->device->id)
return;
@ -358,7 +376,7 @@ device_events_param(void *userdata, int seq, uint32_t id, uint32_t index, uint32
if (output_informations->card_profile_device_id != route->profile_device_id)
return;
/* Update missing informations */
/* Update missing information */
X_FREE_SET(output_informations->form_factor, X_STRDUP(route->form_factor));
X_FREE_SET(output_informations->icon, X_STRDUP(route->icon_name));
@ -384,7 +402,7 @@ node_events_info(void *userdata, struct pw_node_info const *info)
for (size_t i = 0; i < info->n_params; ++i) {
if (info->params[i].id == SPA_PARAM_Props) {
void *target_node = (node_data->is_sink ? data->node_sink : data->node_source);
/* Found it, will emit a param event, the parem will then be handled
/* Found it, will emit a param event, the param will then be handled
* in node_events_param */
pw_node_enum_params(target_node, 0, info->params[i].id, 0, -1, NULL);
break;
@ -398,18 +416,18 @@ node_events_info(void *userdata, struct pw_node_info const *info)
struct spa_dict_item const *item = NULL;
item = spa_dict_lookup_item(info->props, "node.name");
if (item != NULL)
X_FREE_SET(output_informations->name, X_STRDUP(item->value));
X_FREE_SET(output_informations->name, item != NULL ? X_STRDUP(item->value) : NULL);
item = spa_dict_lookup_item(info->props, "node.description");
if (item != NULL)
X_FREE_SET(output_informations->description, X_STRDUP(item->value));
X_FREE_SET(output_informations->description, item != NULL ? X_STRDUP(item->value) : NULL);
item = spa_dict_lookup_item(info->props, "device.id");
if (item != NULL) {
uint32_t value = 0;
spa_atou32(item->value, &value, 10);
output_informations->device_id = value;
} else {
output_informations->device_id = 0;
}
item = spa_dict_lookup_item(info->props, "card.profile.device");
@ -417,30 +435,29 @@ node_events_info(void *userdata, struct pw_node_info const *info)
uint32_t value = 0;
spa_atou32(item->value, &value, 10);
output_informations->card_profile_device_id = value;
} else {
output_informations->card_profile_device_id = 0;
}
/* Device's informations has an more important priority than node's informations */
/* Device's information has an more important priority than node's information */
/* icon_name */
struct route *route = node_find_route(data, node_data->is_sink);
if (route != NULL && route->icon_name != NULL)
output_informations->icon = X_STRDUP(route->icon_name);
X_FREE_SET(output_informations->icon, X_STRDUP(route->icon_name));
else {
item = spa_dict_lookup_item(info->props, "device.icon-name");
if (item != NULL)
X_FREE_SET(output_informations->icon, X_STRDUP(item->value));
X_FREE_SET(output_informations->icon, item != NULL ? X_STRDUP(item->value) : NULL);
}
/* form_factor */
if (route != NULL && route->form_factor != NULL)
output_informations->form_factor = X_STRDUP(route->form_factor);
X_FREE_SET(output_informations->form_factor, X_STRDUP(route->form_factor));
else {
item = spa_dict_lookup_item(info->props, "device.form-factor");
if (item != NULL)
X_FREE_SET(output_informations->form_factor, X_STRDUP(item->value));
X_FREE_SET(output_informations->form_factor, item != NULL ? X_STRDUP(item->value) : NULL);
}
item = spa_dict_lookup_item(info->props, "device.bus");
if (item != NULL)
X_FREE_SET(output_informations->bus, X_STRDUP(item->value));
X_FREE_SET(output_informations->bus, item != NULL ? X_STRDUP(item->value) : NULL);
data->module->bar->refresh(data->module->bar);
}
@ -659,7 +676,7 @@ static void
try_to_bind_node(struct node_data *node_data, char const *target_name, struct node **target_node, void **target_proxy,
struct spa_hook *target_listener)
{
/* profile deactived */
/* profile deactivated */
if (target_name == NULL)
return;
@ -827,18 +844,8 @@ destroy(struct module *module)
pipewire_deinit(private->data);
private->label->destroy(private->label);
/* sink */
free(private->sink_informations.name);
free(private->sink_informations.description);
free(private->sink_informations.icon);
free(private->sink_informations.form_factor);
free(private->sink_informations.bus);
/* source */
free(private->source_informations.name);
free(private->source_informations.description);
free(private->source_informations.icon);
free(private->source_informations.form_factor);
free(private->source_informations.bus);
output_informations_destroy(&private->sink_informations);
output_informations_destroy(&private->source_informations);
free(private);
module_default_destroy(module);

View file

@ -438,7 +438,7 @@ run(struct module *mod)
}
// Create refresh timer.
priv->refresh_timer_fd = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK);
priv->refresh_timer_fd = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK | TFD_CLOEXEC);
if (priv->refresh_timer_fd < 0) {
LOG_ERRNO("failed to create timerfd");
pa_mainloop_free(priv->mainloop);

View file

@ -161,13 +161,10 @@ content(struct module *mod)
static void
find_mount_points(const char *dev_path, mount_point_list_t *mount_points)
{
int fd = open("/proc/self/mountinfo", O_RDONLY | O_CLOEXEC);
FILE *f = fd >= 0 ? fdopen(fd, "r") : NULL;
FILE *f = fopen("/proc/self/mountinfo", "re");
if (fd < 0 || f == NULL) {
if (f == NULL) {
LOG_ERRNO("failed to open /proc/self/mountinfo");
if (fd >= 0)
close(fd);
return;
}

View file

@ -52,6 +52,7 @@ struct seat {
struct private
{
struct module *mod;
bool is_running;
struct zxdg_output_manager_v1 *xdg_output_manager;
struct zriver_status_manager_v1 *status_manager;
struct particle *template;
@ -88,6 +89,11 @@ content(struct module *mod)
mtx_lock(&m->mod->lock);
if (!m->is_running) {
mtx_unlock(&m->mod->lock);
return dynlist_exposable_new(NULL, 0, 0, 0);
}
uint32_t urgent = 0;
uint32_t occupied = 0;
uint32_t output_focused = 0;
@ -685,6 +691,8 @@ run(struct module *mod)
goto out;
}
m->is_running = true;
wl_display_roundtrip(display);
while (true) {

View file

@ -130,7 +130,7 @@ update_application(struct module *mod)
char path[1024];
snprintf(path, sizeof(path), "/proc/%d/cmdline", pid);
int fd = open(path, O_RDONLY);
int fd = open(path, O_RDONLY | O_CLOEXEC);
if (fd == -1)
return;

View file

@ -13,6 +13,61 @@
#include "map.h"
// String globbing match.
// Note: Uses "non-greedy" implementation for "*" wildcard matching
static bool
string_like(const char* name, const char* pattern)
{
LOG_DBG("pattern:%s name:%s", pattern, name);
int px = 0, nx = 0;
int nextpx = 0, nextnx = 0;
while (px < strlen(pattern) || nx < strlen(name)) {
if (px < strlen(pattern)) {
char c = pattern[px];
switch (c) {
case '?': {
// single character
px++;
nx++;
continue;
}
case '*': {
// zero or more glob
nextpx=px;
nextnx=nx+1;
px++;
continue;
}
default: {
// normal character
if (nx < strlen(name) && name[nx] == c)
{
px++;
nx++;
continue;
}
}
}
}
// mismatch
if (0 < nextnx && nextnx <= strlen(name)) {
px = nextpx;
nx = nextnx;
continue;
}
return false;
}
LOG_DBG("map: name %s matched all the pattern %s", name, pattern);
// Matched all of pattern to all of name. Success.
return true;
}
static bool
int_condition(const long tag_value, const long cond_value, enum map_op op)
{
@ -75,6 +130,8 @@ str_condition(const char *tag_value, const char *cond_value, enum map_op op)
return strcmp(tag_value, cond_value) >= 0;
case MAP_OP_GT:
return strcmp(tag_value, cond_value) > 0;
case MAP_OP_LIKE:
return string_like(tag_value, cond_value) != 0;
case MAP_OP_SELF:
LOG_WARN("using String tag as bool");
default:
@ -166,6 +223,7 @@ free_map_condition(struct map_condition *c)
case MAP_OP_LE:
case MAP_OP_LT:
case MAP_OP_GE:
case MAP_OP_LIKE:
case MAP_OP_GT:
free(c->value);
/* FALLTHROUGH */

View file

@ -9,6 +9,7 @@ enum map_op {
MAP_OP_GT,
MAP_OP_SELF,
MAP_OP_NOT,
MAP_OP_LIKE,
MAP_OP_AND,
MAP_OP_OR,

View file

@ -69,6 +69,7 @@ void yyerror(const char *s);
\< yylval.op = MAP_OP_LT; return CMP_OP;
>= yylval.op = MAP_OP_GE; return CMP_OP;
> yylval.op = MAP_OP_GT; return CMP_OP;
~~ yylval.op = MAP_OP_LIKE; return CMP_OP;
&& yylval.op = MAP_OP_AND; return BOOL_OP;
\|\| yylval.op = MAP_OP_OR; return BOOL_OP;
~ return NOT;

View file

@ -35,27 +35,27 @@ result: condition { MAP_CONDITION_PARSE_RESULT = $<condition>1; };
condition:
WORD {
$<condition>$ = malloc(sizeof(struct map_condition));
$<condition>$->tag = $<str>1;
$<condition>$->tag = $<str>1;
$<condition>$->op = MAP_OP_SELF;
}
|
WORD CMP_OP WORD {
$<condition>$ = malloc(sizeof(struct map_condition));
$<condition>$->tag = $<str>1;
$<condition>$->tag = $<str>1;
$<condition>$->op = $<op>2;
$<condition>$->value = $<str>3;
$<condition>$->value = $<str>3;
}
|
WORD CMP_OP STRING {
$<condition>$ = malloc(sizeof(struct map_condition));
$<condition>$->tag = $<str>1;
$<condition>$->tag = $<str>1;
$<condition>$->op = $<op>2;
$<condition>$->value = $<str>3;
$<condition>$->value = $<str>3;
}
|
L_PAR condition R_PAR { $<condition>$ = $<condition>2; }
|
NOT condition {
NOT condition {
$<condition>$ = malloc(sizeof(struct map_condition));
$<condition>$->cond1 = $<condition>2;
$<condition>$->op = MAP_OP_NOT;
@ -79,7 +79,7 @@ static char const*
token_to_str(yysymbol_kind_t tkn)
{
switch (tkn) {
case YYSYMBOL_CMP_OP: return "==, !=, <=, <, >=, >";
case YYSYMBOL_CMP_OP: return "==, !=, <=, <, >=, >, ~~";
case YYSYMBOL_BOOL_OP: return "||, &&";
case YYSYMBOL_L_PAR: return "(";
case YYSYMBOL_R_PAR: return ")";

View file

@ -84,6 +84,12 @@ EXTERN_MODULE(script);
#if defined(HAVE_PLUGIN_sway_xkb)
EXTERN_MODULE(sway_xkb);
#endif
#if defined(HAVE_PLUGIN_niri_language)
EXTERN_MODULE(niri_language);
#endif
#if defined(HAVE_PLUGIN_niri_workspaces)
EXTERN_MODULE(niri_workspaces);
#endif
#if defined(HAVE_PLUGIN_xkb)
EXTERN_MODULE(xkb);
#endif
@ -214,6 +220,12 @@ static void __attribute__((constructor)) init(void)
#if defined(HAVE_PLUGIN_sway_xkb)
REGISTER_CORE_MODULE(sway-xkb, sway_xkb);
#endif
#if defined(HAVE_PLUGIN_niri_language)
REGISTER_CORE_MODULE(niri-language, niri_language);
#endif
#if defined(HAVE_PLUGIN_niri_workspaces)
REGISTER_CORE_MODULE(niri-workspaces, niri_workspaces);
#endif
#if defined(HAVE_PLUGIN_xkb)
REGISTER_CORE_MODULE(xkb, xkb);
#endif

89
tag.c
View file

@ -430,12 +430,12 @@ sbuf_append(struct sbuf *s1, const char *s2)
// stores the number in "*value" on success
static bool
is_number(const char *str, int *value)
is_number(const char *str, long *value)
{
errno = 0;
char *end;
int v = strtol(str, &end, 10);
long v = strtol(str, &end, 10);
if (errno != 0 || *end != '\0')
return false;
@ -510,12 +510,7 @@ tags_expand_template(const char *template, const struct tag_set *tags)
FMT_HEX,
FMT_OCT,
FMT_PERCENT,
FMT_KBYTE,
FMT_MBYTE,
FMT_GBYTE,
FMT_KIBYTE,
FMT_MIBYTE,
FMT_GIBYTE,
FMT_DIVIDE,
} format
= FMT_DEFAULT;
@ -527,8 +522,9 @@ tags_expand_template(const char *template, const struct tag_set *tags)
} kind
= VALUE_VALUE;
int digits = 0;
int decimals = 2;
long digits = 0;
long decimals = 2;
long divider = 1;
bool zero_pad = false;
char *point = NULL;
@ -541,18 +537,38 @@ tags_expand_template(const char *template, const struct tag_set *tags)
format = FMT_OCT;
else if (strcmp(tag_args[i], "%") == 0)
format = FMT_PERCENT;
else if (strcmp(tag_args[i], "kb") == 0)
format = FMT_KBYTE;
else if (strcmp(tag_args[i], "mb") == 0)
format = FMT_MBYTE;
else if (strcmp(tag_args[i], "gb") == 0)
format = FMT_GBYTE;
else if (strcmp(tag_args[i], "kib") == 0)
format = FMT_KIBYTE;
else if (strcmp(tag_args[i], "mib") == 0)
format = FMT_MIBYTE;
else if (strcmp(tag_args[i], "gib") == 0)
format = FMT_GIBYTE;
else if (*tag_args[i] == '/') {
format = FMT_DIVIDE;
const char *divider_str = tag_args[i] + 1;
if (!is_number(divider_str, &divider) || divider == 0) {
divider = 1;
LOG_WARN("tag `%s`: invalid divider %s, reset to 1", tag_name, divider_str);
}
}
else if (strcmp(tag_args[i], "kb") == 0) {
format = FMT_DIVIDE;
divider = 1000;
}
else if (strcmp(tag_args[i], "mb") == 0) {
format = FMT_DIVIDE;
divider = 1000 * 1000;
}
else if (strcmp(tag_args[i], "gb") == 0) {
format = FMT_DIVIDE;
divider = 1000 * 1000 * 1000;
}
else if (strcmp(tag_args[i], "kib") == 0) {
format = FMT_DIVIDE;
divider = 1024;
}
else if (strcmp(tag_args[i], "mib") == 0) {
format = FMT_DIVIDE;
divider = 1024 * 1024;
}
else if (strcmp(tag_args[i], "gib") == 0) {
format = FMT_DIVIDE;
divider = 1024 * 1024 * 1024;
}
else if (strcmp(tag_args[i], "min") == 0)
kind = VALUE_MIN;
else if (strcmp(tag_args[i], "max") == 0)
@ -634,20 +650,7 @@ tags_expand_template(const char *template, const struct tag_set *tags)
break;
}
case FMT_KBYTE:
case FMT_MBYTE:
case FMT_GBYTE:
case FMT_KIBYTE:
case FMT_MIBYTE:
case FMT_GIBYTE: {
const long divider = format == FMT_KBYTE ? 1000
: format == FMT_MBYTE ? 1000 * 1000
: format == FMT_GBYTE ? 1000 * 1000 * 1000
: format == FMT_KIBYTE ? 1024
: format == FMT_MIBYTE ? 1024 * 1024
: format == FMT_GIBYTE ? 1024 * 1024 * 1024
: 1;
case FMT_DIVIDE: {
char str[24];
if (tag->type(tag) == TAG_TYPE_FLOAT) {
const char *fmt = zero_pad ? "%0*.*f" : "%*.*f";
@ -684,19 +687,7 @@ tags_expand_template(const char *template, const struct tag_set *tags)
fmt = zero_pad ? "%0*lu" : "%*lu";
break;
case FMT_KBYTE:
case FMT_MBYTE:
case FMT_GBYTE:
case FMT_KIBYTE:
case FMT_MIBYTE:
case FMT_GIBYTE: {
const long divider = format == FMT_KBYTE ? 1024
: format == FMT_MBYTE ? 1024 * 1024
: format == FMT_GBYTE ? 1024 * 1024 * 1024
: format == FMT_KIBYTE ? 1000
: format == FMT_MIBYTE ? 1000 * 1000
: format == FMT_GIBYTE ? 1000 * 1000 * 1000
: 1;
case FMT_DIVIDE: {
value /= divider;
fmt = zero_pad ? "%0*lu" : "%*lu";
break;

43
yml.c
View file

@ -366,6 +366,47 @@ format_error(enum yml_error err, const struct yml_node *parent, const struct yml
return err_str;
}
static char *
replace_env_variables(const char *str, size_t len)
{
char *result = strndup(str, len);
char *start, *key;
const char *end, *env_value;
const char* prefix = "${";
const char* suffix = "}";
const size_t pref_len = 2;
const size_t suff_len = 1;
size_t key_len;
while ((start = strstr(result, prefix)) != NULL &&
(end = strstr(start, suffix)) != NULL)
{
key_len = end - start - pref_len;
key = strndup(start + pref_len, key_len);
env_value = getenv(key);
if (env_value) {
size_t result_len = strlen(result);
size_t new_len = result_len - key_len - pref_len - suff_len + strlen(env_value);
char *new_result = malloc(new_len + 1);
strncpy(new_result, result, start - result);
new_result[start - result] = '\0';
strcat(new_result, env_value);
strcat(new_result, end + 1);
free(result);
result = new_result;
} else {
memmove(start, end + 1, strlen(end + 1) + 1);
}
free(key);
}
return result;
}
struct yml_node *
yml_load(FILE *yml, char **error)
{
@ -456,7 +497,7 @@ yml_load(FILE *yml, char **error)
case YAML_SCALAR_EVENT: {
struct yml_node *new_scalar = calloc(1, sizeof(*new_scalar));
new_scalar->type = SCALAR;
new_scalar->scalar.value = strndup((const char *)event.data.scalar.value, event.data.scalar.length);
new_scalar->scalar.value = replace_env_variables((const char *)event.data.scalar.value, event.data.scalar.length);
enum yml_error err = add_node(n, new_scalar, event.start_mark);
if (err != YML_ERR_NONE) {