forked from external/yambar
Compare commits
48 commits
releases/1
...
master
Author | SHA1 | Date | |
---|---|---|---|
80404504ed | |||
43c8316380 | |||
|
fc24ea225d | ||
|
e1f7c0292f | ||
|
d746d12f6a | ||
|
61d082c802 | ||
|
57711f0dbe | ||
|
b15714b38a | ||
|
3e0083c9f2 | ||
|
a367895dc6 | ||
|
650d1f13f9 | ||
|
e1b6a78f22 | ||
|
20d48a753b | ||
|
37ecc251a4 | ||
|
4826a52306 | ||
|
0f47cbb889 | ||
|
c3f7fe013d | ||
|
b81e41c3c4 | ||
|
060586dbbe | ||
|
c80bae7604 | ||
|
311c481bfe | ||
|
9498d7e445 | ||
|
2d651d1c0e | ||
|
1b2dee55ef | ||
|
700bf5b28c | ||
|
f8ba887dcd | ||
|
887e770202 | ||
|
54902f46ab | ||
|
699c563051 | ||
|
a5ae61b5df | ||
|
568eb1140f | ||
|
3e0a65f185 | ||
|
1a323c6d21 | ||
|
739dc30323 | ||
|
8422e7e0b1 | ||
|
9cc5e0f7a7 | ||
|
3431d5fc75 | ||
|
20659d3350 | ||
|
0bea49b75e | ||
|
70efd7d15c | ||
|
b8a93a2673 | ||
|
a467f56677 | ||
|
b3313cefc6 | ||
|
00234696fe | ||
|
3a7455913f | ||
|
547cef5afb | ||
|
6f3952819f | ||
|
f148c68736 |
54 changed files with 1477 additions and 215 deletions
|
@ -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
|
||||
|
|
55
CHANGELOG.md
55
CHANGELOG.md
|
@ -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
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
# Yambar
|
||||
|
||||
[](https://repology.org/project/yambar/versions)
|
||||
[](https://repology.org/project/yambar/versions)
|
||||
|
||||
|
||||
## Index
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -137,7 +137,7 @@ content:
|
|||
|
||||
# STACK
|
||||
|
||||
This particles combines multiple decorations.
|
||||
This particle combines multiple decorations.
|
||||
|
||||
## CONFIGURATION
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
34
doc/yambar-modules-niri-language.5.scd
Normal file
34
doc/yambar-modules-niri-language.5.scd
Normal 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)
|
||||
|
60
doc/yambar-modules-niri-workspaces.5.scd
Normal file
60
doc/yambar-modules-niri-workspaces.5.scd
Normal 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)
|
||||
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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: ├}}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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*
|
||||
|
|
69
examples/river-minimal.yml
Normal file
69
examples/river-minimal.yml
Normal 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}"}
|
|
@ -19,7 +19,7 @@
|
|||
#
|
||||
# Now the fun part
|
||||
#
|
||||
# Exemple configuration:
|
||||
# Example configuration:
|
||||
#
|
||||
# - script:
|
||||
# path: /absolute/path/to/dwl-tags.sh
|
||||
|
|
|
@ -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
12
log.c
|
@ -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
2
main.c
|
@ -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;
|
||||
|
|
|
@ -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,
|
||||
},
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
28
modules/i3.c
28
modules/i3.c
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
377
modules/niri-common.c
Normal 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
45
modules/niri-common.h
Normal 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
160
modules/niri-language.c
Normal 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
163
modules/niri-workspaces.c
Normal 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
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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 */
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 ")";
|
||||
|
|
12
plugin.c
12
plugin.c
|
@ -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
89
tag.c
|
@ -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, ÷r) || 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
43
yml.c
|
@ -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) {
|
||||
|
|
Loading…
Add table
Reference in a new issue