Repository: shdown/luastatus
Branch: master
Commit: dda015e2f979
Files: 805
Total size: 1.2 MB
Directory structure:
gitextract_ja3_jqjt/
├── .circleci/
│ ├── config.yml
│ └── run-pt.sh
├── .gitignore
├── .luacheckrc
├── CMakeLists.txt
├── COPYING.LESSER.txt
├── COPYING.txt
├── DEPENDS.txt
├── DOCS/
│ ├── CUSTOM_DATA_SRC_WIDGET.md
│ ├── MIGRATION_GUIDE.md
│ ├── WRITING_BARLIB_OR_PLUGIN.md
│ ├── c_notes/
│ │ ├── eintr-policy.md
│ │ └── empty-ranges-and-c-stdlib.md
│ └── design/
│ ├── locking-patterns.md
│ └── map_get.md
├── README.md
├── barlibs/
│ ├── dwm/
│ │ ├── CMakeLists.txt
│ │ ├── README.rst
│ │ └── dwm.c
│ ├── i3/
│ │ ├── CMakeLists.txt
│ │ ├── README.rst
│ │ ├── escape_json_str.c
│ │ ├── escape_json_str.h
│ │ ├── event_watcher.c
│ │ ├── event_watcher.h
│ │ ├── fuzz_esc_json/
│ │ │ ├── .gitignore
│ │ │ ├── build.sh
│ │ │ ├── clear.sh
│ │ │ ├── fuzz.sh
│ │ │ ├── gen_testcases.sh
│ │ │ ├── harness.c
│ │ │ └── testcases/
│ │ │ ├── testcase_000
│ │ │ ├── testcase_001
│ │ │ ├── testcase_002
│ │ │ ├── testcase_003
│ │ │ ├── testcase_004
│ │ │ ├── testcase_005
│ │ │ ├── testcase_006
│ │ │ ├── testcase_007
│ │ │ ├── testcase_008
│ │ │ └── testcase_009
│ │ ├── fuzz_esc_pango/
│ │ │ ├── .gitignore
│ │ │ ├── build.sh
│ │ │ ├── clear.sh
│ │ │ ├── fuzz.sh
│ │ │ ├── gen_testcases.sh
│ │ │ ├── harness.c
│ │ │ └── testcases/
│ │ │ ├── testcase_000
│ │ │ ├── testcase_001
│ │ │ ├── testcase_002
│ │ │ ├── testcase_003
│ │ │ ├── testcase_004
│ │ │ ├── testcase_005
│ │ │ ├── testcase_006
│ │ │ ├── testcase_007
│ │ │ ├── testcase_008
│ │ │ └── testcase_009
│ │ ├── i3.c
│ │ ├── luastatus-i3-wrapper
│ │ ├── pango_escape.c
│ │ ├── pango_escape.h
│ │ └── priv.h
│ ├── lemonbar/
│ │ ├── CMakeLists.txt
│ │ ├── README.rst
│ │ ├── fuzz_esc/
│ │ │ ├── .gitignore
│ │ │ ├── build.sh
│ │ │ ├── clear.sh
│ │ │ ├── fuzz.sh
│ │ │ ├── gen_testcases.sh
│ │ │ ├── harness.c
│ │ │ └── testcases/
│ │ │ ├── testcase_000
│ │ │ ├── testcase_001
│ │ │ ├── testcase_002
│ │ │ ├── testcase_003
│ │ │ ├── testcase_004
│ │ │ ├── testcase_005
│ │ │ ├── testcase_006
│ │ │ ├── testcase_007
│ │ │ ├── testcase_008
│ │ │ └── testcase_009
│ │ ├── fuzz_sanitize/
│ │ │ ├── .gitignore
│ │ │ ├── build.sh
│ │ │ ├── clear.sh
│ │ │ ├── fuzz.sh
│ │ │ ├── gen_testcases.sh
│ │ │ ├── harness.c
│ │ │ └── testcases/
│ │ │ ├── X_testcase_000
│ │ │ ├── X_testcase_001
│ │ │ ├── X_testcase_002
│ │ │ ├── X_testcase_003
│ │ │ ├── X_testcase_004
│ │ │ ├── X_testcase_005
│ │ │ ├── X_testcase_fps
│ │ │ ├── Y_testcase_000
│ │ │ ├── Y_testcase_001
│ │ │ ├── Y_testcase_002
│ │ │ ├── Y_testcase_003
│ │ │ ├── Z_testcase_000
│ │ │ ├── Z_testcase_001
│ │ │ ├── Z_testcase_002
│ │ │ ├── Z_testcase_003
│ │ │ └── Z_testcase_004
│ │ ├── lemonbar.c
│ │ ├── luastatus-lemonbar-launcher
│ │ ├── markup_utils.c
│ │ └── markup_utils.h
│ └── stdout/
│ ├── CMakeLists.txt
│ ├── README.rst
│ ├── fuzz/
│ │ ├── .gitignore
│ │ ├── build.sh
│ │ ├── clear.sh
│ │ ├── fuzz.sh
│ │ ├── gen_testcases.sh
│ │ ├── harness.c
│ │ └── testcases/
│ │ ├── testcase_000
│ │ ├── testcase_001
│ │ ├── testcase_002
│ │ ├── testcase_003
│ │ ├── testcase_004
│ │ ├── testcase_005
│ │ ├── testcase_006
│ │ ├── testcase_007
│ │ ├── testcase_008
│ │ └── testcase_009
│ ├── luastatus-dvtm
│ ├── luastatus-stdout-wrapper
│ ├── open_stdio_file.c
│ ├── open_stdio_file.h
│ ├── sanitize.c
│ ├── sanitize.h
│ └── stdout.c
├── check_final_newline.py
├── check_includes.sh
├── check_style.sh
├── contrib/
│ ├── luastatus-9999.ebuild
│ └── luastatus.spec
├── debian/
│ ├── changelog
│ ├── compat
│ ├── control
│ ├── copyright
│ └── rules
├── examples/
│ ├── dwm/
│ │ ├── alsa-gauge.lua
│ │ ├── alsa.lua
│ │ ├── backlight.lua
│ │ ├── battery.lua
│ │ ├── bluetooth.lua
│ │ ├── btc-price.lua
│ │ ├── cpu-freq.lua
│ │ ├── cpu-temperature.lua
│ │ ├── cpu-usage.lua
│ │ ├── disk-io.lua
│ │ ├── file-contents.lua
│ │ ├── fs.lua
│ │ ├── gmail.lua
│ │ ├── ip.lua
│ │ ├── loadavg-linux.lua
│ │ ├── media-player-mpris.lua
│ │ ├── mem-usage.lua
│ │ ├── mpd.lua
│ │ ├── network-rate.lua
│ │ ├── pulse-gauge.lua
│ │ ├── pulse.lua
│ │ ├── systemd-unit.lua
│ │ ├── time-battery-combined.lua
│ │ ├── time-date.lua
│ │ ├── tor.lua
│ │ ├── uptime-linux.lua
│ │ ├── weather-rus.lua
│ │ ├── weather.lua
│ │ ├── wireless.lua
│ │ └── xkb.lua
│ ├── i3/
│ │ ├── alsa-gauge.lua
│ │ ├── alsa-interactive-gauge.lua
│ │ ├── alsa.lua
│ │ ├── backlight.lua
│ │ ├── battery.lua
│ │ ├── bluetooth.lua
│ │ ├── btc-price.lua
│ │ ├── cpu-freq.lua
│ │ ├── cpu-temperature.lua
│ │ ├── cpu-usage.lua
│ │ ├── disk-io.lua
│ │ ├── file-contents.lua
│ │ ├── fs.lua
│ │ ├── gmail.lua
│ │ ├── ip.lua
│ │ ├── loadavg-linux.lua
│ │ ├── media-player-mpris.lua
│ │ ├── mem-usage.lua
│ │ ├── mpd-progressbar.lua
│ │ ├── network-rate.lua
│ │ ├── pulse-gauge.lua
│ │ ├── pulse-interactive-gauge.lua
│ │ ├── pulse.lua
│ │ ├── systemd-unit.lua
│ │ ├── time-battery-combined.lua
│ │ ├── time-date.lua
│ │ ├── tor.lua
│ │ ├── update-on-click.lua
│ │ ├── uptime-linux.lua
│ │ ├── weather-rus.lua
│ │ ├── weather.lua
│ │ ├── wireless.lua
│ │ └── xkb.lua
│ └── lemonbar/
│ ├── alsa-gauge.lua
│ ├── alsa.lua
│ ├── backlight.lua
│ ├── battery.lua
│ ├── bluetooth.lua
│ ├── btc-price.lua
│ ├── cpu-freq.lua
│ ├── cpu-temperature.lua
│ ├── cpu-usage.lua
│ ├── disk-io.lua
│ ├── file-contents.lua
│ ├── fs.lua
│ ├── gmail.lua
│ ├── ip.lua
│ ├── loadavg-linux.lua
│ ├── media-player-mpris.lua
│ ├── mem-usage.lua
│ ├── mpd-progressbar.lua
│ ├── network-rate.lua
│ ├── pulse-gauge.lua
│ ├── pulse.lua
│ ├── systemd-unit.lua
│ ├── time-battery-combined.lua
│ ├── time-date.lua
│ ├── tor.lua
│ ├── uptime-linux.lua
│ ├── weather-rus.lua
│ ├── weather.lua
│ ├── wireless.lua
│ └── xkb.lua
├── fuzz_utils/
│ ├── do_fuzz_everything.sh
│ ├── find_interesting.sh
│ ├── fuzz_utils.h
│ └── gen_testcases/
│ ├── .gitignore
│ ├── __init__.py
│ ├── gen_testcases.py
│ ├── mod_generator.py
│ ├── mod_mutator.py
│ └── mod_mutators.py
├── generate-man.sh
├── include/
│ ├── barlib_data.h
│ ├── barlib_data_v1.h
│ ├── barlib_v1.h
│ ├── common.h
│ ├── plugin_data.h
│ ├── plugin_data_v1.h
│ ├── plugin_v1.h
│ └── sayf_macros.h
├── libhackyfix/
│ ├── CMakeLists.txt
│ ├── README.txt
│ ├── fatal.c
│ ├── fatal.h
│ ├── get_rtld_next_handle.c
│ ├── get_rtld_next_handle.h
│ └── libhackyfix.c
├── libls/
│ ├── CMakeLists.txt
│ ├── ls_algo.h
│ ├── ls_alloc_utils.c
│ ├── ls_alloc_utils.h
│ ├── ls_compdep.h
│ ├── ls_cstring_utils.c
│ ├── ls_cstring_utils.h
│ ├── ls_evloop_lfuncs.h
│ ├── ls_fifo_device.h
│ ├── ls_freemem.h
│ ├── ls_getenv_r.c
│ ├── ls_getenv_r.h
│ ├── ls_io_utils.c
│ ├── ls_io_utils.h
│ ├── ls_lua_compat.h
│ ├── ls_osdep.c
│ ├── ls_osdep.h
│ ├── ls_panic.c
│ ├── ls_panic.h
│ ├── ls_parse_int.c
│ ├── ls_parse_int.h
│ ├── ls_probes.in.h
│ ├── ls_strarr.h
│ ├── ls_string.c
│ ├── ls_string.h
│ ├── ls_time_utils.h
│ ├── ls_tls_ebuf.c
│ ├── ls_tls_ebuf.h
│ ├── ls_xallocf.c
│ └── ls_xallocf.h
├── libmoonvisit/
│ ├── CMakeLists.txt
│ ├── README.txt
│ ├── moonvisit.c
│ └── moonvisit.h
├── libprocalive/
│ ├── CMakeLists.txt
│ ├── procalive_lfuncs.c
│ └── procalive_lfuncs.h
├── librunshell/
│ ├── CMakeLists.txt
│ ├── README.txt
│ ├── runshell.c
│ └── runshell.h
├── libsafe/
│ ├── CMakeLists.txt
│ ├── mut_safev.h
│ ├── safe_common.c
│ ├── safe_common.h
│ └── safev.h
├── libwidechar/
│ ├── CMakeLists.txt
│ ├── README.txt
│ ├── config.in.h
│ ├── libwidechar.c
│ ├── libwidechar.h
│ ├── libwidechar_compdep.h
│ └── libwidechar_xspan.h
├── plugins/
│ ├── alsa/
│ │ ├── CMakeLists.txt
│ │ ├── README.rst
│ │ └── alsa.c
│ ├── backlight-linux/
│ │ ├── CMakeLists.txt
│ │ ├── README.rst
│ │ └── backlight-linux.lua
│ ├── battery-linux/
│ │ ├── CMakeLists.txt
│ │ ├── README.rst
│ │ └── battery-linux.lua
│ ├── cpu-freq-linux/
│ │ ├── CMakeLists.txt
│ │ ├── README.rst
│ │ └── cpu-freq-linux.lua
│ ├── cpu-usage-linux/
│ │ ├── CMakeLists.txt
│ │ ├── README.rst
│ │ └── cpu-usage-linux.lua
│ ├── dbus/
│ │ ├── .gitignore
│ │ ├── CMakeLists.txt
│ │ ├── README.rst
│ │ ├── README_FN_CALL_METHOD.rst
│ │ ├── README_FN_MKVAL.rst
│ │ ├── README_FN_PROP.rst
│ │ ├── bustype2idx.h
│ │ ├── cstringify.sh
│ │ ├── cvt.c
│ │ ├── cvt.h
│ │ ├── dbus.c
│ │ ├── load_lualib.c
│ │ ├── load_lualib.h
│ │ ├── lualib.lua
│ │ └── zoo/
│ │ ├── README.txt
│ │ ├── zoo.c
│ │ ├── zoo.h
│ │ ├── zoo_call_params.c
│ │ ├── zoo_call_params.h
│ │ ├── zoo_call_prot.c
│ │ ├── zoo_call_prot.h
│ │ ├── zoo_checkudata.c
│ │ ├── zoo_checkudata.h
│ │ ├── zoo_mt.c
│ │ ├── zoo_mt.h
│ │ ├── zoo_uncvt_type.c
│ │ ├── zoo_uncvt_type.h
│ │ ├── zoo_uncvt_val.c
│ │ └── zoo_uncvt_val.h
│ ├── disk-io-linux/
│ │ ├── CMakeLists.txt
│ │ ├── README.rst
│ │ └── disk-io-linux.lua
│ ├── file-contents-linux/
│ │ ├── CMakeLists.txt
│ │ ├── README.rst
│ │ └── file-contents-linux.lua
│ ├── fs/
│ │ ├── CMakeLists.txt
│ │ ├── README.rst
│ │ ├── fs.c
│ │ ├── strlist.c
│ │ └── strlist.h
│ ├── imap/
│ │ ├── CMakeLists.txt
│ │ ├── README.rst
│ │ └── imap.lua
│ ├── inotify/
│ │ ├── CMakeLists.txt
│ │ ├── README.rst
│ │ ├── inotify.c
│ │ ├── inotify_compat.h
│ │ └── probes.in.h
│ ├── is-program-running/
│ │ ├── CMakeLists.txt
│ │ ├── README.rst
│ │ └── is-program-running.lua
│ ├── mem-usage-linux/
│ │ ├── CMakeLists.txt
│ │ ├── README.rst
│ │ └── mem-usage-linux.lua
│ ├── mpd/
│ │ ├── CMakeLists.txt
│ │ ├── README.rst
│ │ ├── connect.c
│ │ ├── connect.h
│ │ ├── fuzz/
│ │ │ ├── .gitignore
│ │ │ ├── build.sh
│ │ │ ├── clear.sh
│ │ │ ├── fuzz.sh
│ │ │ ├── fuzz_all.sh
│ │ │ ├── gen_testcases.sh
│ │ │ ├── harness
│ │ │ ├── harness.c
│ │ │ ├── testcases_append_kv/
│ │ │ │ ├── testcase_000
│ │ │ │ ├── testcase_001
│ │ │ │ ├── testcase_002
│ │ │ │ ├── testcase_003
│ │ │ │ ├── testcase_004
│ │ │ │ ├── testcase_005
│ │ │ │ ├── testcase_006
│ │ │ │ ├── testcase_007
│ │ │ │ ├── testcase_008
│ │ │ │ └── testcase_009
│ │ │ ├── testcases_check_greeting/
│ │ │ │ ├── testcase_000
│ │ │ │ ├── testcase_001
│ │ │ │ ├── testcase_002
│ │ │ │ ├── testcase_003
│ │ │ │ ├── testcase_004
│ │ │ │ ├── testcase_005
│ │ │ │ ├── testcase_006
│ │ │ │ ├── testcase_007
│ │ │ │ ├── testcase_008
│ │ │ │ ├── testcase_009
│ │ │ │ └── testcase_pfx_only
│ │ │ ├── testcases_get_resp_type/
│ │ │ │ ├── ack_testcase_000
│ │ │ │ ├── ack_testcase_001
│ │ │ │ ├── ack_testcase_002
│ │ │ │ ├── ack_testcase_003
│ │ │ │ ├── ack_testcase_004
│ │ │ │ ├── ok_testcase_000
│ │ │ │ ├── ok_testcase_001
│ │ │ │ ├── ok_testcase_002
│ │ │ │ ├── ok_testcase_003
│ │ │ │ ├── ok_testcase_004
│ │ │ │ ├── ok_testcase_pfx_only
│ │ │ │ ├── other_testcase_000
│ │ │ │ ├── other_testcase_001
│ │ │ │ ├── other_testcase_002
│ │ │ │ ├── other_testcase_003
│ │ │ │ └── other_testcase_004
│ │ │ └── testcases_write_quoted/
│ │ │ ├── testcase_000
│ │ │ ├── testcase_001
│ │ │ ├── testcase_002
│ │ │ ├── testcase_003
│ │ │ ├── testcase_004
│ │ │ ├── testcase_005
│ │ │ ├── testcase_006
│ │ │ ├── testcase_007
│ │ │ ├── testcase_008
│ │ │ └── testcase_009
│ │ ├── line_reader.c
│ │ ├── line_reader.h
│ │ ├── mpd.c
│ │ ├── safe_haven.c
│ │ └── safe_haven.h
│ ├── mpris/
│ │ ├── CMakeLists.txt
│ │ ├── README.rst
│ │ └── mpris.lua
│ ├── multiplex/
│ │ ├── CMakeLists.txt
│ │ ├── README.rst
│ │ ├── config.in.h
│ │ ├── conq.c
│ │ ├── conq.h
│ │ ├── external_context.h
│ │ ├── map_ref.c
│ │ ├── map_ref.h
│ │ ├── multiplex.c
│ │ ├── priv.h
│ │ ├── runner.c
│ │ ├── runner.h
│ │ ├── runtime_data.c
│ │ ├── runtime_data.h
│ │ ├── wspec_list.c
│ │ └── wspec_list.h
│ ├── network-linux/
│ │ ├── CMakeLists.txt
│ │ ├── README.rst
│ │ ├── ethernet_info.c
│ │ ├── ethernet_info.h
│ │ ├── iface_type.c
│ │ ├── iface_type.h
│ │ ├── network.c
│ │ ├── string_set.c
│ │ ├── string_set.h
│ │ ├── wireless_info.c
│ │ └── wireless_info.h
│ ├── network-rate-linux/
│ │ ├── CMakeLists.txt
│ │ ├── README.rst
│ │ └── network-rate-linux.lua
│ ├── pipe/
│ │ ├── CMakeLists.txt
│ │ ├── README.rst
│ │ └── pipe.lua
│ ├── pipev2/
│ │ ├── CMakeLists.txt
│ │ ├── README.rst
│ │ ├── launch.c
│ │ ├── launch.h
│ │ ├── pipev2.c
│ │ ├── sigdb.c
│ │ ├── sigdb.h
│ │ ├── utils.c
│ │ └── utils.h
│ ├── pulse/
│ │ ├── CMakeLists.txt
│ │ ├── README.rst
│ │ └── pulse.c
│ ├── systemd-unit/
│ │ ├── CMakeLists.txt
│ │ ├── README.rst
│ │ └── systemd-unit.lua
│ ├── temperature-linux/
│ │ ├── CMakeLists.txt
│ │ ├── README.rst
│ │ └── temperature-linux.lua
│ ├── timer/
│ │ ├── CMakeLists.txt
│ │ ├── README.rst
│ │ └── timer.c
│ ├── udev/
│ │ ├── CMakeLists.txt
│ │ ├── README.rst
│ │ └── udev.c
│ ├── unixsock/
│ │ ├── CMakeLists.txt
│ │ ├── README.rst
│ │ ├── cloexec_accept.c
│ │ ├── cloexec_accept.h
│ │ ├── probes.in.h
│ │ ├── server.c
│ │ ├── server.h
│ │ └── unixsock.c
│ ├── web/
│ │ ├── CMakeLists.txt
│ │ ├── README.rst
│ │ ├── compat_lua_resume.c
│ │ ├── compat_lua_resume.h
│ │ ├── fuzz_esc_json/
│ │ │ ├── .gitignore
│ │ │ ├── build.sh
│ │ │ ├── clear.sh
│ │ │ ├── fuzz.sh
│ │ │ ├── gen_testcases.sh
│ │ │ ├── harness.c
│ │ │ └── testcases/
│ │ │ ├── testcase_000
│ │ │ ├── testcase_001
│ │ │ ├── testcase_002
│ │ │ ├── testcase_003
│ │ │ ├── testcase_004
│ │ │ ├── testcase_005
│ │ │ ├── testcase_006
│ │ │ ├── testcase_007
│ │ │ ├── testcase_008
│ │ │ └── testcase_009
│ │ ├── fuzz_urldecode/
│ │ │ ├── .gitignore
│ │ │ ├── build.sh
│ │ │ ├── fuzz.sh
│ │ │ ├── gen_testcases.sh
│ │ │ ├── harness.c
│ │ │ └── testcases/
│ │ │ ├── testcase_000
│ │ │ ├── testcase_001
│ │ │ ├── testcase_002
│ │ │ ├── testcase_003
│ │ │ ├── testcase_004
│ │ │ ├── testcase_005
│ │ │ ├── testcase_006
│ │ │ ├── testcase_007
│ │ │ ├── testcase_008
│ │ │ ├── testcase_009
│ │ │ ├── testcase_extra1
│ │ │ ├── testcase_extra2
│ │ │ └── testcase_extra3
│ │ ├── fuzz_urlencode/
│ │ │ ├── .gitignore
│ │ │ ├── build.sh
│ │ │ ├── fuzz.sh
│ │ │ ├── fuzz_all.sh
│ │ │ ├── gen_testcases.sh
│ │ │ ├── harness.c
│ │ │ └── testcases/
│ │ │ ├── testcase_000
│ │ │ ├── testcase_001
│ │ │ ├── testcase_002
│ │ │ ├── testcase_003
│ │ │ ├── testcase_004
│ │ │ ├── testcase_005
│ │ │ ├── testcase_006
│ │ │ ├── testcase_007
│ │ │ ├── testcase_008
│ │ │ └── testcase_009
│ │ ├── get_min_curl_ver.sh
│ │ ├── json_config.in.h
│ │ ├── make_request.c
│ │ ├── make_request.h
│ │ ├── mod_json/
│ │ │ ├── json_decode.c
│ │ │ ├── json_decode.h
│ │ │ ├── json_encode_num.c
│ │ │ ├── json_encode_num.h
│ │ │ ├── json_encode_str.c
│ │ │ ├── json_encode_str.h
│ │ │ ├── mod_json.c
│ │ │ └── mod_json.h
│ │ ├── mod_urlencode/
│ │ │ ├── mod_urlencode.c
│ │ │ ├── mod_urlencode.h
│ │ │ ├── urldecode.c
│ │ │ ├── urldecode.h
│ │ │ ├── urlencode.c
│ │ │ └── urlencode.h
│ │ ├── next_request_params.h
│ │ ├── opts.c
│ │ ├── opts.h
│ │ ├── parse_opts.c
│ │ ├── parse_opts.h
│ │ ├── set_error.c
│ │ ├── set_error.h
│ │ └── web.c
│ ├── xkb/
│ │ ├── CMakeLists.txt
│ │ ├── README.rst
│ │ ├── somehow.c
│ │ ├── somehow.h
│ │ ├── wrongly.c
│ │ ├── wrongly.h
│ │ └── xkb.c
│ └── xtitle/
│ ├── CMakeLists.txt
│ ├── README.rst
│ └── xtitle.c
├── release.sh
├── run_literally_all_tests.sh
├── run_luacheck.sh
└── tests/
├── .gitignore
├── CMakeLists.txt
├── README.txt
├── barlib-runners/
│ └── runner-redirect-34
├── dlopen.supp
├── glib.supp
├── httpserv/
│ ├── .gitignore
│ ├── CMakeLists.txt
│ ├── argparser.c
│ ├── argparser.h
│ ├── buffer.c
│ ├── buffer.h
│ ├── common.c
│ ├── common.h
│ ├── main.c
│ ├── server.c
│ ├── server.h
│ ├── sleep_millis.c
│ └── sleep_millis.h
├── kcov_wrapper.c
├── listnets.c
├── minstd.h
├── mock_barlib.c
├── mock_plugin.c
├── optional/
│ └── dbus_srv.py
├── parrot.c
├── pt.sh
├── pt_dbus_daemon.lib.bash
├── pt_pulseaudio_daemon.lib.bash
├── pt_stopwatch.lib.bash
├── pt_tests/
│ ├── _misc/
│ │ └── 01-simul.lib.bash
│ ├── add_test.sh
│ ├── barlib-i3/
│ │ ├── 00-common.lib.bash
│ │ ├── 01-output.lib.bash
│ │ ├── 02-input.lib.bash
│ │ └── 03-lfunc-pango-escape.lib.bash
│ ├── barlib-lemonbar/
│ │ ├── 00-common.lib.bash
│ │ ├── 01-output.lib.bash
│ │ ├── 02-input.lib.bash
│ │ └── 03-lfunc-pango-escape.lib.bash
│ ├── barlib-stdout/
│ │ ├── 00-common.lib.bash
│ │ ├── 01-output.lib.bash
│ │ ├── 03-input-filename.lib.bash
│ │ └── 04-input-fd.lib.bash
│ ├── luastatus/
│ │ ├── 00-common.lib.bash
│ │ ├── 01-misc.lib.bash
│ │ ├── 02-getenv.lib.bash
│ │ ├── 03-exit.lib.bash
│ │ ├── 04-setlocale.lib.bash
│ │ ├── 05-libwidechar.lib.bash
│ │ └── 06-execute.lib.bash
│ ├── plugin-battery-linux/
│ │ ├── 00-common.lib.bash
│ │ ├── 01-full-status-full.lib.bash
│ │ ├── 02-full-status-not-charging.lib.bash
│ │ ├── 03-dischaging.lib.bash
│ │ ├── 04-charging.lib.bash
│ │ └── 05-nofile.lib.bash
│ ├── plugin-cpu-freq-linux/
│ │ ├── 00-common.lib.bash
│ │ ├── 01-simple.lib.bash
│ │ └── 02-please-reload.lib.bash
│ ├── plugin-cpu-usage-linux/
│ │ ├── 00-common.lib.bash
│ │ ├── 01-total.lib.bash
│ │ ├── 02-cpu1.lib.bash
│ │ ├── 03-cpu2.lib.bash
│ │ ├── 04-cpu3.lib.bash
│ │ └── 05-cpu4.lib.bash
│ ├── plugin-dbus/
│ │ ├── 00-common.lib.bash
│ │ ├── 01-simple.lib.bash
│ │ ├── 02-timeout.lib.bash
│ │ ├── 03-greet.lib.bash
│ │ ├── 04-get-prop.lib.bash
│ │ ├── 05-get-all-prop.lib.bash
│ │ ├── 06-OPTIONAL-getset-prop.lib.bash
│ │ ├── 07-OPTIONAL-getall-prop.lib.bash
│ │ ├── 08-OPTIONAL-call-method-str.lib.bash
│ │ ├── 09-OPTIONAL-call-method-DTLL.lib.bash
│ │ ├── 10-OPTIONAL-call-method-arr.lib.bash
│ │ ├── 11-OPTIONAL-call-method-dict.lib.bash
│ │ ├── 12-OPTIONAL-call-method-tuple.lib.bash
│ │ ├── 13-OPTIONAL-call-method-misc.lib.bash
│ │ ├── 14-OPTIONAL-call-method-handle.lib.bash
│ │ ├── 15-report-when-ready.lib.bash
│ │ └── skip-me-if.lib.bash
│ ├── plugin-disk-io-linux/
│ │ ├── 00-common.lib.bash
│ │ └── 01-simple.lib.bash
│ ├── plugin-file-contents-linux/
│ │ ├── 00-common.lib.bash
│ │ └── 01-simple.lib.bash
│ ├── plugin-fs/
│ │ ├── 00-common.lib.bash
│ │ ├── 01-default.lib.bash
│ │ ├── 02-root.lib.bash
│ │ ├── 03-glob-yesmatch.lib.bash
│ │ ├── 04-glob-nomatch.lib.bash
│ │ ├── 05-bad-path.lib.bash
│ │ ├── 06-wakeup-fifo.lib.bash
│ │ └── 07-dyn-paths.lib.bash
│ ├── plugin-imap/
│ │ ├── 00-common.lib.bash
│ │ ├── 01-simple.lib.bash
│ │ ├── 02-retry-timeout.lib.bash
│ │ └── 03-idle-timeout.lib.bash
│ ├── plugin-inotify/
│ │ ├── 00-common.lib.bash
│ │ ├── 01-simple.lib.bash
│ │ ├── 02-lfunc-get-initial-wds.lib.bash
│ │ ├── 03-lfunc-add-watch.lib.bash
│ │ ├── 04-lfunuc-rm-watch.lib.bash
│ │ ├── 05-timeout.lib.bash
│ │ ├── 06-dir.lib.bash
│ │ ├── 07-lfunc-push-timeout.lib.bash
│ │ ├── 08-lfunc-get-supported-events.lib.bash
│ │ └── 09-access.lib.bash
│ ├── plugin-is-program-running/
│ │ ├── 00-common.lib.bash
│ │ ├── 01-pidfile-devnull.lib.bash
│ │ ├── 02-pidfile.lib.bash
│ │ ├── 03-pidfile-fileerr.lib.bash
│ │ ├── 04-fileexists.lib.bash
│ │ ├── 05-dirnonempty-root.lib.bash
│ │ ├── 06-dirnonempty-normal-files.lib.bash
│ │ ├── 07-dirnonempty-nonexisting.lib.bash
│ │ └── 08-dirnonempty-with-hidden.lib.bash
│ ├── plugin-mem-usage-linux/
│ │ ├── 00-common.lib.bash
│ │ └── 01-simple.lib.bash
│ ├── plugin-mpd/
│ │ ├── 00-common.lib.bash
│ │ ├── 01-bad-answer.lib.bash
│ │ ├── 02-simple-template.lib.bash
│ │ ├── 03-simple.lib.bash
│ │ ├── 04-simple-bind.lib.bash
│ │ ├── 05-simple-bind-invalid.lib.bash
│ │ ├── 06-simple-bind-invalid-ipv6.lib.bash
│ │ ├── 07-keepalive.lib.bash
│ │ ├── 08-retry-in.lib.bash
│ │ ├── 09-retry-fifo.lib.bash
│ │ ├── 10-timeout.lib.bash
│ │ ├── 11-password-and-events.lib.bash
│ │ ├── 12-connect-via-unix-socket.lib.bash
│ │ ├── 13-bad-password.lib.bash
│ │ └── 14-bad-hostname.lib.bash
│ ├── plugin-multiplex/
│ │ ├── 00-common.lib.bash
│ │ ├── 01-simple.lib.bash
│ │ ├── 02-two-data-sources.lib.bash
│ │ └── 03-call-event.lib.bash
│ ├── plugin-network-linux/
│ │ ├── 00-common.lib.bash
│ │ ├── 01-simple.lib.bash
│ │ └── 02-simple-newfmt.lib.bash
│ ├── plugin-network-rate-linux/
│ │ ├── 00-common.lib.bash
│ │ ├── 01-simple.lib.bash
│ │ ├── 02-in-array-form.lib.bash
│ │ └── 03-iface-filter.lib.bash
│ ├── plugin-pipe/
│ │ ├── 00-common.lib.bash
│ │ ├── 01-simple.lib.bash
│ │ └── 02-func-shell-escape.lib.bash
│ ├── plugin-pipev2/
│ │ ├── 00-common.lib.bash
│ │ ├── 01-simple.lib.bash
│ │ ├── 02-greet.lib.bash
│ │ ├── 03-bye.lib.bash
│ │ ├── 04-write-to-stdin.lib.bash
│ │ ├── 05-kill-default.lib.bash
│ │ ├── 06-kill-by-spelling.lib.bash
│ │ ├── 07-kill-by-signo.lib.bash
│ │ └── 08-sigrt-bounds.lib.bash
│ ├── plugin-pulse/
│ │ ├── 00-common.lib.bash
│ │ ├── 01-simple.lib.bash
│ │ └── 02-lfuncs.lib.bash
│ ├── plugin-temperature-linux/
│ │ ├── 00-common.lib.bash
│ │ ├── 01-common-carcass.lib.bash
│ │ ├── 02-simple.lib.bash
│ │ ├── 03-filter-by-kind.lib.bash
│ │ └── 04-filter-by-name.lib.bash
│ ├── plugin-timer/
│ │ ├── 00-common.lib.bash
│ │ ├── 01-default.lib.bash
│ │ ├── 02-period.lib.bash
│ │ ├── 03-wakeup-fifo.lib.bash
│ │ ├── 04-lfuncs.lib.bash
│ │ ├── 05-procalive-lfuncs.lib.bash
│ │ └── 06-self-pipe.lib.bash
│ ├── plugin-unixsock/
│ │ ├── 00-common.lib.bash
│ │ ├── 01-simple.lib.bash
│ │ ├── 02-greet.lib.bash
│ │ ├── 03-do-not-try-unlink-failure.lib.bash
│ │ ├── 04-do-try-unlink-success.lib.bash
│ │ ├── 05-timeout.lib.bash
│ │ └── 06-lfuncs.lib.bash
│ └── plugin-web/
│ ├── 00-common.lib.bash
│ ├── 01-simple.lib.bash
│ ├── 02-post.lib.bash
│ ├── 03-call-cb.lib.bash
│ ├── 04-with-headers.lib.bash
│ ├── 05-json-encode.lib.bash
│ ├── 06-urlencode.lib.bash
│ ├── 07-urldecode.lib.bash
│ ├── 08-json-decode.lib.bash
│ ├── 09-json-decode-null.lib.bash
│ ├── 10-error.lib.bash
│ ├── 11-timeout.lib.bash
│ ├── 12-not-avail.lib.bash
│ ├── 13-time-now.lib.bash
│ └── 14-get-supported-opts.lib.bash
├── pulsetalker.sh
├── stopwatch.c
├── torture.sh
└── utils.lib.bash
================================================
FILE CONTENTS
================================================
================================================
FILE: .circleci/config.yml
================================================
version: 2.1
workflows:
nanodesu:
jobs:
- subetedesu:
matrix:
parameters:
luaver: ["lua5.1/liblua5.1-dev", "luajit/libluajit-5.1-dev", "lua5.2/liblua5.2-dev", "lua5.3/liblua5.3-dev"]
jobs:
subetedesu:
docker:
- image: cimg/base:2021.01-20.04
parameters:
luaver:
type: string
steps:
- checkout
- run: ./check_style.sh
- run: sudo apt-get update
- run: v=<< parameters.luaver >>; sudo apt-get install -y ${v#*/} lua-socket lua-sec python3-docutils python3-dbus cmake valgrind pulseaudio pulseaudio-utils dbus dbus-x11 jq libxcb1-dev libyajl-dev libasound2-dev libglib2.0-dev libpulse-dev libudev-dev libnl-3-dev libnl-genl-3-dev libx11-dev libxcb1-dev libxcb-ewmh-dev libxcb-icccm4-dev libxcb-util0-dev libwebsockets-dev libcurl4-gnutls-dev libcjson-dev
- run: v=<< parameters.luaver >>; cmake -DWITH_LUA_LIBRARY=${v%/*} -DBUILD_PLUGIN_PULSE=ON -DBUILD_PLUGIN_UNIXSOCK=ON -DBUILD_PLUGIN_WEB=ON -DBUILD_TESTS=ON .
- run: make -j
- run: ./tests/torture.sh .
- run: v=<< parameters.luaver >>; LUAVER=$v PLUGIN_DBUS_OPTIONAL=1 ./.circleci/run-pt.sh
================================================
FILE: .circleci/run-pt.sh
================================================
#!/usr/bin/env bash
set -e
atexit=()
trap '
# We need to run finalizers in reversed order.
for (( i = ${#atexit[@]} - 1; i >= 0; --i )); do
func=${atexit[$i]}
$func
done
' EXIT
pa_start() {
atexit+=(pa_end)
pulseaudio --daemonize=no --disallow-exit=yes --exit-idle-time=-1 &
while ! pactl info; do
echo >&2 "Waiting for PulseAudio daemon..."
sleep 1
done
}
pa_end() {
pulseaudio --kill || true
}
dbus_start() {
DBUS_DAEMON_PID=
atexit+=(dbus_end)
local addr_file=/tmp/dbus-session-addr.txt
true > "$addr_file" || return $?
dbus-daemon --session --nofork --print-address=3 3>"$addr_file" &
DBUS_DAEMON_PID=$!
local x
while ! IFS= read -r x < "$addr_file"; do
echo >&2 "Waiting for D-Bus daemon..."
sleep 1 || return $?
done
rm -f "$addr_file" || return $?
export DBUS_SESSION_BUS_ADDRESS="$x"
}
dbus_end() {
if [[ -n "$DBUS_DAEMON_PID" ]]; then
kill "$DBUS_DAEMON_PID" || true
fi
}
dbus_start
sleep 1
pa_start
#PT_TOOL=valgrind PT_MAX_LAG=250 ./tests/pt.sh .
PT_MAX_LAG=300 ./tests/pt.sh .
================================================
FILE: .gitignore
================================================
*.o
*.so
/_gitignored/
# CMake
CMakeCache.txt
CMakeFiles
CMakeScripts
Makefile
cmake_install.cmake
install_manifest.txt
CTestTestfile.cmake
build/
# generated headers
*.generated.[ch]
# generated man pages
luastatus*.[1-8]
================================================
FILE: .luacheckrc
================================================
new_globals = {'widget'}
new_read_globals = {'luastatus'}
ignore = {
'631', -- "Line is too long"
}
================================================
FILE: CMakeLists.txt
================================================
cmake_minimum_required (VERSION 3.1.3...3.10)
project (luastatus C)
if (NOT CMAKE_BUILD_TYPE)
set (CMAKE_BUILD_TYPE Release)
endif ()
set (CMAKE_C_STANDARD 99)
set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra")
#------------------------------------------------------------------------------
option (WITH_UBSAN "build with UBSAN (undefined behavior sanitizer)" OFF)
if (${WITH_UBSAN})
set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize=undefined -fsanitize-trap=all -fno-sanitize-recover=all")
if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize=local-bounds")
endif ()
endif ()
option (WITH_ASAN "build with ASAN (address sanitizer)" OFF)
if (${WITH_ASAN})
set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize=address -fsanitize-address-use-after-scope -fsanitize-trap=all -fno-sanitize-recover=all")
endif ()
option (WITH_TSAN "build with TSAN (thread sanitizer)" OFF)
if (${WITH_TSAN})
set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize=thread -fsanitize-trap=all -fno-sanitize-recover=all")
endif ()
option (WITH_LSAN "build with LSAN (memory leak sanitizer)" OFF)
if (${WITH_LSAN})
set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize=leak -fsanitize-trap=all -fno-sanitize-recover=all")
endif ()
#------------------------------------------------------------------------------
find_package (PkgConfig REQUIRED)
set (WITH_LUA_LIBRARY "-" CACHE STRING "Lua library name")
if (NOT "${WITH_LUA_LIBRARY}" STREQUAL "-")
pkg_search_module (LUA REQUIRED "${WITH_LUA_LIBRARY}")
else ()
pkg_search_module (LUA REQUIRED
lua54 lua-5.4 lua5.4
lua53 lua-5.3 lua5.3
lua52 lua-5.2 lua5.2
lua51 lua-5.1 lua5.1
luajit
lua)
endif ()
function (luastatus_target_compile_with target var)
target_include_directories (${target} SYSTEM PUBLIC
${${var}_INCLUDE_DIRS})
target_compile_options (${target} PUBLIC
${${var}_CFLAGS_OTHER})
endfunction ()
function (luastatus_target_build_with target var)
luastatus_target_compile_with ("${target}" "${var}")
target_link_libraries (${target} PUBLIC ${${var}_LIBRARIES})
endfunction ()
#------------------------------------------------------------------------------
include (GNUInstallDirs)
set (BARLIBS_DIR "${CMAKE_INSTALL_FULL_LIBDIR}/luastatus/barlibs")
set (PLUGINS_DIR "${CMAKE_INSTALL_FULL_LIBDIR}/luastatus/plugins")
set (LUA_PLUGINS_DIR "${CMAKE_INSTALL_FULL_DATAROOTDIR}/luastatus/plugins")
function (luastatus_add_barlib_or_plugin destdir name)
set (sources ${ARGV})
list (REMOVE_AT sources 0 1)
add_library ("${name}" MODULE ${sources})
set_target_properties ("${name}" PROPERTIES PREFIX "")
if (NOT "${destdir}" STREQUAL "-")
install (TARGETS "${name}" DESTINATION "${destdir}")
endif ()
endfunction ()
function (luastatus_add_barlib)
luastatus_add_barlib_or_plugin ("${BARLIBS_DIR}" ${ARGV})
endfunction ()
function (luastatus_add_plugin)
luastatus_add_barlib_or_plugin ("${PLUGINS_DIR}" ${ARGV})
endfunction ()
function (luastatus_add_barlib_noinstall)
luastatus_add_barlib_or_plugin ("-" ${ARGV})
endfunction ()
function (luastatus_add_plugin_noinstall)
luastatus_add_barlib_or_plugin ("-" ${ARGV})
endfunction ()
option (BUILD_DOCS "build man pages" ON)
function (luastatus_add_man_page src basename section)
if (NOT BUILD_DOCS)
return ()
endif ()
set (dest "${basename}.${section}")
add_custom_command (
OUTPUT "${dest}"
COMMAND "${PROJECT_SOURCE_DIR}/generate-man.sh" ARGS "${CMAKE_CURRENT_SOURCE_DIR}/${src}" "${dest}"
WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}"
MAIN_DEPENDENCY "${src}"
VERBATIM)
add_custom_target ("man${section}-${basename}" ALL DEPENDS "${dest}")
install (
FILES "${CMAKE_CURRENT_BINARY_DIR}/${dest}"
DESTINATION "${CMAKE_INSTALL_MANDIR}/man${section}")
endfunction ()
#------------------------------------------------------------------------------
add_subdirectory (libls)
add_subdirectory (libsafe)
add_subdirectory (libhackyfix)
add_subdirectory (librunshell)
add_subdirectory (libmoonvisit)
add_subdirectory (libwidechar)
add_subdirectory (libprocalive)
add_subdirectory (luastatus)
#------------------------------------------------------------------------------
macro (DEF_OPT optname subdir defvalue)
option (${optname} "build ${subdir}" ${defvalue})
if (${optname})
add_subdirectory (${subdir})
endif ()
endmacro ()
DEF_OPT (BUILD_BARLIB_DWM "barlibs/dwm" ON)
DEF_OPT (BUILD_BARLIB_I3 "barlibs/i3" ON)
DEF_OPT (BUILD_BARLIB_LEMONBAR "barlibs/lemonbar" ON)
DEF_OPT (BUILD_BARLIB_STDOUT "barlibs/stdout" ON)
DEF_OPT (BUILD_PLUGIN_ALSA "plugins/alsa" ON)
DEF_OPT (BUILD_PLUGIN_BACKLIGHT_LINUX "plugins/backlight-linux" ON)
DEF_OPT (BUILD_PLUGIN_BATTERY_LINUX "plugins/battery-linux" ON)
DEF_OPT (BUILD_PLUGIN_CPU_FREQ_LINUX "plugins/cpu-freq-linux" ON)
DEF_OPT (BUILD_PLUGIN_CPU_USAGE_LINUX "plugins/cpu-usage-linux" ON)
DEF_OPT (BUILD_PLUGIN_DBUS "plugins/dbus" ON)
DEF_OPT (BUILD_PLUGIN_DISK_IO_LINUX "plugins/disk-io-linux" ON)
DEF_OPT (BUILD_PLUGIN_FILE_CONTENTS_LINUX "plugins/file-contents-linux" ON)
DEF_OPT (BUILD_PLUGIN_FS "plugins/fs" ON)
DEF_OPT (BUILD_PLUGIN_IMAP "plugins/imap" ON)
DEF_OPT (BUILD_PLUGIN_INOTIFY "plugins/inotify" ON)
DEF_OPT (BUILD_PLUGIN_IS_PROGRAM_RUNNING "plugins/is-program-running" ON)
DEF_OPT (BUILD_PLUGIN_MEM_USAGE_LINUX "plugins/mem-usage-linux" ON)
DEF_OPT (BUILD_PLUGIN_MPD "plugins/mpd" ON)
DEF_OPT (BUILD_PLUGIN_MPRIS "plugins/mpris" ON)
DEF_OPT (BUILD_PLUGIN_MULTIPLEX "plugins/multiplex" ON)
DEF_OPT (BUILD_PLUGIN_NETWORK_LINUX "plugins/network-linux" ON)
DEF_OPT (BUILD_PLUGIN_NETWORK_RATE_LINUX "plugins/network-rate-linux" ON)
DEF_OPT (BUILD_PLUGIN_PIPE "plugins/pipe" OFF)
DEF_OPT (BUILD_PLUGIN_PIPEV2 "plugins/pipev2" ON)
DEF_OPT (BUILD_PLUGIN_PULSE "plugins/pulse" OFF)
DEF_OPT (BUILD_PLUGIN_SYSTEMD_UNIT "plugins/systemd-unit" OFF)
DEF_OPT (BUILD_PLUGIN_TEMPERATURE_LINUX "plugins/temperature-linux" ON)
DEF_OPT (BUILD_PLUGIN_TIMER "plugins/timer" ON)
DEF_OPT (BUILD_PLUGIN_UDEV "plugins/udev" ON)
DEF_OPT (BUILD_PLUGIN_UNIXSOCK "plugins/unixsock" OFF)
DEF_OPT (BUILD_PLUGIN_WEB "plugins/web" OFF)
DEF_OPT (BUILD_PLUGIN_XKB "plugins/xkb" ON)
DEF_OPT (BUILD_PLUGIN_XTITLE "plugins/xtitle" ON)
DEF_OPT (BUILD_TESTS "tests" OFF)
================================================
FILE: COPYING.LESSER.txt
================================================
GNU LESSER GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc.
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
This version of the GNU Lesser General Public License incorporates
the terms and conditions of version 3 of the GNU General Public
License, supplemented by the additional permissions listed below.
0. Additional Definitions.
As used herein, "this License" refers to version 3 of the GNU Lesser
General Public License, and the "GNU GPL" refers to version 3 of the GNU
General Public License.
"The Library" refers to a covered work governed by this License,
other than an Application or a Combined Work as defined below.
An "Application" is any work that makes use of an interface provided
by the Library, but which is not otherwise based on the Library.
Defining a subclass of a class defined by the Library is deemed a mode
of using an interface provided by the Library.
A "Combined Work" is a work produced by combining or linking an
Application with the Library. The particular version of the Library
with which the Combined Work was made is also called the "Linked
Version".
The "Minimal Corresponding Source" for a Combined Work means the
Corresponding Source for the Combined Work, excluding any source code
for portions of the Combined Work that, considered in isolation, are
based on the Application, and not on the Linked Version.
The "Corresponding Application Code" for a Combined Work means the
object code and/or source code for the Application, including any data
and utility programs needed for reproducing the Combined Work from the
Application, but excluding the System Libraries of the Combined Work.
1. Exception to Section 3 of the GNU GPL.
You may convey a covered work under sections 3 and 4 of this License
without being bound by section 3 of the GNU GPL.
2. Conveying Modified Versions.
If you modify a copy of the Library, and, in your modifications, a
facility refers to a function or data to be supplied by an Application
that uses the facility (other than as an argument passed when the
facility is invoked), then you may convey a copy of the modified
version:
a) under this License, provided that you make a good faith effort to
ensure that, in the event an Application does not supply the
function or data, the facility still operates, and performs
whatever part of its purpose remains meaningful, or
b) under the GNU GPL, with none of the additional permissions of
this License applicable to that copy.
3. Object Code Incorporating Material from Library Header Files.
The object code form of an Application may incorporate material from
a header file that is part of the Library. You may convey such object
code under terms of your choice, provided that, if the incorporated
material is not limited to numerical parameters, data structure
layouts and accessors, or small macros, inline functions and templates
(ten or fewer lines in length), you do both of the following:
a) Give prominent notice with each copy of the object code that the
Library is used in it and that the Library and its use are
covered by this License.
b) Accompany the object code with a copy of the GNU GPL and this license
document.
4. Combined Works.
You may convey a Combined Work under terms of your choice that,
taken together, effectively do not restrict modification of the
portions of the Library contained in the Combined Work and reverse
engineering for debugging such modifications, if you also do each of
the following:
a) Give prominent notice with each copy of the Combined Work that
the Library is used in it and that the Library and its use are
covered by this License.
b) Accompany the Combined Work with a copy of the GNU GPL and this license
document.
c) For a Combined Work that displays copyright notices during
execution, include the copyright notice for the Library among
these notices, as well as a reference directing the user to the
copies of the GNU GPL and this license document.
d) Do one of the following:
0) Convey the Minimal Corresponding Source under the terms of this
License, and the Corresponding Application Code in a form
suitable for, and under terms that permit, the user to
recombine or relink the Application with a modified version of
the Linked Version to produce a modified Combined Work, in the
manner specified by section 6 of the GNU GPL for conveying
Corresponding Source.
1) Use a suitable shared library mechanism for linking with the
Library. A suitable mechanism is one that (a) uses at run time
a copy of the Library already present on the user's computer
system, and (b) will operate properly with a modified version
of the Library that is interface-compatible with the Linked
Version.
e) Provide Installation Information, but only if you would otherwise
be required to provide such information under section 6 of the
GNU GPL, and only to the extent that such information is
necessary to install and execute a modified version of the
Combined Work produced by recombining or relinking the
Application with a modified version of the Linked Version. (If
you use option 4d0, the Installation Information must accompany
the Minimal Corresponding Source and Corresponding Application
Code. If you use option 4d1, you must provide the Installation
Information in the manner specified by section 6 of the GNU GPL
for conveying Corresponding Source.)
5. Combined Libraries.
You may place library facilities that are a work based on the
Library side by side in a single library together with other library
facilities that are not Applications and are not covered by this
License, and convey such a combined library under terms of your
choice, if you do both of the following:
a) Accompany the combined library with a copy of the same work based
on the Library, uncombined with any other library facilities,
conveyed under the terms of this License.
b) Give prominent notice with the combined library that part of it
is a work based on the Library, and explaining where to find the
accompanying uncombined form of the same work.
6. Revised Versions of the GNU Lesser General Public License.
The Free Software Foundation may publish revised and/or new versions
of the GNU Lesser General Public License from time to time. Such new
versions will be similar in spirit to the present version, but may
differ in detail to address new problems or concerns.
Each version is given a distinguishing version number. If the
Library as you received it specifies that a certain numbered version
of the GNU Lesser General Public License "or any later version"
applies to it, you have the option of following the terms and
conditions either of that published version or of any later version
published by the Free Software Foundation. If the Library as you
received it does not specify a version number of the GNU Lesser
General Public License, you may choose any version of the GNU Lesser
General Public License ever published by the Free Software Foundation.
If the Library as you received it specifies that a proxy can decide
whether future versions of the GNU Lesser General Public License shall
apply, that proxy's public statement of acceptance of any version is
permanent authorization for you to choose that version for the
Library.
================================================
FILE: COPYING.txt
================================================
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc.
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The GNU General Public License is a free, copyleft license for
software and other kinds of works.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
the GNU General Public License is intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users. We, the Free Software Foundation, use the
GNU General Public License for most of our software; it applies also to
any other work released this way by its authors. You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
To protect your rights, we need to prevent others from denying you
these rights or asking you to surrender the rights. Therefore, you have
certain responsibilities if you distribute copies of the software, or if
you modify it: responsibilities to respect the freedom of others.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received. You must make sure that they, too, receive
or can get the source code. And you must show them these terms so they
know their rights.
Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.
For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software. For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
authors of previous versions.
Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the manufacturer
can do so. This is fundamentally incompatible with the aim of
protecting users' freedom to change the software. The systematic
pattern of such abuse occurs in the area of products for individuals to
use, which is precisely where it is most unacceptable. Therefore, we
have designed this version of the GPL to prohibit the practice for those
products. If such problems arise substantially in other domains, we
stand ready to extend this provision to those domains in future versions
of the GPL, as needed to protect the freedom of users.
Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish to
avoid the special danger that patents applied to a free program could
make it effectively proprietary. To prevent this, the GPL assures that
patents cannot be used to render the program non-free.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Use with the GNU Affero General Public License.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU Affero General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the
combination as such.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
Copyright (C)
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:
Copyright (C)
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, your program's commands
might be different; for a GUI interface, you would use an "about box".
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see
.
The GNU General Public License does not permit incorporating your program
into proprietary programs. If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with
the library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License. But first, please read
.
================================================
FILE: DEPENDS.txt
================================================
luastatus itself, without any barlibs and/or plugins, has the following dependencies:
* docutils >=0.11 (rst2man program)
* CMake >=3.1.3
* pkg-config >=0.25
* either Lua 5.1, Lua 5.2, Lua 5.3, Lua 5.4, or LuaJIT >=2.0.0
For running tests, you will also need:
* valgrind (some older versions don't like LuaJIT; >=3.11.0 are known to be OK)
* For 'i3' barlib tests: 'jq' binary
* For 'dbus' plugin tests: a D-Bus daemon running in session mode; 'dbus-send' binary
* For 'pulse' plugin tests: a running PulseAudio daemon; 'pactl' and 'pacmd' binaries
* For 'web' plugin tests: 'libwebsockets' library (to build 'httpserv' program)
Barlib 'dwm' has the following dependencies:
* xcb >=1.10
Barlib 'i3' has the following dependencies:
* yajl >=2.0.4
Plugin 'alsa' has the following dependencies:
* alsa >=1.0.27.2
Plugin 'backlight-linux' has the following dependencies:
* plugin 'udev'
Plugin 'battery-linux' has the following dependencies:
* plugin 'udev'
Plugin 'cpu-freq-linux' has the following dependencies:
* plugin 'timer'
Plugin 'cpu-usage-linux' has the following dependencies:
* plugin 'timer'
Plugin 'dbus' has the following dependencies:
* glib-2.0 >=2.40.2
* gio-2.0 >=2.40.2
Plugin 'disk-io-linux' has the following dependencies:
* plugin 'timer'
Plugin 'file-contents-linux' has the following dependencies:
* plugin 'inotify'
Plugin 'imap' has the following dependencies:
* LuaSocket library for the Lua version that luastatus was built with
* LuaSec library for the Lua version that luastatus was built with
Plugin 'is-program-running' has the following dependencies:
* plugin 'timer'
Plugin 'inotify' has the following dependencies:
* a Linux system with a libc that provides (preferably glibc)
Plugin 'mem-usage-linux' has the following dependencies:
* plugin 'timer'
Plugin 'mpris' has the following dependencies:
* plugin 'dbus'
Plugin 'network-linux' has the following dependencies:
* a Linux system with Linux kernel headers
* libnl >=3.0
* libnl-genl >=3.0
Plugin 'network-rate-linux' has the following dependencies:
* plugin 'timer'
Plugin 'pipe' has the following dependencies:
* plugin 'timer'
Plugin 'pulse' has the following dependencies:
* libpulse >=4.0
Plugin 'systemd-unit' has the following dependencies:
* plugin 'dbus'
Plugin 'temperature-linux' has the following dependencies:
* plugin 'timer'
Plugin 'udev' has the following dependencies:
* libudev >=204
Plugin 'web' has the following dependencies:
* cJSON >=1.7.10
* libcurl >=7.8
Plugin 'xkb' has the following dependencies:
* x11 >=1.6.2
Plugin 'xtitle' has the following dependencies:
* xcb >=1.10
* xcb-ewmh >=0.4.1
* xcb-icccm >=0.4.1
* xcb-event >=0.3.8
================================================
FILE: DOCS/CUSTOM_DATA_SRC_WIDGET.md
================================================
Writing a widget that uses custom data source
===
A “custom data source” widget is a fancy way to refer to a widget that wants to wait for events
and/or receive data using some blocking Lua operation (most commonly using some external Lua
module), rather than using some luastatus plugin.
The luastatus project explicitly supports this flow; in fact, the “separate state” thing is there
exactly to help such widgets react to events timely.
The basic mechanism is (details may vary for different widgets):
* Specify `timer` as a plugin, with `period = 0` in plugin options (`opts`).
* Do the following in the callback function (`cb`):
+ wait for next event (unless this is the first time `cb` is called);
+ generate data for barlib and return it.
* If need to handle events, use separate-state event handler.
Tricks to know about
---
* Use `luastatus.plugin.push_period(seconds)` function of the `timer` plugin to force it to sleep
for a specified number of seconds after barlib receives your data. Note that this is a
“*push* timeout” operation, not “*set* timeout”, meaning that it will only be respected for a single
iteration, and then “popped” (forgotten).
* Use the argument to `cb` provided by the `timer` plugin to tell if this is the first time `cb`
is called: the argument is a string, and it is `"hello"` if and only if this is the first time.
* The `timer` plugin supports wakeup FIFO. This might be useful if `push_period` is used in case of
error, as a way to force an earlier re-try. The FIFO may be touched from inside the event handler,
or from anywhere else.
List of derived plugins using custom data sources
---
* imap;
* pipe.
List of widget examples using custom data sources
---
* btc-price;
* gmail;
* update-on-click;
* weather.
================================================
FILE: DOCS/MIGRATION_GUIDE.md
================================================
0.2.0 to 0.3.0
===
* Semantics of the `greet` option of `inotify` plugin has changed;
see `plugins/inotify/README.md` for details.
0.1.0 to 0.2.0
===
* `luastatus.spawn`, `luastatus.rc` and `luastatus.dollar` functions have been removed.
Use `os.execute` and/or `io.popen` instead — and please don’t forget to properly escape the arguments:
````lua
function shell_escape(s)
return "'" .. s:gsub("'", "'\\''") .. "'"
end
````
* `pipe` plugin has been removed. Use the `timer` plugin and `io.open` instead:
````lua
f = io.popen('your command', 'r')
wdiget = {
plugin = 'timer',
cb = function()
local line = f:read('*line')
-- ...
end,
}
````
================================================
FILE: DOCS/WRITING_BARLIB_OR_PLUGIN.md
================================================
Thread-safety
===
Each non-thread-safe thing must be synchronized with other entities by means of the `map_get`
function (see `DOCS/design/map_get.md`).
Environment variables
===
Your plugin or barlib must not modify environment variables (this includes `unsetenv()`, `setenv()`,
`putenv()`, and modifying `environ`).
Signals
===
Your plugin or barlib can install signal handlers, but:
1. this must be done with `sigaction()`, and `sa_mask` field of `struct sigaction` must include
`SA_RESTART`;
2. use the `map_get` facility to ensure there are no conflicting handlers installed; use, for
example, the key of `"flag:signal_handled:SIGUSR1"` for signal `SIGUSR1`; the value behind the
key should be checked to be null and then set to some non-null pointer. For POSIX real-time
signals, use `"SIGRTMIN+%d"` nomenclature, where `%d` is a non-negative offset in decimal.
Your plugin or barlib can call `pthread_sigmask()` (and, consequently, `pselect()`).
Writing a plugin
===
Copy `include/plugin_data.h`, `include/plugin_data_v1.h`, `include/plugin_v1.h` and `include/common.h`;
include `include/plugin_v1.h` and start reading `include/plugin_data.h`.
Then, declare a global `const LuastatusIfacePlugin luastatus_iface_plugin_v1` variable.
Writing a barlib
===
Copy `include/barlib_data.h`, `include/barlib_data_v1.h`, `include/barlib_v1.h` and `include/common.h`;
include `include/barlib_v1.h` and start reading `include/barlib_data.h`.
Then, declare a global `const LuastatusIfaceBarlib luastatus_iface_barlib_v1` variable.
================================================
FILE: DOCS/c_notes/eintr-policy.md
================================================
The main problem is that all the stdio functions may, according to POSIX, fail with `EINTR`, and there is no way to restart some of them, e.g. `fprintf`.
Our solution is to ensure that `SA_RESTART` is set for all the signals that could be raised (without terminating the program).
See also: http://man7.org/linux/man-pages/man7/signal.7.html, section "Interruption of system calls and library functions by signal handlers".
================================================
FILE: DOCS/c_notes/empty-ranges-and-c-stdlib.md
================================================
http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1256.pdf
Subclause 7.1.4 Use of library functions, point 1:
> Each of the following statements applies unless explicitly stated otherwise in the
> detailed descriptions that follow: If an argument to a function has an invalid value (such
> as a value outside the domain of the function, or a pointer outside the address space of
> the program, or a null pointer, or a pointer to non-modifiable storage when the
> corresponding parameter is not const-qualified) or a type (after promotion) not expected
> by a function with variable number of arguments, the behavior is undefined. If a function
> argument is described as being an array, the pointer actually passed to the function shall
> have a value such that all address computations and accesses to objects (that would be
> valid if the pointer did point to the first element of such an array) are in fact valid.
It’s not OK to call any function defined in `` with a pointer that can’t be dereferenced:
Subclause 7.21.1 String handling conventions, point 2:
> Where an argument declared as `size_t n` specifies the length of the array for a function, `n`
> can have the value zero on a call to that function. Unless explicitly stated otherwise in
> the description of a particular function in this subclause, pointer arguments on such a
> call shall still have valid values, as described in 7.1.4. On such a call, a function
> that locates a character finds no occurrence, a function that compares two character
> sequences returns zero, and a function that copies characters copies zero characters.
It’s not OK to call `qsort`/`bsearch` with a pointer that can’t be dereferenced:
Subclause 7.20.5 Searching and sorting utilites, point 1:
> These utilities make use of a comparison function to search or sort arrays of unspecified
> type. Where an argument declared as `size_t nmemb` specifies the length of the array for a
> function, `nmemb` can have the value zero on a call to that function; the comparison function
> is not called, a search finds no matching element, and sorting performs no rearrangement.
> Pointer arguments on such a call shall still have valid values, as described in 7.1.4.
However, it’s OK to call `snprintf`/`vsnprintf` with a pointer that can’t be dereferenced, as long as `n` is zero:
Subclause 7.19.6.5 The `snprintf` function, point 2:
> The `snprintf` function is equivalent to `fprintf`, except that the output is written into an
> array (specified by argument `s`) rather than to a stream. If `n` is zero, nothing is written, and
> `s` may be a null pointer. Otherwise, output characters beyond the `n-1`st are discarded rather
> than being written to the array, and a null character is written at the end of the characters
> actually written into the array. If copying takes place between objects that overlap, the behavior
> is undefined.
================================================
FILE: DOCS/design/locking-patterns.md
================================================
The list of locking patterns of the main luastatus binary; run
grep -Ew '(UN)?LOCK_[A-Z]' luastatus/luastatus.c
to verify it is up-to-date.
It can be said that our order is: E < L < B.
We also don't lock the same mutex twice in any of the “procedures”.
This suffices to say there are no deadlocks.
(We also have the `tests/torture.sh` test!)
cb-gets-called() {
lock L
lock B
unlock B
unlock L
}
plugin-begins-call-and-cancels() {
lock L
unlock L
}
#-----------------------------------------------
event-gets-called-and-raises-error-E() {
lock E
lock B
unlock B
unlock E
}
events-gets-called-and-succeeds-E() {
lock E
unlock E
}
barlib-ew-begins-call-and-cancels-E() {
lock E
unlock E
}
#-----------------------------------------------
event-gets-called-and-raises-error-L() {
lock L
lock B
unlock B
unlock L
}
events-gets-called-and-succeeds-L() {
lock L
unlock L
}
barlib-ew-begins-call-and-cancels-L() {
lock L
unlock L
}
#-----------------------------------------------
set-error-when-plugin-run-returned() {
lock B
unlock B
}
set-error-when-widget-init-failed() {
lock B
unlock B
}
================================================
FILE: DOCS/design/map_get.md
================================================
Overview
===
luastatus provides the following function to barlibs and plugins:
```c
void ** (*map_get)(void *userdata, const char *key);
```
This function is **not** thread-safe and should only be used in the `init` function.
luastatus maintains a global mapping from zero-terminated strings to pointers (`void *`).
`map_get` returns a pointer to the pointer corresponding to the given key; if a map entry with the given key does not exist, it creates one with a null pointer value.
You can read and/or write from/to this pointer-to-pointer; it is guaranteed to be persistent across other calls to `map_get` and other functions.
Its intended use is for synchronization.
================================================
FILE: README.md
================================================
[](https://circleci.com/gh/shdown/luastatus)

**luastatus** is a universal status bar content generator. It allows you to configure the way the
data from event sources is processed and shown, with Lua.
Its main feature is that the content can be updated immediately as some event occurs, be it a change
of keyboard layout, active window title, volume or a song in your favorite music player (provided
that there is a plugin for it) — a thing rather uncommon for tiling window managers.
Its motto is:
> No more heavy-forking, second-lagging shell-script status bar generators!
Screenshot
===

Above is i3bar with luastatus with Bitcoin price, time, volume, and keyboard layout widgets.
Key concepts
===

In short:
* plugin is a thing that decides when to call the callback function `widget.cb` and what to pass to it;
* barlib (**bar** **lib**rary) is a thing that decides what to with values that `widget.cb` function returns;
* there are also *derived plugins*, which are plugins written in Lua that use regular plugins.
Examples
===
ALSA volume widget:
```lua
widget = {
plugin = 'alsa',
opts = {
channel = 'PCM'
},
cb = function(t)
if t.mute then
return {full_text = '[mute]', color = '#e03838'}
else
local percent = (t.vol.cur - t.vol.min)
/ (t.vol.max - t.vol.min)
* 100
return {full_text = string.format('[%3d%%]', math.floor(0.5 + percent)),
color = '#718ba6'}
end
end,
event = function(t)
if t.button == 1 then -- left mouse button
os.execute('urxvt -e alsamixer &')
end
end
}
```
GMail widget (uses the derived plugin `imap`):
```lua
--[[
-- Expects 'credentials.lua' to be present in the current directory; it may contain, e.g.,
-- return {
-- gmail = {
-- login = 'john.smith',
-- password = 'qwerty'
-- }
-- }
--]]
credentials = require 'credentials'
widget = luastatus.require_plugin('imap').widget{
host = 'imap.gmail.com',
port = 993,
mailbox = 'Inbox',
use_ssl = true,
timeout = 2 * 60,
handshake_timeout = 10,
login = credentials.gmail.login,
password = credentials.gmail.password,
error_sleep_period = 60,
cb = function(unseen)
if unseen == nil then
return nil
elseif unseen == 0 then
return {full_text = '[-]', color = '#595959'}
else
return {full_text = string.format('[%d unseen]', unseen)}
end
end,
event = [[ -- separate-state event function
local t = ... -- obtain argument of this implicit function
if t.button == 1 then -- left mouse button
os.execute('xdg-open https://gmail.com &')
end
]]
}
```
See more examples [here](https://github.com/shdown/luastatus/tree/master/examples).
Installation
===
`cmake . && make && sudo make install`
You can specify a Lua library to build with: `cmake -DWITH_LUA_LIBRARY=luajit .`
You can disable building certain barlibs and plugins, e.g. `cmake -DBUILD_PLUGIN_XTITLE=OFF .`
You can disable building man pages: `cmake -DBUILD_DOCS=OFF .`
ArchLinux
---
ArchLinux users can use one of the following AUR packages:
* [luastatus](https://aur.archlinux.org/packages/luastatus)
* [luastatus-luajit](https://aur.archlinux.org/packages/luastatus-luajit)
* [luastatus-git](https://aur.archlinux.org/packages/luastatus-git)
* [luastatus-luajit-git](https://aur.archlinux.org/packages/luastatus-luajit-git)
There is also the [luastatus-meta](https://aur.archlinux.org/packages/luastatus-meta)
meta package which installs the dependencies of all of luastatus's plugins.
Getting started
===
It is recommended to first have a look at the
[luastatus' man page](https://github.com/shdown/luastatus/blob/master/luastatus/README.rst).
Then, read the barlib's and plugins' documentation, either via directly viewing
`barlibs//README.rst` and `plugins//README.rst` files, or via installing the man pages
and reading `luastatus-barlib-(7)` and `luastatus-plugin-(7)`.
Barlib-specific notes on usage follow.
i3 or sway
----------
`luastatus-i3-wrapper` should be specified as the i3bar's or sway-bar's status command in the config, e.g.:
```
bar {
status_command cd ~/.config/luastatus && exec luastatus-i3-wrapper -B no_separators time-battery-combined.lua alsa.lua xkb.lua
```
Since sway-bar is format-compatible with i3wm, the exact same configuration works for both.
See also [README for i3](https://github.com/shdown/luastatus/blob/master/barlibs/i3/README.rst) and
[examples for i3](https://github.com/shdown/luastatus/tree/master/examples/i3).
dwm
---
luastatus should simply be launched with `-b dwm`, e.g.:
```
luastatus -b dwm -B separator=' • ' alsa.lua time-battery-combined.lua
```
See also [README for dwm](https://github.com/shdown/luastatus/blob/master/barlibs/dwm/README.rst)
and [examples for dwm](https://github.com/shdown/luastatus/tree/master/examples/dwm).
lemonbar
--------
`lemonbar` should be launched with `luastatus-lemonbar-launcher`, e.g.:
```
luastatus-lemonbar-launcher -p -B#111111 -p -f'Droid Sans Mono for Powerline:pixelsize=12:weight=Bold' -- -Bseparator=' ' alsa.lua time-date.lua
```
See also
[README for lemonbar](https://github.com/shdown/luastatus/blob/master/barlibs/lemonbar/README.rst)
and [examples for lemonbar](https://github.com/shdown/luastatus/tree/master/examples/lemonbar).
stdout
------
luastatus should be launched with `luastatus-stdout-wrapper`; or write your own wrapper, see e.g.
the [wrapper for launching dvtm with luastatus](https://github.com/shdown/luastatus/blob/master/barlibs/stdout/luastatus-dvtm).
See also
[README for stdout](https://github.com/shdown/luastatus/blob/master/barlibs/stdout/README.rst) and
and [examples for stdout](https://github.com/shdown/luastatus/tree/master/examples/stdout).
Supported Lua versions
===
* 5.1
* LuaJIT, which is currently 5.1-compatible with "some language and library extensions from Lua 5.2"
* 5.2
* 5.3
* 5.4
* 5.5
Reporting bugs, requesting features, suggesting patches
===
Feel free to open an issue or a pull request.
Migrating from older versions
===
See the [Migration Guide](https://github.com/shdown/luastatus/blob/master/DOCS/MIGRATION_GUIDE.md).
Testing
===
Here, at luastatus, we take code correctness and safety very seriously.
We do the following things:
* We use best practices for C programming:
- All non-trivial string processing goes through libsafe
- We mark printf-like functions with appropriate attributes in order to get warnings on illegal format string or wrong argument types
- We handle every error possible (except for cases where we can't, and don't want to, do anything about an error)
- We pay attention to integer overflows/underflows, conversion of integers to/from floating-point types, and other possible cases of undefined behavior
- We use typedef'd enum types instead of "untyped" integers to represent enum's values so that we can get warnings when a switch does not account for some value
- We use macros for (re)allocations that make it impossible to get types wrong
* We compile with `-Wall -Wextra`
* We have a comprehensive test suite; it contains tests for luastatus, barlibs and plugins, and also includes "torture"-style tests (a.k.a. stress tests)
which bombard luastatus with a lot of events from a plugin and a barlib simultaneously:
* It passes under valgrind [memcheck tool]
* It passes under valgrind [helgrind tool]
* It passes under UBSAN (Undefined Behavior Sanitizer)
* It passes under ASAN (Address Sanitizer)
* It passes under LSAN (Leak Sanitizer)
* It passes under TSAN (Thread Sanitizer)
* Everything that theoretically can be an attack surface is fuzzed, under UBSAN, with both AFL and AFL++; we also have a sophisticated fuzz testcase generator (in `fuzz_utils/gen_testcases/`)
We tried to use additional compiler warnings, `-fanalyzer`, and external linters/static analyzers, but
these tools only gave false positives, except for PVS-Studio, which found one actual bug, but with a lot of false positives.
So we don't use any of these on a regular basis.
================================================
FILE: barlibs/dwm/CMakeLists.txt
================================================
file (GLOB sources "*.c")
luastatus_add_barlib (barlib-dwm $ ${sources})
target_compile_definitions (barlib-dwm PUBLIC -D_POSIX_C_SOURCE=200809L)
luastatus_target_compile_with (barlib-dwm LUA)
target_include_directories (barlib-dwm PUBLIC "${PROJECT_SOURCE_DIR}")
find_package (PkgConfig REQUIRED)
pkg_check_modules (XCB REQUIRED xcb)
luastatus_target_build_with (barlib-dwm XCB)
luastatus_add_man_page (README.rst luastatus-barlib-dwm 7)
================================================
FILE: barlibs/dwm/README.rst
================================================
.. :X-man-page-only: luastatus-barlib-dwm
.. :X-man-page-only: #####################
.. :X-man-page-only:
.. :X-man-page-only: ##########################
.. :X-man-page-only: dwm barlib for luastatus
.. :X-man-page-only: ##########################
.. :X-man-page-only:
.. :X-man-page-only: :Copyright: LGPLv3
.. :X-man-page-only: :Manual section: 7
Overview
========
This barlib updates the name of the root window.
It joins all non-empty strings returned by widgets by a separator, which defaults to ``" | "``.
It does not provide functions and does not support events.
``cb`` return value
===================
Either of:
* a string
An empty string hides the widget.
* an array of strings
Equivalent to returning a string with all non-empty elements of the array joined by the separator.
* ``nil``
Hides the widget.
Options
=======
The following options are supported:
* ``display=``
Set the name of a display to connect to. Default is to use ``DISPLAY`` environment variable.
* ``separator=``
Set the separator.
================================================
FILE: barlibs/dwm/dwm.c
================================================
/*
* Copyright (C) 2015-2025 luastatus developers
*
* This file is part of luastatus.
*
* luastatus is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* luastatus is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with luastatus. If not, see .
*/
#include "include/barlib_v1.h"
#include "include/sayf_macros.h"
#include
#include
#include
#include
#include
#include
#include "libls/ls_alloc_utils.h"
#include "libls/ls_cstring_utils.h"
#include "libls/ls_string.h"
#include "libls/ls_lua_compat.h"
typedef struct {
size_t nwidgets;
LS_String *bufs;
// Temporary buffer for secondary buffering, to avoid unneeded redraws.
LS_String tmpbuf;
// Buffer for the content of the widgets joined by /sep/.
LS_String joined;
char *sep;
xcb_connection_t *conn;
xcb_window_t root;
} Priv;
static void destroy(LuastatusBarlibData *bd)
{
Priv *p = bd->priv;
for (size_t i = 0; i < p->nwidgets; ++i)
ls_string_free(p->bufs[i]);
free(p->bufs);
ls_string_free(p->tmpbuf);
ls_string_free(p->joined);
free(p->sep);
if (p->conn)
xcb_disconnect(p->conn);
free(p);
}
// Returns zero on success, non-zero XCB error code on failure. In either case, /*out_conn/ is
// written to, and should be closed with /xcb_disconnect()/.
static int do_connect(
const char *dpyname,
xcb_connection_t **out_conn,
xcb_window_t *out_root)
{
int screenp;
*out_conn = xcb_connect(dpyname, &screenp);
int r = xcb_connection_has_error(*out_conn);
if (r != 0)
return r;
const xcb_setup_t *setup = xcb_get_setup(*out_conn);
xcb_screen_iterator_t iter = xcb_setup_roots_iterator(setup);
for (int i = 0; i < screenp; ++i)
xcb_screen_next(&iter);
*out_root = iter.data->root;
return 0;
}
static bool redraw(LuastatusBarlibData *bd)
{
Priv *p = bd->priv;
LS_String *joined = &p->joined;
size_t n = p->nwidgets;
LS_String *bufs = p->bufs;
const char *sep = p->sep;
ls_string_clear(joined);
for (size_t i = 0; i < n; ++i) {
if (bufs[i].size) {
if (joined->size) {
ls_string_append_s(joined, sep);
}
ls_string_append_b(joined, bufs[i].data, bufs[i].size);
}
}
xcb_generic_error_t *err = xcb_request_check(
p->conn,
xcb_change_property_checked(
p->conn,
XCB_PROP_MODE_REPLACE,
p->root,
XCB_ATOM_WM_NAME,
XCB_ATOM_STRING,
8,
joined->size,
joined->data
)
);
if (err) {
LS_FATALF(bd, "XCB error %d occurred", err->error_code);
free(err);
return false;
}
return true;
}
static int init(LuastatusBarlibData *bd, const char *const *opts, size_t nwidgets)
{
Priv *p = bd->priv = LS_XNEW(Priv, 1);
*p = (Priv) {
.nwidgets = nwidgets,
.bufs = LS_XNEW(LS_String, nwidgets),
.tmpbuf = ls_string_new_reserve(512),
.joined = ls_string_new_reserve(1024),
.sep = NULL,
.conn = NULL,
};
for (size_t i = 0; i < nwidgets; ++i)
p->bufs[i] = ls_string_new_reserve(512);
// All the options may be passed multiple times!
const char *dpyname = NULL;
const char *sep = NULL;
for (const char *const *s = opts; *s; ++s) {
const char *v;
if ((v = ls_strfollow(*s, "display="))) {
dpyname = v;
} else if ((v = ls_strfollow(*s, "separator="))) {
sep = v;
} else {
LS_FATALF(bd, "unknown option '%s'", *s);
goto error;
}
}
p->sep = ls_xstrdup(sep ? sep : " | ");
int r = do_connect(dpyname, &p->conn, &p->root);
if (r != 0) {
LS_FATALF(bd, "can't connect to display: XCB error %d", r);
goto error;
}
// Clear the current name.
if (!redraw(bd))
goto error;
return LUASTATUS_OK;
error:
destroy(bd);
return LUASTATUS_ERR;
}
static int set(LuastatusBarlibData *bd, lua_State *L, size_t widget_idx)
{
Priv *p = bd->priv;
LS_String *buf = &p->tmpbuf;
ls_string_clear(buf);
// L: ? data
switch (lua_type(L, -1)) {
case LUA_TSTRING:
{
size_t ns;
const char *s = lua_tolstring(L, -1, &ns);
ls_string_assign_b(buf, s, ns);
}
break;
case LUA_TNIL:
break;
case LUA_TTABLE:
{
const char *sep = p->sep;
size_t len = ls_lua_array_len(L, -1);
for (size_t i = 1; i <= len; ++i) {
lua_rawgeti(L, -1, i); // L: ? data value
if (lua_isnil(L, -1)) {
goto next;
}
if (!lua_isstring(L, -1)) {
LS_ERRF(bd, "table value: expected string, found %s", luaL_typename(L, -1));
goto invalid_data;
}
size_t ns;
const char *s = lua_tolstring(L, -1, &ns);
if (buf->size && ns) {
ls_string_append_s(buf, sep);
}
ls_string_append_b(buf, s, ns);
next:
lua_pop(L, 1); // L: ? data
}
// L: ? data
}
break;
default:
LS_ERRF(bd, "expected table, string or nil, found %s", luaL_typename(L, -1));
goto invalid_data;
}
if (!ls_string_eq(*buf, p->bufs[widget_idx])) {
ls_string_swap(buf, &p->bufs[widget_idx]);
if (!redraw(bd)) {
return LUASTATUS_ERR;
}
}
return LUASTATUS_OK;
invalid_data:
ls_string_clear(&p->bufs[widget_idx]);
return LUASTATUS_NONFATAL_ERR;
}
static int set_error(LuastatusBarlibData *bd, size_t widget_idx)
{
Priv *p = bd->priv;
ls_string_assign_s(&p->bufs[widget_idx], "(Error)");
if (!redraw(bd)) {
return LUASTATUS_ERR;
}
return LUASTATUS_OK;
}
LuastatusBarlibIface luastatus_barlib_iface_v1 = {
.init = init,
.set = set,
.set_error = set_error,
.destroy = destroy,
};
================================================
FILE: barlibs/i3/CMakeLists.txt
================================================
file (GLOB sources "*.c")
luastatus_add_barlib (
barlib-i3
$
$
${sources}
)
target_compile_definitions (barlib-i3 PUBLIC -D_POSIX_C_SOURCE=200809L)
luastatus_target_compile_with (barlib-i3 LUA)
target_include_directories (barlib-i3 PUBLIC "${PROJECT_SOURCE_DIR}")
find_package (PkgConfig REQUIRED)
pkg_check_modules (YAJL REQUIRED yajl>=2.0.4)
luastatus_target_build_with (barlib-i3 YAJL)
find_library (MATH_LIBRARY m)
if (MATH_LIBRARY)
target_link_libraries (barlib-i3 PUBLIC ${MATH_LIBRARY})
endif ()
include (GNUInstallDirs)
install (PROGRAMS luastatus-i3-wrapper DESTINATION ${CMAKE_INSTALL_BINDIR})
luastatus_add_man_page (README.rst luastatus-barlib-i3 7)
================================================
FILE: barlibs/i3/README.rst
================================================
.. :X-man-page-only: luastatus-barlib-i3
.. :X-man-page-only: ###################
.. :X-man-page-only:
.. :X-man-page-only: ############################
.. :X-man-page-only: i3/sway barlib for luastatus
.. :X-man-page-only: ############################
.. :X-man-page-only:
.. :X-man-page-only: :Copyright: LGPLv3
.. :X-man-page-only: :Manual section: 7
Overview
========
This barlib talks with i3bar (or sway-bar).
To use this barlib, you need to specify ``luastatus-i3-wrapper`` with appropriate arguments as the
``status_command`` parameter of a bar in the i3 (or sway) configuration file. For example::
bar {
status_command cd ~/.config/luastatus && exec luastatus-i3-wrapper -B no_separators time-battery-combined.lua alsa.lua xkb.lua
Redirections and ``luastatus-i3-wrapper``
=========================================
i3bar and sway-bar require all the data to be written to stdout and read from stdin.
This makes it very easy
to mess things up: Lua's ``print()`` prints to stdout, processes spawned by widgets/plugins inherit
our stdin and stdout, etc.
That's why this barlib requires that stdin and stdout file descriptors are manually redirected.
A shell wrapper, ``luastatus-i3-wrapper``, is shipped with it; it does all the redirections and
executes ``luastatus`` with ``-b i3``, all the required ``-B`` options, and additional arguments
passed by you.
``cb`` return value
===================
Either of:
* an empty table or ``nil``
Hide the widget.
* a table with strings keys
Is interpreted as a single segment (or a "block"). The keys *should not* include ``name``, as it
is set automatically to be able to tell which widget was clicked.
For more information, see http://i3wm.org/docs/i3bar-protocol.html#_blocks_in_detail.
* an array (table with numeric keys)
Is interpreted as an array of segments. To be able to tell which one was clicked, set the
``instance`` field of a segment.
``nil`` elements are ignored.
``event`` argument
==================
A table with all click properties i3bar (or sway-bar) provides.
For more information, see http://i3wm.org/docs/i3bar-protocol.html#_click_events.
Functions
=========
The following functions are provided:
* ``escaped_str = luastatus.barlib.pango_escape(str)``
Escapes text for the Pango markup.
Example
=======
An example that uses all possible features::
function get_user_name()
local f = io.popen('whoami', 'r')
local r = f:read('*line')
f:close()
return r
end
i = 0
widget = {
plugin = 'timer',
opts = {period = 2},
cb = function(t)
i = i + 1
if i == 1 then
return {} -- hides widget; alternatively, you can return nil.
elseif i == 2 then
-- no markup unless you specify markup='pango'
return {full_text = '', color = '#aaaa00'}
elseif i == 3 then
-- see https://developer.gnome.org/pygtk/stable/pango-markup-language.html
return {full_text = 'Hello, '
.. luastatus.barlib.pango_escape(get_user_name())
.. '!',
markup = 'pango'}
elseif i == 4 then
i = 0
return {
{full_text = 'Now,', instance = 'now-segment'},
nil, -- nils are ignored so that you can do
-- return {get_time(), get_battery(), get_smth_else()}
-- where, for example, get_battery() returns nil if the battery is full.
{full_text = 'click me!', instance = 'click-me-segment'},
}
end
end,
event = function(t)
local r = {'properties:'}
for k, v in pairs(t) do
table.insert(r, k .. '=' .. tostring(v))
end
os.execute(string.format(
"notify-send 'Click event' '%s'", table.concat(r, ' ')))
end,
}
Options
=======
The following options are supported:
* ``in_fd=``
File descriptor to read i3bar or sway-bar input from. Usually set by the wrapper.
* ``out_fd=``
File descriptor to write to. Usually set by the wrapper.
* ``no_click_events``
Tell i3bar (or sway-bar) we don't want to receive click events. This changes its behavior in that
it will interpret "clicks" on segments as if an empty space on the bar was clicked,
particularly, will switch workspaces if you scroll on a segment.
* ``no_separators``
Append ``"separator": false`` to a segment, unless it has a ``separator`` key. Also appends it
to an ``(Error)`` segment.
* ``allow_stopping``
Allow i3bar (or sway-bar) to send luastatus ``SIGSTOP`` when it thinks it becomes invisible, and ``SIGCONT``
when it thinks it becomes visible. Quite a questionable feature.
* ``extra_init_json=``
Extra JSON to output in header, e.g. ``"key1":10,"key2":true``.
This could be of some use because Sway WM seems to be experimenting with additional header
fields (but not yet documenting them), e.g. ``float_event_coords``.
================================================
FILE: barlibs/i3/escape_json_str.c
================================================
/*
* Copyright (C) 2025 luastatus developers
*
* This file is part of luastatus.
*
* luastatus is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* luastatus is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with luastatus. If not, see .
*/
#include "escape_json_str.h"
#include
#include "libsafe/safev.h"
#include "libsafe/mut_safev.h"
static const SAFEV HEX_CHARS = SAFEV_STATIC_INIT_FROM_LITERAL("0123456789ABCDEF");
static inline void append_sv(LS_String *dst, SAFEV v)
{
ls_string_append_b(dst, SAFEV_ptr_UNSAFE(v), SAFEV_len(v));
}
void append_json_escaped_str(LS_String *dst, SAFEV v)
{
ls_string_append_c(dst, '"');
char esc_arr[] = {'\\', 'u', '0', '0', '#', '#'};
MUT_SAFEV esc = MUT_SAFEV_new_UNSAFE(esc_arr, sizeof(esc_arr));
size_t n = SAFEV_len(v);
size_t prev = 0;
for (size_t i = 0; i < n; ++i) {
unsigned char c = SAFEV_at(v, i);
if (c < 32 || c == '\\' || c == '"' || c == '/') {
append_sv(dst, SAFEV_subspan(v, prev, i));
MUT_SAFEV_set_at(esc, 4, SAFEV_at(HEX_CHARS, c / 16));
MUT_SAFEV_set_at(esc, 5, SAFEV_at(HEX_CHARS, c % 16));
append_sv(dst, MUT_SAFEV_TO_SAFEV(esc));
prev = i + 1;
}
}
append_sv(dst, SAFEV_subspan(v, prev, n));
ls_string_append_c(dst, '"');
}
================================================
FILE: barlibs/i3/escape_json_str.h
================================================
/*
* Copyright (C) 2025 luastatus developers
*
* This file is part of luastatus.
*
* luastatus is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* luastatus is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with luastatus. If not, see .
*/
#ifndef escape_json_str_h_
#define escape_json_str_h_
#include "libls/ls_string.h"
#include "libsafe/safev.h"
// Append to /dst/ JSON-escaped C string /v/.
void append_json_escaped_str(LS_String *dst, SAFEV v);
#endif
================================================
FILE: barlibs/i3/event_watcher.c
================================================
/*
* Copyright (C) 2015-2025 luastatus developers
*
* This file is part of luastatus.
*
* luastatus is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* luastatus is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with luastatus. If not, see .
*/
#include "event_watcher.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include "include/sayf_macros.h"
#include "libls/ls_tls_ebuf.h"
#include "libls/ls_parse_int.h"
#include "libls/ls_strarr.h"
#include "libls/ls_panic.h"
#include "libls/ls_lua_compat.h"
#include "libls/ls_alloc_utils.h"
#include "libls/ls_freemem.h"
#include "priv.h"
// If this is to be incremented, /lua_checkstack()/ must be called at appropriate times, and the
// depth of the recursion in /push_object()/ be potentially limited somehow.
enum { DEPTH_LIMIT = 10 };
typedef struct {
enum {
TYPE_ARRAY_START,
TYPE_ARRAY_END,
TYPE_MAP_START,
TYPE_MAP_END,
TYPE_STRING_KEY,
TYPE_STRING,
TYPE_NUMBER,
TYPE_BOOL,
TYPE_NULL,
} type;
union {
size_t str_idx;
double num;
bool flag;
} as;
} Token;
typedef struct {
Token *data;
size_t size;
size_t capacity;
} TokenList;
static inline TokenList token_list_new(void)
{
return (TokenList) {NULL, 0, 0};
}
static inline void token_list_push(TokenList *x, Token token)
{
if (x->size == x->capacity) {
x->data = LS_M_X2REALLOC(x->data, &x->capacity);
}
x->data[x->size++] = token;
}
static inline void token_list_clear(TokenList *x)
{
x->data = LS_M_FREEMEM(x->data, &x->size, &x->capacity);
}
static inline void token_list_free(TokenList *x)
{
free(x->data);
}
typedef struct {
// Current JSON nesting depth. Before the initial '[', depth == -1.
int depth;
// Whether the last key (at depth == 1) was "name".
bool last_key_is_name;
// An array in which all the JSON strings, including keys, are stored.
LS_StringArray strarr;
// A flat list of current event's tokens.
TokenList tokens;
// Current event's widget index, or a negative value if is not known yet or invalid.
int widget;
LuastatusBarlibData *bd;
LuastatusBarlibEWFuncs funcs;
} Context;
// Converts a JSON object that starts at the token with index /*idx/ in /ctx->tokens/, to a Lua
// object, and pushes it onto /L/'s stack.
// Advances /*idx/ so that it points to one token past the last token of the object.
static void push_object(lua_State *L, Context *ctx, size_t *idx)
{
Token t = ctx->tokens.data[*idx];
switch (t.type) {
case TYPE_ARRAY_START:
lua_newtable(L); // L: table
++*idx;
for (size_t n = 1; ctx->tokens.data[*idx].type != TYPE_ARRAY_END; ++n) {
push_object(L, ctx, idx); // L: table elem
LS_ASSERT(n <= (size_t) LS_LUA_MAXI);
lua_rawseti(L, -2, n); // L: table
}
break;
case TYPE_MAP_START:
lua_newtable(L); // L: table
++*idx;
while (ctx->tokens.data[*idx].type != TYPE_MAP_END) {
Token key = ctx->tokens.data[*idx];
LS_ASSERT(key.type == TYPE_STRING_KEY);
// To limit the maximum number of slots pushed onto /L/'s stack to /N + O(1)/, where /N/
// is the maximum /ctx->depth/ encountered, we have to push the value first.
// Unfortunately, /lua_settable()/ expects the key to be pushed first. So we simply swap
// them with /lua_insert()/.
++*idx;
push_object(L, ctx, idx); // L: table value
size_t ns;
const char *s = ls_strarr_at(ctx->strarr, key.as.str_idx, &ns);
lua_pushlstring(L, s, ns); // L: table value key
lua_insert(L, -2); // L: table key value
lua_settable(L, -3); // L: table
}
break;
case TYPE_STRING:
{
size_t ns;
const char *s = ls_strarr_at(ctx->strarr, t.as.str_idx, &ns);
lua_pushlstring(L, s, ns);
}
break;
case TYPE_NUMBER:
lua_pushnumber(L, t.as.num);
break;
case TYPE_BOOL:
lua_pushboolean(L, t.as.flag);
break;
case TYPE_NULL:
lua_pushnil(L);
break;
default:
LS_MUST_BE_UNREACHABLE();
}
// Now, /*idx/ points to the last token of the object; increment it by one.
++*idx;
}
static void flush(Context *ctx)
{
Priv *p = ctx->bd->priv;
if (ctx->widget >= 0 && (size_t) ctx->widget < p->nwidgets) {
lua_State *L = ctx->funcs.call_begin(ctx->bd->userdata, ctx->widget);
size_t idx = 0;
push_object(L, ctx, &idx);
LS_ASSERT(idx == ctx->tokens.size);
ctx->funcs.call_end(ctx->bd->userdata, ctx->widget);
}
// reset the context
ctx->last_key_is_name = false;
ls_strarr_clear(&ctx->strarr);
token_list_clear(&ctx->tokens);
ctx->widget = -1;
}
static int token_helper(Context *ctx, Token token)
{
if (ctx->depth == -1) {
if (token.type != TYPE_ARRAY_START) {
LS_ERRF(ctx->bd, "(event watcher) expected '['");
return 0;
}
++ctx->depth;
} else {
if (ctx->depth == 0 && token.type != TYPE_MAP_START) {
LS_ERRF(ctx->bd, "(event watcher) expected '{'");
return 0;
}
token_list_push(&ctx->tokens, token);
switch (token.type) {
case TYPE_ARRAY_START:
case TYPE_MAP_START:
if (++ctx->depth >= DEPTH_LIMIT) {
LS_ERRF(ctx->bd, "(event watcher) nesting depth limit exceeded");
return 0;
}
break;
case TYPE_ARRAY_END:
case TYPE_MAP_END:
if (--ctx->depth == 0) {
flush(ctx);
}
break;
default:
break;
}
}
return 1;
}
static inline size_t append_to_strarr(Context *ctx, const char *buf, size_t nbuf)
{
ls_strarr_append(&ctx->strarr, buf, nbuf);
return ls_strarr_size(ctx->strarr) - 1;
}
static int callback_null(void *vctx)
{
return token_helper(vctx, (Token) {TYPE_NULL, {0}});
}
static int callback_boolean(void *vctx, int value)
{
return token_helper(vctx, (Token) {TYPE_BOOL, {.flag = value}});
}
static int callback_integer(void *vctx, long long value)
{
return token_helper(vctx, (Token) {TYPE_NUMBER, {.num = value}});
}
static int callback_double(void *vctx, double value)
{
return token_helper(vctx, (Token) {TYPE_NUMBER, {.num = value}});
}
static int callback_string(void *vctx, const unsigned char *buf, size_t nbuf)
{
Context *ctx = vctx;
if (ctx->depth == 1 && ctx->last_key_is_name) {
// parse error is OK here, /ctx->widget/ is checked in /flush()/.
ctx->widget = ls_full_strtou_b((const char *) buf, nbuf);
}
return token_helper(ctx, (Token) {
TYPE_STRING,
{.str_idx = append_to_strarr(ctx, (const char *) buf, nbuf)}
});
}
static int callback_start_map(void *vctx)
{
return token_helper(vctx, (Token) {TYPE_MAP_START, {0}});
}
static int callback_map_key(void *vctx, const unsigned char *buf, size_t nbuf)
{
Context *ctx = vctx;
if (ctx->depth == 1) {
ctx->last_key_is_name = (nbuf == 4 && memcmp(buf, "name", 4) == 0);
}
return token_helper(ctx, (Token) {
TYPE_STRING_KEY,
{.str_idx = append_to_strarr(ctx, (const char *) buf, nbuf)}
});
}
static int callback_end_map(void *vctx)
{
return token_helper(vctx, (Token) {TYPE_MAP_END, {0}});
}
static int callback_start_array(void *vctx)
{
return token_helper(vctx, (Token) {TYPE_ARRAY_START, {0}});
}
static int callback_end_array(void *vctx)
{
return token_helper(vctx, (Token) {TYPE_ARRAY_END, {0}});
}
int event_watcher(LuastatusBarlibData *bd, LuastatusBarlibEWFuncs funcs)
{
Priv *p = bd->priv;
if (p->noclickev)
return LUASTATUS_NONFATAL_ERR;
Context ctx = {
.depth = -1,
.last_key_is_name = false,
.strarr = ls_strarr_new(),
.tokens = token_list_new(),
.widget = -1,
.bd = bd,
.funcs = funcs,
};
yajl_callbacks callbacks = {
.yajl_null = callback_null,
.yajl_boolean = callback_boolean,
.yajl_integer = callback_integer,
.yajl_double = callback_double,
.yajl_string = callback_string,
.yajl_start_map = callback_start_map,
.yajl_map_key = callback_map_key,
.yajl_end_map = callback_end_map,
.yajl_start_array = callback_start_array,
.yajl_end_array = callback_end_array,
};
yajl_handle hand = yajl_alloc(&callbacks, NULL, &ctx);
unsigned char buf[1024];
while (1) {
ssize_t nread = read(p->in_fd, buf, sizeof(buf));
if (nread < 0) {
LS_ERRF(bd, "(event watcher) read error: %s", ls_tls_strerror(errno));
goto error;
} else if (nread == 0) {
LS_ERRF(bd, "(event watcher) i3bar closed its end of the pipe");
goto error;
}
switch (yajl_parse(hand, buf, nread)) {
case yajl_status_ok:
break;
case yajl_status_client_canceled:
goto error;
case yajl_status_error:
{
unsigned char *descr = yajl_get_error(hand, /*verbose*/ 1, buf, nread);
LS_ERRF(bd, "(event watcher) yajl parse error: %s", (char *) descr);
yajl_free_error(hand, descr);
}
goto error;
}
}
error:
ls_strarr_destroy(ctx.strarr);
token_list_free(&ctx.tokens);
yajl_free(hand);
return LUASTATUS_ERR;
}
================================================
FILE: barlibs/i3/event_watcher.h
================================================
/*
* Copyright (C) 2015-2025 luastatus developers
*
* This file is part of luastatus.
*
* luastatus is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* luastatus is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with luastatus. If not, see .
*/
#ifndef event_watcher_h_
#define event_watcher_h_
#include "include/barlib_data_v1.h"
int event_watcher(LuastatusBarlibData *bd, LuastatusBarlibEWFuncs funcs);
#endif
================================================
FILE: barlibs/i3/fuzz_esc_json/.gitignore
================================================
harness
findings
================================================
FILE: barlibs/i3/fuzz_esc_json/build.sh
================================================
#!/bin/sh
if [ -z "$CC" ]; then
echo >&2 "You must set the 'CC' environment variable."
echo >&2 "Hint: you probably want to set 'CC' to 'some-directory/afl-gcc'."
exit 1
fi
cd -- "$(dirname "$(readlink "$0" || printf '%s\n' "$0")")"
luastatus_root=../../..
$CC -Wall -Wextra -O3 -fsanitize=undefined -std=c99 -D_POSIX_C_SOURCE=200809L \
-I"$luastatus_root" \
./harness.c \
../escape_json_str.c \
"$luastatus_root"/libls/ls_string.c \
"$luastatus_root"/libls/ls_alloc_utils.c \
"$luastatus_root"/libls/ls_panic.c \
"$luastatus_root"/libls/ls_cstring_utils.c \
"$luastatus_root"/libsafe/*.c \
-o harness
================================================
FILE: barlibs/i3/fuzz_esc_json/clear.sh
================================================
#!/bin/sh
set -e
cd -- "$(dirname "$(readlink "$0" || printf '%s\n' "$0")")"
rm -rf ./findings
================================================
FILE: barlibs/i3/fuzz_esc_json/fuzz.sh
================================================
#!/bin/sh
set -e
if [ -z "$XXX_AFL_DIR" ]; then
echo >&2 "You must set the 'XXX_AFL_DIR' environment variable."
exit 1
fi
cd -- "$(dirname "$(readlink "$0" || printf '%s\n' "$0")")"
mkdir -p ./findings
export UBSAN_OPTIONS=halt_on_error=1
export AFL_EXIT_WHEN_DONE=1
"$XXX_AFL_DIR"/afl-fuzz -i testcases -o findings -t 5 ./harness @@
================================================
FILE: barlibs/i3/fuzz_esc_json/gen_testcases.sh
================================================
#!/bin/sh
set -e
cd -- "$(dirname "$(readlink "$0" || printf '%s\n' "$0")")"
luastatus_root=../../..
"$luastatus_root"/fuzz_utils/gen_testcases/gen_testcases.py \
./testcases \
--a=1:'\"/' \
--a-range=h:1:0-31 \
--b=1:abc \
--b-range=h:1:127-255 \
--a-is-important \
--length=5-20 \
--num-files=10 \
--random-seed=123
================================================
FILE: barlibs/i3/fuzz_esc_json/harness.c
================================================
/*
* Copyright (C) 2025 luastatus developers
*
* This file is part of luastatus.
*
* luastatus is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* luastatus is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with luastatus. If not, see .
*/
#include
#include
#include
#include
#include "libls/ls_string.h"
#include "libsafe/safev.h"
#include "fuzz_utils/fuzz_utils.h"
#include "../escape_json_str.h"
int main(int argc, char **argv)
{
if (argc != 2) {
fprintf(stderr, "USAGE: harness INPUT_FILE\n");
return 2;
}
int fd_in = open(argv[1], O_RDONLY | O_CLOEXEC);
if (fd_in < 0) {
perror(argv[1]);
abort();
}
FuzzInput input = fuzz_input_new_prealloc(1024);
if (fuzz_input_read(fd_in, &input) < 0) {
perror("read");
abort();
}
LS_String res = ls_string_new_from_s("escape result = ");
append_json_escaped_str(&res, SAFEV_new_UNSAFE(input.data, input.size));
fuzz_utils_used(res.data, res.size);
fuzz_input_free(input);
ls_string_free(res);
close(fd_in);
return 0;
}
================================================
FILE: barlibs/i3/fuzz_esc_json/testcases/testcase_000
================================================
\"//\
================================================
FILE: barlibs/i3/fuzz_esc_json/testcases/testcase_001
================================================
""\
================================================
FILE: barlibs/i3/fuzz_esc_json/testcases/testcase_003
================================================
/\
================================================
FILE: barlibs/i3/fuzz_esc_json/testcases/testcase_004
================================================
"/cb\"ac"b\
================================================
FILE: barlibs/i3/fuzz_esc_json/testcases/testcase_006
================================================
baa߫"
================================================
FILE: barlibs/i3/fuzz_esc_json/testcases/testcase_007
================================================
c"bcc
================================================
FILE: barlibs/i3/fuzz_esc_json/testcases/testcase_008
================================================
cbcȂbab\
================================================
FILE: barlibs/i3/fuzz_esc_json/testcases/testcase_009
================================================
cbƐbccb
================================================
FILE: barlibs/i3/fuzz_esc_pango/.gitignore
================================================
harness
findings
================================================
FILE: barlibs/i3/fuzz_esc_pango/build.sh
================================================
#!/bin/sh
if [ -z "$CC" ]; then
echo >&2 "You must set the 'CC' environment variable."
echo >&2 "Hint: you probably want to set 'CC' to 'some-directory/afl-gcc'."
exit 1
fi
cd -- "$(dirname "$(readlink "$0" || printf '%s\n' "$0")")"
luastatus_root=../../..
$CC -Wall -Wextra -O3 -fsanitize=undefined -std=c99 -D_POSIX_C_SOURCE=200809L \
-I"$luastatus_root" \
./harness.c \
../pango_escape.c \
"$luastatus_root"/libls/ls_string.c \
"$luastatus_root"/libls/ls_alloc_utils.c \
"$luastatus_root"/libls/ls_panic.c \
"$luastatus_root"/libls/ls_cstring_utils.c \
"$luastatus_root"/libsafe/*.c \
-o harness
================================================
FILE: barlibs/i3/fuzz_esc_pango/clear.sh
================================================
#!/bin/sh
set -e
cd -- "$(dirname "$(readlink "$0" || printf '%s\n' "$0")")"
rm -rf ./findings
================================================
FILE: barlibs/i3/fuzz_esc_pango/fuzz.sh
================================================
#!/bin/sh
set -e
if [ -z "$XXX_AFL_DIR" ]; then
echo >&2 "You must set the 'XXX_AFL_DIR' environment variable."
exit 1
fi
cd -- "$(dirname "$(readlink "$0" || printf '%s\n' "$0")")"
mkdir -p ./findings
export UBSAN_OPTIONS=halt_on_error=1
export AFL_EXIT_WHEN_DONE=1
"$XXX_AFL_DIR"/afl-fuzz -i testcases -o findings -t 5 ./harness @@
================================================
FILE: barlibs/i3/fuzz_esc_pango/gen_testcases.sh
================================================
#!/bin/sh
set -e
cd -- "$(dirname "$(readlink "$0" || printf '%s\n' "$0")")"
luastatus_root=../../..
"$luastatus_root"/fuzz_utils/gen_testcases/gen_testcases.py \
./testcases \
--a=1:'&<>\"' \
--b=1:abc \
--a-is-important \
--length=5-20 \
--num-files=10 \
--random-seed=123
================================================
FILE: barlibs/i3/fuzz_esc_pango/harness.c
================================================
/*
* Copyright (C) 2025 luastatus developers
*
* This file is part of luastatus.
*
* luastatus is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* luastatus is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with luastatus. If not, see .
*/
#include
#include
#include
#include
#include "libls/ls_string.h"
#include "libsafe/safev.h"
#include "fuzz_utils/fuzz_utils.h"
#include "../pango_escape.h"
static void append_to_ls_string(void *ud, SAFEV segment)
{
LS_String *dst = ud;
ls_string_append_b(dst, SAFEV_ptr_UNSAFE(segment), SAFEV_len(segment));
}
int main(int argc, char **argv)
{
if (argc != 2) {
fprintf(stderr, "USAGE: harness INPUT_FILE\n");
return 2;
}
int fd_in = open(argv[1], O_RDONLY | O_CLOEXEC);
if (fd_in < 0) {
perror(argv[1]);
abort();
}
FuzzInput input = fuzz_input_new_prealloc(1024);
if (fuzz_input_read(fd_in, &input) < 0) {
perror("read");
abort();
}
LS_String res = ls_string_new_from_s("pango escape result = ");
pango_escape(
SAFEV_new_UNSAFE(input.data, input.size),
append_to_ls_string,
&res);
fuzz_utils_used(res.data, res.size);
fuzz_input_free(input);
ls_string_free(res);
close(fd_in);
return 0;
}
================================================
FILE: barlibs/i3/fuzz_esc_pango/testcases/testcase_000
================================================
&<>\"&
================================================
FILE: barlibs/i3/fuzz_esc_pango/testcases/testcase_001
================================================
>&&<&\>&b\"">""c\
================================================
FILE: barlibs/i3/fuzz_esc_pango/testcases/testcase_002
================================================
>c>>\&\c"\a&
================================================
FILE: barlibs/i3/fuzz_esc_pango/testcases/testcase_003
================================================
&<\<cbb""<\<
================================================
FILE: barlibs/i3/fuzz_esc_pango/testcases/testcase_004
================================================
">&aca""><"bb>"baa&
================================================
FILE: barlibs/i3/fuzz_esc_pango/testcases/testcase_005
================================================
&\bcbbb>b\>cc>&ab
================================================
FILE: barlibs/i3/fuzz_esc_pango/testcases/testcase_006
================================================
acb&\ab\a
================================================
FILE: barlibs/i3/fuzz_esc_pango/testcases/testcase_007
================================================
ac>bc
================================================
FILE: barlibs/i3/fuzz_esc_pango/testcases/testcase_008
================================================
bbccba<
================================================
FILE: barlibs/i3/fuzz_esc_pango/testcases/testcase_009
================================================
ababbaabcbbb
================================================
FILE: barlibs/i3/i3.c
================================================
/*
* Copyright (C) 2015-2025 luastatus developers
*
* This file is part of luastatus.
*
* luastatus is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* luastatus is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with luastatus. If not, see .
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include "include/barlib_v1.h"
#include "include/sayf_macros.h"
#include "libls/ls_string.h"
#include "libls/ls_alloc_utils.h"
#include "libls/ls_parse_int.h"
#include "libls/ls_cstring_utils.h"
#include "libls/ls_tls_ebuf.h"
#include "libls/ls_io_utils.h"
#include "libls/ls_lua_compat.h"
#include "libsafe/safev.h"
#include "priv.h"
#include "event_watcher.h"
#include "escape_json_str.h"
#include "pango_escape.h"
static void destroy(LuastatusBarlibData *bd)
{
Priv *p = bd->priv;
for (size_t i = 0; i < p->nwidgets; ++i) {
ls_string_free(p->bufs[i]);
}
free(p->bufs);
ls_string_free(p->tmpbuf);
ls_close(p->in_fd);
if (p->out) {
fclose(p->out);
}
free(p);
}
static int init(LuastatusBarlibData *bd, const char *const *opts, size_t nwidgets)
{
Priv *p = bd->priv = LS_XNEW(Priv, 1);
*p = (Priv) {
.nwidgets = nwidgets,
.bufs = LS_XNEW(LS_String, nwidgets),
.tmpbuf = ls_string_new_reserve(1024),
.in_fd = -1,
.out = NULL,
.noclickev = false,
.noseps = false,
};
for (size_t i = 0; i < nwidgets; ++i)
p->bufs[i] = ls_string_new_reserve(1024);
// All the options may be passed multiple times!
int in_fd = -1;
int out_fd = -1;
const char *extra_init_json = NULL;
bool allow_stopping = false;
for (const char *const *s = opts; *s; ++s) {
const char *v;
if ((v = ls_strfollow(*s, "in_fd="))) {
if ((in_fd = ls_full_strtou(v)) < 0) {
LS_FATALF(bd, "in_fd value is not a valid unsigned integer");
goto error;
}
} else if ((v = ls_strfollow(*s, "out_fd="))) {
if ((out_fd = ls_full_strtou(v)) < 0) {
LS_FATALF(bd, "out_fd value is not a valid unsigned integer");
goto error;
}
} else if (strcmp(*s, "no_click_events") == 0) {
p->noclickev = true;
} else if (strcmp(*s, "no_separators") == 0) {
p->noseps = true;
} else if (strcmp(*s, "allow_stopping") == 0) {
allow_stopping = true;
} else if ((v = ls_strfollow(*s, "extra_init_json="))) {
extra_init_json = v;
} else {
LS_FATALF(bd, "unknown option '%s'", *s);
goto error;
}
}
// we require /in_fd/ and /out_fd/ to >=3 because making stdin/stdout/stderr CLOEXEC has very
// bad consequences, and we just don't want to complicate the logic.
if (in_fd < 3) {
LS_FATALF(bd, "in_fd is not specified or less than 3");
goto error;
}
if (out_fd < 3) {
LS_FATALF(bd, "out_fd is not specified or less than 3");
goto error;
}
// assign
p->in_fd = in_fd;
if (!(p->out = fdopen(out_fd, "w"))) {
LS_FATALF(bd, "can't fdopen %d: %s", out_fd, ls_tls_strerror(errno));
goto error;
}
// make CLOEXEC
if (ls_make_cloexec(in_fd) < 0) {
LS_FATALF(bd, "can't make fd %d CLOEXEC: %s", in_fd, ls_tls_strerror(errno));
goto error;
}
if (ls_make_cloexec(out_fd) < 0) {
LS_FATALF(bd, "can't make fd %d CLOEXEC: %s", out_fd, ls_tls_strerror(errno));
goto error;
}
// print header
fprintf(p->out, "{\"version\":1,\"click_events\":%s", p->noclickev ? "false" : "true");
if (extra_init_json && extra_init_json[0]) {
fprintf(p->out, ",%s", extra_init_json);
}
if (!allow_stopping) {
fprintf(p->out, ",\"stop_signal\":0,\"cont_signal\":0");
}
fprintf(p->out, "}\n[\n");
fflush(p->out);
if (ferror(p->out)) {
LS_FATALF(bd, "write error: %s", ls_tls_strerror(errno));
goto error;
}
return LUASTATUS_OK;
error:
destroy(bd);
return LUASTATUS_ERR;
}
static bool redraw(LuastatusBarlibData *bd)
{
Priv *p = bd->priv;
FILE *out = p->out;
size_t n = p->nwidgets;
LS_String *bufs = p->bufs;
putc('[', out);
bool first = true;
for (size_t i = 0; i < n; ++i) {
if (bufs[i].size) {
if (!first) {
putc(',', out);
}
fwrite(bufs[i].data, 1, bufs[i].size, out);
first = false;
}
}
fputs("],\n", out);
fflush(out);
if (ferror(out)) {
LS_FATALF(bd, "write error: %s", ls_tls_strerror(errno));
return false;
}
return true;
}
static void append_to_lua_buf(void *ud, SAFEV segment)
{
luaL_Buffer *b = ud;
luaL_addlstring(b, SAFEV_ptr_UNSAFE(segment), SAFEV_len(segment));
}
static int l_pango_escape(lua_State *L)
{
size_t ns;
// WARNING: luaL_check*() functions do a long jump on error!
const char *s = luaL_checklstring(L, 1, &ns);
luaL_Buffer b;
luaL_buffinit(L, &b);
SAFEV v = SAFEV_new_UNSAFE(s, ns);
pango_escape(v, append_to_lua_buf, &b);
luaL_pushresult(&b);
return 1;
}
static void register_funcs(LuastatusBarlibData *bd, lua_State *L)
{
(void) bd;
// L: table
lua_pushcfunction(L, l_pango_escape); // L: table l_pango_escape
lua_setfield(L, -2, "pango_escape"); // L: table
}
static inline bool append_json_number(LS_String *s, double value)
{
if (!isfinite(value)) {
return false;
}
ls_string_append_f(s, "%.20g", value);
return true;
}
// Appends a JSON segment generated from table at the top of /L/'s stack, to
// /((Priv *) bd->priv)->tmpbuf/.
static bool append_segment(LuastatusBarlibData *bd, lua_State *L, size_t widget_idx)
{
Priv *p = bd->priv;
LS_String *dst = &p->tmpbuf;
// add a "prologue"
if (dst->size) {
ls_string_append_c(dst, ',');
}
ls_string_append_f(dst, "{\"name\":\"%zu\"", widget_idx);
bool has_separator_key = false;
// L: ? table
lua_pushnil(L); // L: ? table nil
while (lua_next(L, -2)) {
// L: ? table key value
if (!lua_isstring(L, -2)) {
LS_ERRF(bd, "segment key: expected string, found %s", luaL_typename(L, -2));
return false;
}
const char *key = lua_tostring(L, -2);
if (strcmp(key, "name") == 0) {
LS_WARNF(bd, "segment: ignoring 'name', it is set automatically; use 'instance' "
"instead");
goto next_entry;
}
if (strcmp(key, "separator") == 0) {
has_separator_key = true;
}
ls_string_append_c(dst, ',');
append_json_escaped_str(dst, SAFEV_new_from_cstr_UNSAFE(key));
ls_string_append_c(dst, ':');
switch (lua_type(L, -1)) {
case LUA_TNUMBER:
{
double val = lua_tonumber(L, -1);
if (!append_json_number(dst, val)) {
LS_ERRF(bd, "segment entry '%s': invalid number (NaN/Inf)", key);
return false;
}
}
break;
case LUA_TSTRING:
{
size_t ns;
const char *s = lua_tolstring(L, -1, &ns);
append_json_escaped_str(dst, SAFEV_new_UNSAFE(s, ns));
}
break;
case LUA_TBOOLEAN:
{
bool val = lua_toboolean(L, -1);
ls_string_append_s(dst, val ? "true" : "false");
}
break;
case LUA_TNIL:
ls_string_append_s(dst, "null");
break;
default:
LS_ERRF(bd, "segment entry '%s': expected string, number, boolean or nil, found %s",
key, luaL_typename(L, -1));
return false;
}
next_entry:
lua_pop(L, 1); // L: ? table key
}
// L: ? table
// add an "epilogue"
if (p->noseps && !has_separator_key) {
ls_string_append_s(dst, ",\"separator\":false");
}
ls_string_append_c(dst, '}');
return true;
}
typedef enum {
TC_EMPTY,
TC_ARRAY,
TC_DICT,
} TableClass;
static inline TableClass classify_table(lua_State *L)
{
// L: ? table
lua_pushnil(L); // L: ? table nil
if (!lua_next(L, -2)) {
// L: ? table
return TC_EMPTY;
}
// L: ? table key value
bool is_array = lua_isnumber(L, -2);
lua_pop(L, 2); // L: ? table
return is_array ? TC_ARRAY : TC_DICT;
}
static int set(LuastatusBarlibData *bd, lua_State *L, size_t widget_idx)
{
Priv *p = bd->priv;
ls_string_clear(&p->tmpbuf);
// L: ? data
switch (lua_type(L, -1)) {
case LUA_TNIL:
break;
case LUA_TTABLE:
switch (classify_table(L)) {
case TC_EMPTY:
break;
case TC_DICT:
if (!append_segment(bd, L, widget_idx)) {
goto invalid_data;
}
break;
case TC_ARRAY:
{
size_t len = ls_lua_array_len(L, -1);
for (size_t i = 1; i <= len; ++i) {
lua_rawgeti(L, -1, i); // L: ? data value
switch (lua_type(L, -1)) {
case LUA_TTABLE:
if (!append_segment(bd, L, widget_idx)) {
goto invalid_data;
}
break;
case LUA_TNIL:
break;
default:
LS_ERRF(bd, "array value: expected table or nil, found %s",
luaL_typename(L, -1));
goto invalid_data;
}
lua_pop(L, 1); // L: ? data
}
}
}
// L: ? data
break;
default:
LS_ERRF(bd, "expected table or nil, found %s", luaL_typename(L, -1));
goto invalid_data;
}
if (!ls_string_eq(p->tmpbuf, p->bufs[widget_idx])) {
ls_string_swap(&p->tmpbuf, &p->bufs[widget_idx]);
if (!redraw(bd)) {
return LUASTATUS_ERR;
}
}
return LUASTATUS_OK;
invalid_data:
ls_string_clear(&p->bufs[widget_idx]);
return LUASTATUS_NONFATAL_ERR;
}
static int set_error(LuastatusBarlibData *bd, size_t widget_idx)
{
Priv *p = bd->priv;
LS_String *s = &p->bufs[widget_idx];
ls_string_assign_s(
s, "{\"full_text\":\"(Error)\",\"color\":\"#ff0000\",\"background\":\"#000000\"");
if (p->noseps) {
ls_string_append_s(s, ",\"separator\":false");
}
ls_string_append_c(s, '}');
if (!redraw(bd)) {
return LUASTATUS_ERR;
}
return LUASTATUS_OK;
}
LuastatusBarlibIface luastatus_barlib_iface_v1 = {
.init = init,
.register_funcs = register_funcs,
.set = set,
.set_error = set_error,
.event_watcher = event_watcher,
.destroy = destroy,
};
================================================
FILE: barlibs/i3/luastatus-i3-wrapper
================================================
#!/bin/sh
exec ${LUASTATUS:-luastatus} -b i3 -B in_fd=3 -B out_fd=4 "$@" 3<&0 0&1 1>&2
================================================
FILE: barlibs/i3/pango_escape.c
================================================
/*
* Copyright (C) 2025 luastatus developers
*
* This file is part of luastatus.
*
* luastatus is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* luastatus is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with luastatus. If not, see .
*/
#include "pango_escape.h"
#include
#include "libsafe/safev.h"
#define LIT(StrLit_) SAFEV_new_from_literal(StrLit_)
void pango_escape(
SAFEV v,
void (*append)(void *ud, SAFEV segment),
void *ud)
{
size_t n = SAFEV_len(v);
size_t prev = 0;
for (size_t i = 0; i < n; ++i) {
SAFEV esc = {0};
switch (SAFEV_at(v, i)) {
case '&': esc = LIT("&"); break;
case '<': esc = LIT("<"); break;
case '>': esc = LIT(">"); break;
case '\'': esc = LIT("'"); break;
case '"': esc = LIT("""); break;
default: continue;
}
append(ud, SAFEV_subspan(v, prev, i));
append(ud, esc);
prev = i + 1;
}
append(ud, SAFEV_suffix(v, prev));
}
================================================
FILE: barlibs/i3/pango_escape.h
================================================
/*
* Copyright (C) 2025 luastatus developers
*
* This file is part of luastatus.
*
* luastatus is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* luastatus is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with luastatus. If not, see .
*/
#ifndef pango_escape_h_
#define pango_escape_h_
#include "libsafe/safev.h"
void pango_escape(
SAFEV v,
void (*append)(void *ud, SAFEV segment),
void *ud);
#endif
================================================
FILE: barlibs/i3/priv.h
================================================
/*
* Copyright (C) 2015-2025 luastatus developers
*
* This file is part of luastatus.
*
* luastatus is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* luastatus is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with luastatus. If not, see .
*/
#ifndef priv_h_
#define priv_h_
#include
#include
#include
#include "libls/ls_string.h"
typedef struct {
size_t nwidgets;
LS_String *bufs;
// Temporary buffer for secondary buffering, to avoid unneeded redraws.
LS_String tmpbuf;
// Input file descriptor.
int in_fd;
// /fdopen/'ed output file descritor.
FILE *out;
bool noclickev;
bool noseps;
} Priv;
#endif
================================================
FILE: barlibs/lemonbar/CMakeLists.txt
================================================
file (GLOB sources "*.c")
luastatus_add_barlib (
barlib-lemonbar
$
$
${sources}
)
target_compile_definitions (barlib-lemonbar PUBLIC -D_POSIX_C_SOURCE=200809L)
luastatus_target_compile_with (barlib-lemonbar LUA)
target_include_directories (barlib-lemonbar PUBLIC "${PROJECT_SOURCE_DIR}")
include (GNUInstallDirs)
install (PROGRAMS luastatus-lemonbar-launcher DESTINATION ${CMAKE_INSTALL_BINDIR})
luastatus_add_man_page (README.rst luastatus-barlib-lemonbar 7)
================================================
FILE: barlibs/lemonbar/README.rst
================================================
.. :X-man-page-only: luastatus-barlib-lemonbar
.. :X-man-page-only: #########################
.. :X-man-page-only:
.. :X-man-page-only: #############################
.. :X-man-page-only: lemonbar barlib for luastatus
.. :X-man-page-only: #############################
.. :X-man-page-only:
.. :X-man-page-only: :Copyright: LGPLv3
.. :X-man-page-only: :Manual section: 7
Overview
========
This barlib talks with ``lemonbar``.
It joins all non-empty strings returned by widgets by a separator, which defaults to ``" | "``.
Redirections and ``luastatus-lemonbar-launcher``
================================================
``lemonbar`` is not capable of creating a bidirectional pipe itself; instead, it requires all the
data to be written to its stdin and read from its stdout.
That's why the input/output file descriptors of this barlib must be manually redirected.
A launcher, ``luastatus-lemonbar-launcher``, is shipped with it; it spawns ``lemonbar`` connected to
a bidirectional pipe and executes ``luastatus`` with ``-b lemonbar``, all the required ``-B``
options, and additional arguments passed by you.
Pass each ``lemonbar`` argument with ``-p``, then pass ``--``, then pass luastatus arguments, e.g.::
luastatus-lemonbar-launcher -p -B#111111 -p -f'Droid Sans Mono for Powerline:pixelsize=12:weight=Bold' -- -Bseparator=' ' widget1.lua widget2.lua
``cb`` return value
===================
Either of:
* a string with lemonbar markup
An empty string hides the widget.
* an array of such strings
Equivalent to returning a string with all non-empty elements of the array joined by the
separator.
* ``nil``
Hides the widget.
Functions
=========
The following functions are provided:
* ``escaped_str = luastatus.barlib.escape(str)``
Escapes text for lemonbar markup.
``event`` argument
==================
A string with the name of the command.
Options
=======
The following options are supported:
* ``in_fd=``
File descriptor to read ``lemonbar`` input from. Usually set by the launcher.
* ``out_fd=``
File descriptor to write to. Usually set by the launcher.
* ``separator=``
Set the separator.
================================================
FILE: barlibs/lemonbar/fuzz_esc/.gitignore
================================================
harness
findings
================================================
FILE: barlibs/lemonbar/fuzz_esc/build.sh
================================================
#!/bin/sh
if [ -z "$CC" ]; then
echo >&2 "You must set the 'CC' environment variable."
echo >&2 "Hint: you probably want to set 'CC' to 'some-directory/afl-gcc'."
exit 1
fi
cd -- "$(dirname "$(readlink "$0" || printf '%s\n' "$0")")"
luastatus_root=../../..
$CC -Wall -Wextra -O3 -fsanitize=undefined -std=c99 -D_POSIX_C_SOURCE=200809L \
-I"$luastatus_root" \
./harness.c \
../markup_utils.c \
"$luastatus_root"/libls/ls_string.c \
"$luastatus_root"/libls/ls_alloc_utils.c \
"$luastatus_root"/libls/ls_panic.c \
"$luastatus_root"/libls/ls_cstring_utils.c \
"$luastatus_root"/libls/ls_parse_int.c \
"$luastatus_root"/libsafe/*.c \
-o harness
================================================
FILE: barlibs/lemonbar/fuzz_esc/clear.sh
================================================
#!/bin/sh
set -e
cd -- "$(dirname "$(readlink "$0" || printf '%s\n' "$0")")"
rm -rf ./findings
================================================
FILE: barlibs/lemonbar/fuzz_esc/fuzz.sh
================================================
#!/bin/sh
set -e
if [ -z "$XXX_AFL_DIR" ]; then
echo >&2 "You must set the 'XXX_AFL_DIR' environment variable."
exit 1
fi
cd -- "$(dirname "$(readlink "$0" || printf '%s\n' "$0")")"
mkdir -p ./findings
export UBSAN_OPTIONS=halt_on_error=1
export AFL_EXIT_WHEN_DONE=1
"$XXX_AFL_DIR"/afl-fuzz -i testcases -o findings -t 5 ./harness @@
================================================
FILE: barlibs/lemonbar/fuzz_esc/gen_testcases.sh
================================================
#!/bin/sh
set -e
cd -- "$(dirname "$(readlink "$0" || printf '%s\n' "$0")")"
luastatus_root=../../..
"$luastatus_root"/fuzz_utils/gen_testcases/gen_testcases.py \
./testcases \
--a=1:abc \
--b=1:'%' \
--length=5-20 \
--num-files=10 \
--random-seed=123
================================================
FILE: barlibs/lemonbar/fuzz_esc/harness.c
================================================
/*
* Copyright (C) 2025 luastatus developers
*
* This file is part of luastatus.
*
* luastatus is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* luastatus is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with luastatus. If not, see .
*/
#include
#include
#include
#include
#include "libls/ls_string.h"
#include "libsafe/safev.h"
#include "fuzz_utils/fuzz_utils.h"
#include "../markup_utils.h"
static void append_to_ls_string(void *ud, SAFEV segment)
{
LS_String *dst = ud;
ls_string_append_b(dst, SAFEV_ptr_UNSAFE(segment), SAFEV_len(segment));
}
int main(int argc, char **argv)
{
if (argc != 2) {
fprintf(stderr, "USAGE: harness INPUT_FILE\n");
return 2;
}
int fd_in = open(argv[1], O_RDONLY | O_CLOEXEC);
if (fd_in < 0) {
perror(argv[1]);
abort();
}
FuzzInput input = fuzz_input_new_prealloc(1024);
if (fuzz_input_read(fd_in, &input) < 0) {
perror("read");
abort();
}
LS_String res = ls_string_new_from_s("escape result = ");
escape(
append_to_ls_string,
&res,
SAFEV_new_UNSAFE(input.data, input.size));
fuzz_utils_used(res.data, res.size);
fuzz_input_free(input);
ls_string_free(res);
close(fd_in);
return 0;
}
================================================
FILE: barlibs/lemonbar/fuzz_esc/testcases/testcase_000
================================================
acabca
================================================
FILE: barlibs/lemonbar/fuzz_esc/testcases/testcase_001
================================================
abbc%cccbbacacb%cc
================================================
FILE: barlibs/lemonbar/fuzz_esc/testcases/testcase_002
================================================
ccacb%a%%aacbcc
================================================
FILE: barlibs/lemonbar/fuzz_esc/testcases/testcase_003
================================================
%c%abb%%%ccb%abcb
================================================
FILE: barlibs/lemonbar/fuzz_esc/testcases/testcase_004
================================================
%a%ca%b%
================================================
FILE: barlibs/lemonbar/fuzz_esc/testcases/testcase_005
================================================
%a%cbc%%%
================================================
FILE: barlibs/lemonbar/fuzz_esc/testcases/testcase_006
================================================
%b%b%b%%c%%%%b%
================================================
FILE: barlibs/lemonbar/fuzz_esc/testcases/testcase_007
================================================
%%%%%%%%bb
================================================
FILE: barlibs/lemonbar/fuzz_esc/testcases/testcase_008
================================================
%%%%b%%%%%%%%%c%
================================================
FILE: barlibs/lemonbar/fuzz_esc/testcases/testcase_009
================================================
%%%%%%
================================================
FILE: barlibs/lemonbar/fuzz_sanitize/.gitignore
================================================
harness
findings
================================================
FILE: barlibs/lemonbar/fuzz_sanitize/build.sh
================================================
#!/bin/sh
if [ -z "$CC" ]; then
echo >&2 "You must set the 'CC' environment variable."
echo >&2 "Hint: you probably want to set 'CC' to 'some-directory/afl-gcc'."
exit 1
fi
cd -- "$(dirname "$(readlink "$0" || printf '%s\n' "$0")")"
luastatus_root=../../..
$CC -Wall -Wextra -O3 -fsanitize=undefined -std=c99 -D_POSIX_C_SOURCE=200809L \
-I"$luastatus_root" \
./harness.c \
../markup_utils.c \
"$luastatus_root"/libls/ls_string.c \
"$luastatus_root"/libls/ls_alloc_utils.c \
"$luastatus_root"/libls/ls_panic.c \
"$luastatus_root"/libls/ls_cstring_utils.c \
"$luastatus_root"/libls/ls_parse_int.c \
"$luastatus_root"/libsafe/*.c \
-o harness
================================================
FILE: barlibs/lemonbar/fuzz_sanitize/clear.sh
================================================
#!/bin/sh
set -e
cd -- "$(dirname "$(readlink "$0" || printf '%s\n' "$0")")"
rm -rf ./findings
================================================
FILE: barlibs/lemonbar/fuzz_sanitize/fuzz.sh
================================================
#!/bin/sh
set -e
if [ -z "$XXX_AFL_DIR" ]; then
echo >&2 "You must set the 'XXX_AFL_DIR' environment variable."
exit 1
fi
# We add '-d' argument for original AFL because otherwise it
# does not terminate within 24 hours.
#
# '-d' means "quick & dirty mode (skips deterministic steps)".
case "$XXX_AFL_IS_PP" in
0)
extra_opts='-d'
;;
1)
extra_opts=
;;
*)
echo >&2 "You must set 'XXX_AFL_IS_PP' environment variable to either 0 or 1."
echo >&2 "Set it to 0 if AFL is the original Google's version (not AFL++)."
echo >&2 "Set it to 1 if AFL is actually AFL++."
exit 1
esac
# We also set AFL_NO_ARITH=1 because it's a text-based format.
# This potentially speeds up fuzzing.
export AFL_NO_ARITH=1
cd -- "$(dirname "$(readlink "$0" || printf '%s\n' "$0")")"
mkdir -p ./findings
export UBSAN_OPTIONS=halt_on_error=1
export AFL_EXIT_WHEN_DONE=1
"$XXX_AFL_DIR"/afl-fuzz $extra_opts -i testcases -o findings -t 5 ./harness @@
================================================
FILE: barlibs/lemonbar/fuzz_sanitize/gen_testcases.sh
================================================
#!/bin/sh
set -e
cd -- "$(dirname "$(readlink "$0" || printf '%s\n' "$0")")"
luastatus_root=../../..
nl=$(printf '\nx')
nl=${nl%x}
# 'fps' means 'final percent sign'
"$luastatus_root"/fuzz_utils/gen_testcases/gen_testcases.py \
./testcases \
--file-prefix='X_' \
--a=1:x \
--b=1:x \
--mut-substrings="|${nl}|}|%{A|%{x|%x|%%" \
--length=10 \
--num-files=6 \
--random-seed=123 \
--extra-testcase='fps:x%'
"$luastatus_root"/fuzz_utils/gen_testcases/gen_testcases.py \
./testcases \
--file-prefix='Y_' \
--a=1:x \
--b=1:x \
--mut-prefix='%{A' \
--mut-substrings='|:' \
--length=10 \
--num-files=4 \
--random-seed=123
"$luastatus_root"/fuzz_utils/gen_testcases/gen_testcases.py \
./testcases \
--file-prefix='Z_' \
--a=1:x \
--b=1:x \
--mut-prefix='x%{A' \
--mut-substrings='|:' \
--length=10 \
--num-files=5 \
--random-seed=123
================================================
FILE: barlibs/lemonbar/fuzz_sanitize/harness.c
================================================
/*
* Copyright (C) 2025 luastatus developers
*
* This file is part of luastatus.
*
* luastatus is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* luastatus is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with luastatus. If not, see .
*/
#include
#include
#include
#include
#include "libls/ls_string.h"
#include "libsafe/safev.h"
#include "fuzz_utils/fuzz_utils.h"
#include "../markup_utils.h"
int main(int argc, char **argv)
{
if (argc != 2) {
fprintf(stderr, "USAGE: harness INPUT_FILE\n");
return 2;
}
int fd_in = open(argv[1], O_RDONLY | O_CLOEXEC);
if (fd_in < 0) {
perror(argv[1]);
abort();
}
FuzzInput input = fuzz_input_new_prealloc(1024);
if (fuzz_input_read(fd_in, &input) < 0) {
perror("read");
abort();
}
LS_String res = ls_string_new_reserve(2048);
ls_string_append_s(&res, "sanitize result = ");
append_sanitized(
&res,
123,
SAFEV_new_UNSAFE(input.data, input.size));
fuzz_utils_used(res.data, res.size);
fuzz_input_free(input);
ls_string_free(res);
close(fd_in);
return 0;
}
================================================
FILE: barlibs/lemonbar/fuzz_sanitize/testcases/X_testcase_000
================================================
xxxx
xxxxxx
================================================
FILE: barlibs/lemonbar/fuzz_sanitize/testcases/X_testcase_001
================================================
xxxxxxx}xxx
================================================
FILE: barlibs/lemonbar/fuzz_sanitize/testcases/X_testcase_002
================================================
xxxxxxxxxx%{A
================================================
FILE: barlibs/lemonbar/fuzz_sanitize/testcases/X_testcase_003
================================================
xxxxx%{xxxxxx
================================================
FILE: barlibs/lemonbar/fuzz_sanitize/testcases/X_testcase_004
================================================
xxxxxxxx%xxx
================================================
FILE: barlibs/lemonbar/fuzz_sanitize/testcases/X_testcase_005
================================================
xxxxxxxxx%%x
================================================
FILE: barlibs/lemonbar/fuzz_sanitize/testcases/X_testcase_fps
================================================
x%
================================================
FILE: barlibs/lemonbar/fuzz_sanitize/testcases/Y_testcase_000
================================================
xxxx:xxxxxx
================================================
FILE: barlibs/lemonbar/fuzz_sanitize/testcases/Y_testcase_001
================================================
%xxxxxxx:xxx
================================================
FILE: barlibs/lemonbar/fuzz_sanitize/testcases/Y_testcase_002
================================================
%{xxxxxxxxxx:
================================================
FILE: barlibs/lemonbar/fuzz_sanitize/testcases/Y_testcase_003
================================================
%{Axxxxx:xxxxx
================================================
FILE: barlibs/lemonbar/fuzz_sanitize/testcases/Z_testcase_000
================================================
xxxx:xxxxxx
================================================
FILE: barlibs/lemonbar/fuzz_sanitize/testcases/Z_testcase_001
================================================
xxxxxxxx:xxx
================================================
FILE: barlibs/lemonbar/fuzz_sanitize/testcases/Z_testcase_002
================================================
x%xxxxxxxxxx:
================================================
FILE: barlibs/lemonbar/fuzz_sanitize/testcases/Z_testcase_003
================================================
x%{xxxxx:xxxxx
================================================
FILE: barlibs/lemonbar/fuzz_sanitize/testcases/Z_testcase_004
================================================
x%{Axxxxxxxx:xx
================================================
FILE: barlibs/lemonbar/lemonbar.c
================================================
/*
* Copyright (C) 2015-2025 luastatus developers
*
* This file is part of luastatus.
*
* luastatus is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* luastatus is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with luastatus. If not, see .
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include "include/barlib_v1.h"
#include "include/sayf_macros.h"
#include "libls/ls_string.h"
#include "libls/ls_cstring_utils.h"
#include "libls/ls_tls_ebuf.h"
#include "libls/ls_parse_int.h"
#include "libls/ls_io_utils.h"
#include "libls/ls_alloc_utils.h"
#include "libls/ls_lua_compat.h"
#include "libsafe/safev.h"
#include "markup_utils.h"
typedef struct {
size_t nwidgets;
LS_String *bufs;
// Temporary buffer for secondary buffering, to avoid unneeded redraws.
LS_String tmpbuf;
char *sep;
// /fdopen/'ed input file descriptor.
FILE *in;
// /fdopen/'ed output file descriptor.
FILE *out;
} Priv;
static void destroy(LuastatusBarlibData *bd)
{
Priv *p = bd->priv;
for (size_t i = 0; i < p->nwidgets; ++i)
ls_string_free(p->bufs[i]);
free(p->bufs);
ls_string_free(p->tmpbuf);
free(p->sep);
if (p->in)
fclose(p->in);
if (p->out)
fclose(p->out);
free(p);
}
static int init(LuastatusBarlibData *bd, const char *const *opts, size_t nwidgets)
{
Priv *p = bd->priv = LS_XNEW(Priv, 1);
*p = (Priv) {
.nwidgets = nwidgets,
.bufs = LS_XNEW(LS_String, nwidgets),
.tmpbuf = ls_string_new_reserve(512),
.sep = NULL,
.in = NULL,
.out = NULL,
};
for (size_t i = 0; i < nwidgets; ++i)
p->bufs[i] = ls_string_new_reserve(512);
// All the options may be passed multiple times!
const char *sep = NULL;
int in_fd = -1;
int out_fd = -1;
for (const char *const *s = opts; *s; ++s) {
const char *v;
if ((v = ls_strfollow(*s, "in_fd="))) {
if ((in_fd = ls_full_strtou(v)) < 0) {
LS_FATALF(bd, "in_fd value is not a valid unsigned integer");
goto error;
}
} else if ((v = ls_strfollow(*s, "out_fd="))) {
if ((out_fd = ls_full_strtou(v)) < 0) {
LS_FATALF(bd, "out_fd value is not a valid unsigned integer");
goto error;
}
} else if ((v = ls_strfollow(*s, "separator="))) {
sep = v;
} else {
LS_FATALF(bd, "unknown option '%s'", *s);
goto error;
}
}
p->sep = ls_xstrdup(sep ? sep : " | ");
// we require /in_fd/ and /out_fd/ to be >=3 because making stdin/stdout/stderr CLOEXEC has very
// bad consequences, and we just don't want to complicate the logic.
if (in_fd < 3) {
LS_FATALF(bd, "in_fd is not specified or less than 3");
goto error;
}
if (out_fd < 3) {
LS_FATALF(bd, "out_fd is not specified or less than 3");
goto error;
}
// open
if (!(p->in = fdopen(in_fd, "r"))) {
LS_FATALF(bd, "can't fdopen %d: %s", in_fd, ls_tls_strerror(errno));
goto error;
}
if (!(p->out = fdopen(out_fd, "w"))) {
LS_FATALF(bd, "can't fdopen %d: %s", out_fd, ls_tls_strerror(errno));
goto error;
}
// make CLOEXEC
if (ls_make_cloexec(in_fd) < 0) {
LS_FATALF(bd, "can't make fd %d CLOEXEC: %s", in_fd, ls_tls_strerror(errno));
goto error;
}
if (ls_make_cloexec(out_fd) < 0) {
LS_FATALF(bd, "can't make fd %d CLOEXEC: %s", out_fd, ls_tls_strerror(errno));
goto error;
}
return LUASTATUS_OK;
error:
destroy(bd);
return LUASTATUS_ERR;
}
static void append_to_lua_buf_callback(void *ud, SAFEV v)
{
luaL_Buffer *b = ud;
luaL_addlstring(b, SAFEV_ptr_UNSAFE(v), SAFEV_len(v));
}
static int l_escape(lua_State *L)
{
size_t ns;
// WARNING: /luaL_check*()/ functions do a long jump on error!
const char *s = luaL_checklstring(L, 1, &ns);
luaL_Buffer b;
luaL_buffinit(L, &b);
escape(append_to_lua_buf_callback, &b, SAFEV_new_UNSAFE(s, ns));
luaL_pushresult(&b); // L: result
return 1;
}
static void register_funcs(LuastatusBarlibData *bd, lua_State *L)
{
(void) bd;
// L: table
lua_pushcfunction(L, l_escape); // L: table l_escape
lua_setfield(L, -2, "escape"); // L: table
}
static bool redraw(LuastatusBarlibData *bd)
{
Priv *p = bd->priv;
FILE *out = p->out;
size_t n = p->nwidgets;
LS_String *bufs = p->bufs;
const char *sep = p->sep;
bool first = true;
for (size_t i = 0; i < n; ++i) {
if (bufs[i].size) {
if (!first) {
fputs(sep, out);
}
fwrite(bufs[i].data, 1, bufs[i].size, out);
first = false;
}
}
putc('\n', out);
fflush(out);
if (ferror(out)) {
LS_FATALF(bd, "write error: %s", ls_tls_strerror(errno));
return false;
}
return true;
}
static int set(LuastatusBarlibData *bd, lua_State *L, size_t widget_idx)
{
Priv *p = bd->priv;
LS_String *buf = &p->tmpbuf;
ls_string_clear(buf);
// L: ? data
switch (lua_type(L, -1)) {
case LUA_TNIL:
break;
case LUA_TSTRING:
{
size_t ns;
const char *s = lua_tolstring(L, -1, &ns);
append_sanitized(buf, widget_idx, SAFEV_new_UNSAFE(s, ns));
}
break;
case LUA_TTABLE:
{
const char *sep = p->sep;
size_t len = ls_lua_array_len(L, -1);
for (size_t i = 1; i <= len; ++i) {
lua_rawgeti(L, -1, i); // L: ? data value
if (lua_isnil(L, -1)) {
goto next;
}
if (!lua_isstring(L, -1)) {
LS_ERRF(bd, "table value: expected string, found %s", luaL_typename(L, -1));
goto invalid_data;
}
size_t ns;
const char *s = lua_tolstring(L, -1, &ns);
if (buf->size && ns) {
ls_string_append_s(buf, sep);
}
append_sanitized(buf, widget_idx, SAFEV_new_UNSAFE(s, ns));
next:
lua_pop(L, 1); // L: ? data
}
// L: ? data
}
break;
default:
LS_ERRF(bd, "expected string, table or nil, found %s", luaL_typename(L, -1));
goto invalid_data;
}
if (!ls_string_eq(*buf, p->bufs[widget_idx])) {
ls_string_swap(buf, &p->bufs[widget_idx]);
if (!redraw(bd)) {
return LUASTATUS_ERR;
}
}
return LUASTATUS_OK;
invalid_data:
ls_string_clear(&p->bufs[widget_idx]);
return LUASTATUS_NONFATAL_ERR;
}
static int set_error(LuastatusBarlibData *bd, size_t widget_idx)
{
Priv *p = bd->priv;
ls_string_assign_s(&p->bufs[widget_idx], "%{B#f00}%{F#fff}(Error)%{B-}%{F-}");
if (!redraw(bd)) {
return LUASTATUS_ERR;
}
return LUASTATUS_OK;
}
static int event_watcher(LuastatusBarlibData *bd, LuastatusBarlibEWFuncs funcs)
{
Priv *p = bd->priv;
char *buf = NULL;
size_t nbuf = 256;
for (ssize_t nread; (nread = getline(&buf, &nbuf, p->in)) >= 0;) {
if (nread == 0 || buf[nread - 1] != '\n')
continue;
size_t ncommand;
size_t widget_idx;
const char *command = parse_command(buf, nread - 1, &ncommand, &widget_idx);
if (!command)
continue;
if (widget_idx >= p->nwidgets) {
continue;
}
lua_State *L = funcs.call_begin(bd->userdata, widget_idx);
lua_pushlstring(L, command, ncommand);
funcs.call_end(bd->userdata, widget_idx);
}
if (feof(p->in)) {
LS_ERRF(bd, "lemonbar closed its pipe end");
} else {
LS_ERRF(bd, "read error: %s", ls_tls_strerror(errno));
}
free(buf);
return LUASTATUS_ERR;
}
LuastatusBarlibIface luastatus_barlib_iface_v1 = {
.init = init,
.register_funcs = register_funcs,
.set = set,
.set_error = set_error,
.event_watcher = event_watcher,
.destroy = destroy,
};
================================================
FILE: barlibs/lemonbar/luastatus-lemonbar-launcher
================================================
#!/usr/bin/env bash
set -e
if (( BASH_VERSINFO[0] < 4 )); then
echo >&2 "bash 4.0 or higher is required (this one is $BASH_VERSION)."
exit 1
fi
usage() {
printf '%s\n' >&2 "$*
USAGE: ${0##*/} [-p lemonbar_argument [-p ...]] -- [luastatus_argument [...]]
Note that '--' is mandatory."
exit 2
}
lemonbar_args=()
# There is no easy way to require '--' with getopts, so we parse $@ manually.
while true; do
if (( $# == 0 )); then
usage "'--' argument not found."
fi
case "$1" in
--)
shift
break
;;
-p)
if (( $# == 1 )); then
usage "'-p' option requires an argument."
fi
shift
lemonbar_args+=("$1")
;;
-p*)
lemonbar_args+=("${1:2}")
;;
*)
usage "Unexpected argument '$1' found before '--'."
;;
esac
shift
done
coproc ${LEMONBAR:-lemonbar} "${lemonbar_args[@]}"
exec <&${COPROC[0]} >&${COPROC[1]}
exec ${LUASTATUS:-luastatus} -b lemonbar -B in_fd=3 -B out_fd=4 "$@" 3<&0 0&1 1>&2
================================================
FILE: barlibs/lemonbar/markup_utils.c
================================================
/*
* Copyright (C) 2015-2025 luastatus developers
*
* This file is part of luastatus.
*
* luastatus is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* luastatus is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with luastatus. If not, see .
*/
#include "markup_utils.h"
#include "libls/ls_string.h"
#include "libls/ls_parse_int.h"
#include "libsafe/safev.h"
#include
#include
void escape(
void (*append)(void *ud, SAFEV segment),
void *ud,
SAFEV v)
{
// just replace all "%"s with "%%"
for (;;) {
size_t i = SAFEV_index_of(v, '%');
if (i == (size_t) -1) {
break;
}
append(ud, SAFEV_subspan(v, 0, i));
append(ud, SAFEV_new_from_literal("%%"));
v = SAFEV_suffix(v, i + 1);
}
append(ud, v);
}
static inline void append_sv(LS_String *dst, SAFEV v)
{
ls_string_append_b(dst, SAFEV_ptr_UNSAFE(v), SAFEV_len(v));
}
void append_sanitized(LS_String *buf, size_t widget_idx, SAFEV v)
{
size_t n = SAFEV_len(v);
size_t prev = 0;
bool a_tag = false;
for (size_t i = 0; i < n; ++i) {
#define DO_PREV(WhetherToIncludeThis_) \
do { \
size_t j__ = i + ((WhetherToIncludeThis_) ? 1 : 0); \
append_sv(buf, SAFEV_subspan(v, prev, j__)); \
prev = i + 1; \
} while (0)
#define PEEK(Offset_) SAFEV_at_or(v, i + (Offset_), '\0')
switch (SAFEV_at(v, i)) {
case '\n':
DO_PREV(false);
break;
case '%':
if (PEEK(1) == '{' && PEEK(2) == 'A') {
a_tag = true;
} else if (PEEK(1) == '%') {
++i;
}
break;
case ':':
if (a_tag) {
DO_PREV(true);
ls_string_append_f(buf, "%zu_", widget_idx);
a_tag = false;
}
break;
case '}':
a_tag = false;
break;
}
#undef DO_PREV
#undef PEEK
}
append_sv(buf, SAFEV_suffix(v, prev));
}
const char *parse_command(const char *line, size_t nline, size_t *ncommand, size_t *widget_idx)
{
const char *endptr;
int idx = ls_strtou_b(line, nline, &endptr);
if (idx < 0 ||
endptr == line ||
endptr == line + nline ||
*endptr != '_')
{
return NULL;
}
const char *command = endptr + 1;
*ncommand = nline - (command - line);
*widget_idx = idx;
return command;
}
================================================
FILE: barlibs/lemonbar/markup_utils.h
================================================
/*
* Copyright (C) 2015-2025 luastatus developers
*
* This file is part of luastatus.
*
* luastatus is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* luastatus is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with luastatus. If not, see .
*/
#ifndef markup_utils_h_
#define markup_utils_h_
#include
#include "libls/ls_string.h"
#include "libsafe/safev.h"
void escape(
void (*append)(void *ud, SAFEV segment),
void *ud,
SAFEV v);
void append_sanitized(LS_String *buf, size_t widget_idx, SAFEV v);
const char *parse_command(const char *line, size_t nline, size_t *ncommand, size_t *widget_idx);
#endif
================================================
FILE: barlibs/stdout/CMakeLists.txt
================================================
file (GLOB sources "*.c")
luastatus_add_barlib (
barlib-stdout
$
$
${sources}
)
target_compile_definitions (barlib-stdout PUBLIC -D_POSIX_C_SOURCE=200809L)
luastatus_target_compile_with (barlib-stdout LUA)
target_include_directories (barlib-stdout PUBLIC "${PROJECT_SOURCE_DIR}")
include (GNUInstallDirs)
install (PROGRAMS luastatus-stdout-wrapper DESTINATION ${CMAKE_INSTALL_BINDIR})
luastatus_add_man_page (README.rst luastatus-barlib-stdout 7)
================================================
FILE: barlibs/stdout/README.rst
================================================
.. :X-man-page-only: luastatus-barlib-stdout
.. :X-man-page-only: #######################
.. :X-man-page-only:
.. :X-man-page-only: ###########################
.. :X-man-page-only: stdout barlib for luastatus
.. :X-man-page-only: ###########################
.. :X-man-page-only:
.. :X-man-page-only: :Copyright: LGPLv3
.. :X-man-page-only: :Manual section: 7
Overview
========
This barlib simply writes lines to a file descriptor.
It can be used for status bars such as **dzen**/**dzen2**, **xmobar**, **yabar**, **dvtm**, and
others.
It joins all non-empty strings returned by widgets by a separator, which defaults to ``" | "``.
It does not provide functions.
Redirections and ``luastatus-stdout-wrapper``
=============================================
Since we need to write to stdout, it is very easy to mess things up: Lua's ``print()`` prints to
stdout, processes spawned by widgets/plugins inherit our stdin and stdout, etc.
That's why this barlib requires that stdout file descriptor is manually redirected. A shell wrapper,
``luastatus-stdout-wrapper``, is shipped with it; it does all the redirections needed and executes
``luastatus`` with ``-b stdout`` and additional arguments passed by you.
It does not redirect stdin and/or pass ``in_fd=`` option. Make your own wrapper if this is needed.
``cb`` return value
===================
Either of:
* a string
An empty string hides the widget.
* an array of strings
Equivalent to returning a string with all non-empty elements of the array joined by the
separator.
* ``nil``
Hides the widget.
Options
=======
The following options are supported:
* ``out_fd=``
File descriptor to write to. Usually set by the wrapper.
* ``separator=``
Set the separator.
* ``error=``
Set the content of an "error" segment. Defaults to ``"(Error)"``.
* ``in_filename=`` or ``in_fd=``
Enable event watcher.
If ``in_filename=`` is specified, this barlib will open the specified
filename for reading and then read from. If ``in_fd=`` is specified, the
specified file descriptor will be read from. It is invalid to pass both options;
in this case, this barlib will fail to initialize.
This barilib will then read lines from the specified source. Each line will be
treated as an event.
This barlib doesn't try to interpret the content of the line; instead, the event
will be broadcast to all widgets. Thus, if this option is used, it is the
responsibility of each widget (that listens to the events) to check if the event
is somehow related to it.
``event`` argument
==================
Events are only reported if either ``in_filename=`` or ``in_fd=`` option was
specified (see above). In this case, the argument is the line that was read from
the specified file, without the trailing newline character.
================================================
FILE: barlibs/stdout/fuzz/.gitignore
================================================
harness
findings
================================================
FILE: barlibs/stdout/fuzz/build.sh
================================================
#!/bin/sh
if [ -z "$CC" ]; then
echo >&2 "You must set the 'CC' environment variable."
echo >&2 "Hint: you probably want to set 'CC' to 'some-directory/afl-gcc'."
exit 1
fi
cd -- "$(dirname "$(readlink "$0" || printf '%s\n' "$0")")"
luastatus_root=../../..
$CC -Wall -Wextra -O3 -fsanitize=undefined -std=c99 -D_POSIX_C_SOURCE=200809L \
-I"$luastatus_root" \
./harness.c \
../sanitize.c \
"$luastatus_root"/libls/ls_string.c \
"$luastatus_root"/libls/ls_alloc_utils.c \
"$luastatus_root"/libls/ls_panic.c \
"$luastatus_root"/libls/ls_cstring_utils.c \
"$luastatus_root"/libsafe/*.c \
-o harness
================================================
FILE: barlibs/stdout/fuzz/clear.sh
================================================
#!/bin/sh
set -e
cd -- "$(dirname "$(readlink "$0" || printf '%s\n' "$0")")"
rm -rf ./findings
================================================
FILE: barlibs/stdout/fuzz/fuzz.sh
================================================
#!/bin/sh
set -e
if [ -z "$XXX_AFL_DIR" ]; then
echo >&2 "You must set the 'XXX_AFL_DIR' environment variable."
exit 1
fi
cd -- "$(dirname "$(readlink "$0" || printf '%s\n' "$0")")"
mkdir -p ./findings
export UBSAN_OPTIONS=halt_on_error=1
export AFL_EXIT_WHEN_DONE=1
"$XXX_AFL_DIR"/afl-fuzz -i testcases -o findings -t 5 ./harness @@
================================================
FILE: barlibs/stdout/fuzz/gen_testcases.sh
================================================
#!/bin/sh
set -e
cd -- "$(dirname "$(readlink "$0" || printf '%s\n' "$0")")"
luastatus_root=../../..
nl=$(printf '\nx')
nl=${nl%x}
"$luastatus_root"/fuzz_utils/gen_testcases/gen_testcases.py \
./testcases \
--a=1:"$nl" \
--b=1:xyz \
--a-is-important \
--length=5-20 \
--num-files=10 \
--random-seed=123
================================================
FILE: barlibs/stdout/fuzz/harness.c
================================================
/*
* Copyright (C) 2025 luastatus developers
*
* This file is part of luastatus.
*
* luastatus is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* luastatus is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with luastatus. If not, see .
*/
#include
#include
#include
#include
#include "libls/ls_string.h"
#include "libsafe/safev.h"
#include "fuzz_utils/fuzz_utils.h"
#include "../sanitize.h"
int main(int argc, char **argv)
{
if (argc != 2) {
fprintf(stderr, "USAGE: harness INPUT_FILE\n");
return 2;
}
int fd_in = open(argv[1], O_RDONLY | O_CLOEXEC);
if (fd_in < 0) {
perror(argv[1]);
abort();
}
FuzzInput input = fuzz_input_new_prealloc(1024);
if (fuzz_input_read(fd_in, &input) < 0) {
perror("read");
abort();
}
LS_String res = ls_string_new_from_s("sanitize result = ");
append_sanitized(&res, SAFEV_new_UNSAFE(input.data, input.size));
fuzz_utils_used(res.data, res.size);
fuzz_input_free(input);
ls_string_free(res);
close(fd_in);
return 0;
}
================================================
FILE: barlibs/stdout/fuzz/testcases/testcase_000
================================================
================================================
FILE: barlibs/stdout/fuzz/testcases/testcase_001
================================================
y
================================================
FILE: barlibs/stdout/fuzz/testcases/testcase_002
================================================
z
y
x
x
================================================
FILE: barlibs/stdout/fuzz/testcases/testcase_003
================================================
xz
z
z
y
z
================================================
FILE: barlibs/stdout/fuzz/testcases/testcase_004
================================================
y
x
z
z
================================================
FILE: barlibs/stdout/fuzz/testcases/testcase_005
================================================
z
xx
z
y
================================================
FILE: barlibs/stdout/fuzz/testcases/testcase_006
================================================
x
y
z
yy
yyy
yxx
================================================
FILE: barlibs/stdout/fuzz/testcases/testcase_007
================================================
xyyxyx
zy
zy
zxyy
================================================
FILE: barlibs/stdout/fuzz/testcases/testcase_008
================================================
xxxxzxx
================================================
FILE: barlibs/stdout/fuzz/testcases/testcase_009
================================================
zyyyyzxyyxyxxx
================================================
FILE: barlibs/stdout/luastatus-dvtm
================================================
#!/bin/sh
FIFO=~/.luastatus-dvtm
set -e
rm -f "$FIFO"
mkfifo -m600 "$FIFO"
exec ${LUASTATUS:-luastatus} -b stdout -B out_fd=3 3>"$FIFO" "$@" &
status_pid=$!
${DVTM:-dvtm} -s "$FIFO" 2>/dev/null
kill "$status_pid"
rm -f "$FIFO"
================================================
FILE: barlibs/stdout/luastatus-stdout-wrapper
================================================
#!/bin/sh
exec ${LUASTATUS:-luastatus} -b stdout -B out_fd=3 "$@" 3>&1 1>&2
================================================
FILE: barlibs/stdout/open_stdio_file.c
================================================
/*
* Copyright (C) 2026 luastatus developers
*
* This file is part of luastatus.
*
* luastatus is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* luastatus is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with luastatus. If not, see .
*/
#include "open_stdio_file.h"
#include
#include
#include
#include
#include "libls/ls_io_utils.h"
#include "libls/ls_tls_ebuf.h"
#include "include/barlib_data_v1.h"
#include "include/sayf_macros.h"
static bool open_input_fd(
LuastatusBarlibData *bd,
FILE **dst,
int fd)
{
if (ls_make_cloexec(fd) < 0) {
LS_FATALF(bd, "can't make fd %d (in_fd) CLOEXEC: %s", fd, ls_tls_strerror(errno));
return false;
}
*dst = fdopen(fd, "r");
if (!*dst) {
LS_FATALF(bd, "can't fdopen %d (in_fd): %s", fd, ls_tls_strerror(errno));
return false;
}
return true;
}
static bool open_input_filename(
LuastatusBarlibData *bd,
FILE **dst,
const char *filename)
{
int fd = open(filename, O_RDONLY | O_CLOEXEC);
if (fd < 0) {
LS_FATALF(bd, "can't open '%s' (in_filename): %s", filename, ls_tls_strerror(errno));
return false;
}
*dst = fdopen(fd, "r");
if (!*dst) {
LS_FATALF(bd, "can't fdopen %d (opened in_filename): %s", fd, ls_tls_strerror(errno));
close(fd);
return false;
}
return true;
}
bool open_input(
LuastatusBarlibData *bd,
FILE **dst,
int fd,
const char *filename)
{
if (fd >= 0 && filename) {
LS_FATALF(bd, "both in_fd and in_filename were specified");
return false;
}
if (fd >= 0) {
return open_input_fd(bd, dst, fd);
}
if (filename) {
return open_input_filename(bd, dst, filename);
}
return true;
}
bool open_output(
LuastatusBarlibData *bd,
FILE **dst,
int fd)
{
if (ls_make_cloexec(fd) < 0) {
LS_FATALF(bd, "can't make fd %d (out_fd) CLOEXEC: %s", fd, ls_tls_strerror(errno));
return false;
}
*dst = fdopen(fd, "w");
if (!*dst) {
LS_FATALF(bd, "can't fdopen %d (out_fd): %s", fd, ls_tls_strerror(errno));
return false;
}
return true;
}
================================================
FILE: barlibs/stdout/open_stdio_file.h
================================================
/*
* Copyright (C) 2026 luastatus developers
*
* This file is part of luastatus.
*
* luastatus is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* luastatus is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with luastatus. If not, see .
*/
#ifndef open_stdio_file_
#define open_stdio_file_
#include
#include
#include "include/barlib_data_v1.h"
bool open_input(
LuastatusBarlibData *bd,
FILE **dst,
int fd,
const char *filename);
bool open_output(
LuastatusBarlibData *bd,
FILE **dst,
int fd);
#endif
================================================
FILE: barlibs/stdout/sanitize.c
================================================
/*
* Copyright (C) 2015-2025 luastatus developers
*
* This file is part of luastatus.
*
* luastatus is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* luastatus is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with luastatus. If not, see .
*/
#include "sanitize.h"
#include
#include "libls/ls_string.h"
#include "libsafe/safev.h"
static inline void append_sv(LS_String *dst, SAFEV v)
{
ls_string_append_b(dst, SAFEV_ptr_UNSAFE(v), SAFEV_len(v));
}
void append_sanitized(LS_String *buf, SAFEV v)
{
for (;;) {
size_t i = SAFEV_index_of(v, '\n');
if (i == (size_t) -1) {
break;
}
append_sv(buf, SAFEV_subspan(v, 0, i));
v = SAFEV_suffix(v, i + 1);
}
append_sv(buf, v);
}
================================================
FILE: barlibs/stdout/sanitize.h
================================================
/*
* Copyright (C) 2015-2025 luastatus developers
*
* This file is part of luastatus.
*
* luastatus is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* luastatus is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with luastatus. If not, see .
*/
#ifndef sanitize_h_
#define sanitize_h_
#include "libls/ls_string.h"
#include "libsafe/safev.h"
void append_sanitized(LS_String *buf, SAFEV v);
#endif
================================================
FILE: barlibs/stdout/stdout.c
================================================
/*
* Copyright (C) 2015-2025 luastatus developers
*
* This file is part of luastatus.
*
* luastatus is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* luastatus is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with luastatus. If not, see .
*/
#include
#include
#include
#include
#include
#include
#include
#include "include/barlib_v1.h"
#include "include/sayf_macros.h"
#include "libls/ls_string.h"
#include "libls/ls_cstring_utils.h"
#include "libls/ls_tls_ebuf.h"
#include "libls/ls_parse_int.h"
#include "libls/ls_alloc_utils.h"
#include "libls/ls_lua_compat.h"
#include "libsafe/safev.h"
#include "sanitize.h"
#include "open_stdio_file.h"
typedef struct {
size_t nwidgets;
LS_String *bufs;
// Temporary buffer for secondary buffering, to avoid unneeded redraws.
LS_String tmpbuf;
char *sep;
// Content of an "error" segment.
char *error;
// /fdopen/'ed output file descriptor.
FILE *out;
// Value of /in_filename/ option.
FILE *in;
} Priv;
static void destroy(LuastatusBarlibData *bd)
{
Priv *p = bd->priv;
for (size_t i = 0; i < p->nwidgets; ++i) {
ls_string_free(p->bufs[i]);
}
free(p->bufs);
ls_string_free(p->tmpbuf);
free(p->sep);
free(p->error);
if (p->out) {
fclose(p->out);
}
if (p->in) {
fclose(p->in);
}
free(p);
}
static int init(LuastatusBarlibData *bd, const char *const *opts, size_t nwidgets)
{
Priv *p = bd->priv = LS_XNEW(Priv, 1);
*p = (Priv) {
.nwidgets = nwidgets,
.bufs = LS_XNEW(LS_String, nwidgets),
.tmpbuf = ls_string_new_reserve(512),
.sep = NULL,
.error = NULL,
.out = NULL,
.in = NULL,
};
for (size_t i = 0; i < nwidgets; ++i)
p->bufs[i] = ls_string_new_reserve(512);
// All the options may be passed multiple times!
const char *sep = NULL;
const char *error = NULL;
const char *in_filename = NULL;
int out_fd = -1;
int in_fd = -1;
for (const char *const *s = opts; *s; ++s) {
const char *v;
if ((v = ls_strfollow(*s, "out_fd="))) {
if ((out_fd = ls_full_strtou(v)) < 0) {
LS_FATALF(bd, "out_fd value is not a valid unsigned integer");
goto error;
}
} else if ((v = ls_strfollow(*s, "in_fd="))) {
if ((in_fd = ls_full_strtou(v)) < 0) {
LS_FATALF(bd, "in_fd value is not a valid unsigned integer");
goto error;
}
} else if ((v = ls_strfollow(*s, "separator="))) {
sep = v;
} else if ((v = ls_strfollow(*s, "error="))) {
error = v;
} else if ((v = ls_strfollow(*s, "in_filename="))) {
in_filename = v;
} else {
LS_FATALF(bd, "unknown option '%s'", *s);
goto error;
}
}
p->sep = ls_xstrdup(sep ? sep : " | ");
p->error = ls_xstrdup(error ? error : "(Error)");
// we require /out_fd/ to be >=3 because making stdin/stdout/stderr CLOEXEC has very bad
// consequences, and we just don't want to complicate the logic.
if (out_fd < 3) {
LS_FATALF(bd, "out_fd is not specified or less than 3");
goto error;
}
// same goes for /in_fd/, if specified
if (in_fd >= 0 && in_fd < 3) {
LS_FATALF(bd, "in_fd is less than 3");
goto error;
}
// open output
if (!open_output(bd, &p->out, out_fd)) {
goto error;
}
// open input
if (!open_input(bd, &p->in, in_fd, in_filename)) {
goto error;
}
return LUASTATUS_OK;
error:
destroy(bd);
return LUASTATUS_ERR;
}
static bool redraw(LuastatusBarlibData *bd)
{
Priv *p = bd->priv;
FILE *out = p->out;
size_t n = p->nwidgets;
LS_String *bufs = p->bufs;
const char *sep = p->sep;
bool first = true;
for (size_t i = 0; i < n; ++i) {
if (bufs[i].size) {
if (!first) {
fputs(sep, out);
}
fwrite(bufs[i].data, 1, bufs[i].size, out);
first = false;
}
}
putc('\n', out);
fflush(out);
if (ferror(out)) {
LS_FATALF(bd, "write error: %s", ls_tls_strerror(errno));
return false;
}
return true;
}
static int set(LuastatusBarlibData *bd, lua_State *L, size_t widget_idx)
{
Priv *p = bd->priv;
LS_String *buf = &p->tmpbuf;
ls_string_clear(buf);
// L: ? data
switch (lua_type(L, -1)) {
case LUA_TNIL:
break;
case LUA_TSTRING:
{
size_t ns;
const char *s = lua_tolstring(L, -1, &ns);
append_sanitized(buf, SAFEV_new_UNSAFE(s, ns));
}
break;
case LUA_TTABLE:
{
const char *sep = p->sep;
size_t len = ls_lua_array_len(L, -1);
for (size_t i = 1; i <= len; ++i) {
lua_rawgeti(L, -1, i); // L: ? data value
if (lua_isnil(L, -1)) {
goto next;
}
if (!lua_isstring(L, -1)) {
LS_ERRF(bd, "table value: expected string, found %s", luaL_typename(L, -1));
goto invalid_data;
}
size_t ns;
const char *s = lua_tolstring(L, -1, &ns);
if (buf->size && ns) {
ls_string_append_s(buf, sep);
}
append_sanitized(buf, SAFEV_new_UNSAFE(s, ns));
next:
lua_pop(L, 1); // L: ? data value
}
// L: ? data
}
break;
default:
LS_ERRF(bd, "expected string, table or nil, found %s", luaL_typename(L, -1));
goto invalid_data;
}
if (!ls_string_eq(*buf, p->bufs[widget_idx])) {
ls_string_swap(buf, &p->bufs[widget_idx]);
if (!redraw(bd)) {
return LUASTATUS_ERR;
}
}
return LUASTATUS_OK;
invalid_data:
ls_string_clear(&p->bufs[widget_idx]);
return LUASTATUS_NONFATAL_ERR;
}
static int set_error(LuastatusBarlibData *bd, size_t widget_idx)
{
Priv *p = bd->priv;
ls_string_assign_s(&p->bufs[widget_idx], p->error);
if (!redraw(bd)) {
return LUASTATUS_ERR;
}
return LUASTATUS_OK;
}
static int event_watcher(LuastatusBarlibData *bd, LuastatusBarlibEWFuncs funcs)
{
Priv *p = bd->priv;
if (!p->in) {
LS_DEBUGF(bd, "event watcher: in_fd/in_filename not specified, returning");
return LUASTATUS_NONFATAL_ERR;
}
char *line = NULL;
size_t line_buf_n = 1024;
ssize_t line_n;
while ((line_n = getline(&line, &line_buf_n, p->in)) >= 0) {
if (line_n && line[line_n - 1] == '\n') {
--line_n;
}
for (size_t i = 0; i < p->nwidgets; ++i) {
lua_State *L = funcs.call_begin(bd->userdata, i);
lua_pushlstring(L, line, line_n);
funcs.call_end(bd->userdata, i);
}
}
if (feof(p->in)) {
LS_FATALF(bd, "event watcher: the other end of pipe/FIFO/something has been closed");
} else {
LS_FATALF(bd, "event watcher: I/O error: %s", ls_tls_strerror(errno));
}
free(line);
return LUASTATUS_ERR;
}
LuastatusBarlibIface luastatus_barlib_iface_v1 = {
.init = init,
.set = set,
.set_error = set_error,
.event_watcher = event_watcher,
.destroy = destroy,
};
================================================
FILE: check_final_newline.py
================================================
#!/usr/bin/env python3
import os
import sys
def say(s: str) -> None:
print(s, file=sys.stderr)
def main() -> None:
args = sys.argv[1:]
if not args:
say('USAGE: check_final_newline.py FILE [FILE ...]')
sys.exit(2)
everything_ok = True
for arg in args:
with open(arg, 'rb') as f:
f.seek(0, os.SEEK_END)
file_size = f.tell()
if not file_size:
continue
f.seek(-1, os.SEEK_END)
final_byte = f.read(1)
if final_byte != b'\n':
say(f'File "{arg}" has no trailing newline!')
everything_ok = False
if everything_ok:
sys.exit(0)
else:
sys.exit(1)
if __name__ == '__main__':
main()
================================================
FILE: check_includes.sh
================================================
#!/usr/bin/env bash
# USAGE: check_includes.sh DIRECTORY [EXTRA CFLAGS...]
# Requires 'include-what-you-use' tool.
set -e
set -o pipefail
check_entity=${1?}; shift
if [[ -d $check_entity ]]; then
check_dir=$check_entity
check_file=
else
check_dir=$(dirname -- "$check_entity")
check_file=$check_entity
fi
extra_cflags=( -D_POSIX_C_SOURCE=200809L "$@" )
luastatus_dir="$check_dir"
luastatus_dir_found=0
for (( i = 0; i < 10; ++i )); do
if [[ -e "$luastatus_dir"/generate-man.sh ]]; then
luastatus_dir_found=1
break
fi
luastatus_dir+='/..'
done
if (( ! luastatus_dir_found )); then
echo >&2 "Cannot find luastatus dir"
exit 1
fi
cmakelists_dir=${CMAKELISTS_DIR:-$check_dir}
modules=()
if [[ -e "$cmakelists_dir"/CMakeLists.txt ]]; then
modules_raw=$(sed -rn 's/^\s*pkg_check_modules\s*\(.*\s+REQUIRED\s+(.*)\)\s*$/\1/p' "$cmakelists_dir"/CMakeLists.txt)
# Replace all whitespace with newlines
modules_raw=$(sed -r 's/\s+/\n/g' <<< "$modules_raw")
# Remove version specifications (e.g. "yajl>=2.0.4" -> "yajl")
modules_raw=$(sed -r 's/^([-a-zA-Z0-9_.]+).*/\1/' <<< "$modules_raw")
# Split by whitespace, assign to 'modules' array
modules=( $modules_raw )
fi
if (( ${#modules[@]} )); then
echo >&2 "Modules: ${modules[*]}"
else
echo >&2 "Modules: (none)"
fi
my_filter() {
awk '
/^The full include-list for / {
skip = 1
}
/^---$/ {
skip = 0
}
# print if and only if (!skip)
!skip
'
}
cflags=$(pkg-config --cflags ${LUA_LIB:-lua} "${modules[@]}")
do_check_specific_file() {
include-what-you-use -I"$luastatus_dir" $cflags "${extra_cflags[@]}" "$1" 2>&1 | my_filter
}
if [[ -n $check_file ]]; then
do_check_specific_file "$check_file"
else
find "$check_dir" -name '*.[ch]' | while IFS= read -r src_file; do
if [[ $src_file == *.in.h ]]; then
continue
fi
do_check_specific_file "$src_file"
done
fi
================================================
FILE: check_style.sh
================================================
#!/usr/bin/env bash
set -e
set -o pipefail
cd -- "$(dirname "$(readlink "$0" || printf '%s\n' "$0")")"
say() {
printf '%s\n' "$*" >&2
}
exts=(
c h
lua
py
sh bash
md rst txt
)
find_name_clause=()
for ext in "${exts[@]}"; do
if (( ${#find_name_clause[@]} )); then
find_name_clause+=( -or )
fi
find_name_clause+=( -name "*.$ext" )
done
ignore_db=(
f:CMakeCache.txt
D:CMakeFiles
D:CMakeScripts
f:Makefile
f:cmake_install.cmake
f:install_manifest.txt
f:CTestTestfile.cmake
D:build
)
check_against_ignore_db() {
local entry
local entry_basename
for entry in "${ignore_db[@]}"; do
entry_basename=${entry#?:}
case "$entry" in
f:*)
if [[ $1 == */"$entry_basename" ]]; then
return 1
fi
;;
D:*)
if [[ $1 == */"$entry_basename"/* ]]; then
return 1
fi
;;
*)
say "FATAL: ignore_db contains invalid entry '$entry'.";
exit 1
;;
esac
done
return 0
}
paths=()
while IFS= read -r path; do
if ! check_against_ignore_db "$path"; then
continue
fi
paths+=( "$path" )
done < <(find \( "${find_name_clause[@]}" \) -and -type f)
if (( ! ${#paths[@]} )); then
say 'No files found with extensions of interest.'
say 'This means something is wrong, so exiting with non-zero code.'
exit 1
fi
printf '%s\n' "${paths[@]}" | xargs -d $'\n' ./check_final_newline.py
ok=1
for path in "${paths[@]}"; do
if grep -E -H -n '\s$' -- "$path"; then
ok=0
fi
done
if (( !ok )); then
say 'Some files have lines with trailing whitespace; see above.'
exit 1
fi
say "Checked ${#paths[@]} file(s), everything is OK."
exit 0
================================================
FILE: contrib/luastatus-9999.ebuild
================================================
# Copyright 1999-2023 Gentoo Authors
# Distributed under the terms of the GNU General Public License v2
EAPI=8
CMAKE_IN_SOURCE_BUILD=1
inherit cmake
DESCRIPTION="Universal status bar content generator"
HOMEPAGE="https://github.com/shdown/luastatus"
if [[ ${PV} == *9999* ]]; then
inherit git-r3
SRC_URI=""
EGIT_REPO_URI="https://github.com/shdown/${PN}.git"
KEYWORDS="~amd64 ~x86"
else
SRC_URI="https://github.com/shdown/${PN}/archive/v${PV}.tar.gz -> ${P}.tar.gz"
KEYWORDS="amd64 x86"
fi
BARLIBS="
${PN}_barlibs_dwm
${PN}_barlibs_i3
${PN}_barlibs_lemonbar
${PN}_barlibs_stdout
"
PROPER_PLUGINS="
+${PN}_plugins_alsa
+${PN}_plugins_dbus
+${PN}_plugins_fs
+${PN}_plugins_inotify
+${PN}_plugins_mpd
+${PN}_plugins_network-linux
+${PN}_plugins_pulse
+${PN}_plugins_timer
+${PN}_plugins_udev
+${PN}_plugins_unixsock
+${PN}_plugins_xkb
+${PN}_plugins_xtitle
"
DERIVED_PLUGINS="
+${PN}_plugins_backlight-linux
+${PN}_plugins_battery-linux
+${PN}_plugins_cpu-usage-linux
+${PN}_plugins_file-contents-linux
+${PN}_plugins_imap
+${PN}_plugins_mem-usage-linux
+${PN}_plugins_pipe
"
PLUGINS="
${PROPER_PLUGINS}
${DERIVED_PLUGINS}
"
LICENSE="LGPL-3+"
SLOT="0"
IUSE="doc examples luajit ${BARLIBS} ${PLUGINS}"
REQUIRED_USE="
${PN}_plugins_backlight-linux? ( ${PN}_plugins_udev )
${PN}_plugins_battery-linux? ( ${PN}_plugins_udev )
${PN}_plugins_cpu-usage-linux? ( ${PN}_plugins_timer )
${PN}_plugins_file-contents-linux? ( ${PN}_plugins_inotify )
${PN}_plugins_imap? ( ${PN}_plugins_timer )
${PN}_plugins_mem-usage-linux? ( ${PN}_plugins_timer )
${PN}_plugins_pipe? ( ${PN}_plugins_timer )
"
DEPEND="
doc? ( dev-python/docutils )
"
RDEPEND="
luajit? ( dev-lang/luajit:2 )
!luajit? ( dev-lang/lua )
${PN}_barlibs_dwm? ( x11-libs/libxcb )
${PN}_barlibs_i3? ( >=dev-libs/yajl-2.0.4 )
${PN}_plugins_alsa? ( media-libs/alsa-lib )
${PN}_plugins_dbus? ( dev-libs/glib )
${PN}_plugins_network-linux? ( sys-kernel/linux-headers dev-libs/libnl )
${PN}_plugins_pulse? ( media-sound/pulseaudio )
${PN}_plugins_udev? ( virtual/libudev )
${PN}_plugins_xkb? ( x11-libs/libX11 )
${PN}_plugins_xtitle? ( x11-libs/libxcb x11-libs/xcb-util-wm x11-libs/xcb-util )
"
src_configure() {
local mycmakeargs=(
$(use luajit && echo -DWITH_LUA_LIBRARY=luajit)
-DBUILD_DOCS=$(usex doc)
-DBUILD_BARLIB_DWM=$(usex ${PN}_barlibs_dwm)
-DBUILD_BARLIB_I3=$(usex ${PN}_barlibs_i3)
-DBUILD_BARLIB_LEMONBAR=$(usex ${PN}_barlibs_lemonbar)
-DBUILD_BARLIB_STDOUT=$(usex ${PN}_barlibs_stdout)
-DBUILD_PLUGIN_ALSA=$(usex ${PN}_plugins_alsa)
-DBUILD_PLUGIN_BACKLIGHT_LINUX=$(usex ${PN}_plugins_backlight-linux)
-DBUILD_PLUGIN_BATTERY_LINUX=$(usex ${PN}_plugins_battery-linux)
-DBUILD_PLUGIN_CPU_USAGE_LINUX=$(usex ${PN}_plugins_cpu-usage-linux)
-DBUILD_PLUGIN_DBUS=$(usex ${PN}_plugins_dbus)
-DBUILD_PLUGIN_FILE_CONTENTS_LINUX=$(usex ${PN}_plugins_file-contents-linux)
-DBUILD_PLUGIN_FS=$(usex ${PN}_plugins_fs)
-DBUILD_PLUGIN_IMAP=$(usex ${PN}_plugins_imap)
-DBUILD_PLUGIN_INOTIFY=$(usex ${PN}_plugins_inotify)
-DBUILD_PLUGIN_MEM_USAGE_LINUX=$(usex ${PN}_plugins_mem-usage-linux)
-DBUILD_PLUGIN_MPD=$(usex ${PN}_plugins_mpd)
-DBUILD_PLUGIN_NETWORK_LINUX=$(usex ${PN}_plugins_network-linux)
-DBUILD_PLUGIN_PIPE=$(usex ${PN}_plugins_pipe)
-DBUILD_PLUGIN_PULSE=$(usex ${PN}_plugins_pulse)
-DBUILD_PLUGIN_TIMER=$(usex ${PN}_plugins_timer)
-DBUILD_PLUGIN_UDEV=$(usex ${PN}_plugins_udev)
-DBUILD_PLUGIN_UNIXSOCK=$(usex ${PN}_plugins_unixsock)
-DBUILD_PLUGIN_XKB=$(usex ${PN}_plugins_xkb)
-DBUILD_PLUGIN_XTITLE=$(usex ${PN}_plugins_xtitle)
)
cmake_src_configure
}
src_install() {
cmake_src_install
local i
if use examples; then
dodir /usr/share/doc/${PF}/examples
docinto examples
for i in ${BARLIBS//+/}; do
if use ${i}; then
barlib=${i#${PN}_barlibs_}
dodoc -r examples/${barlib}
docompress -x /usr/share/doc/${PF}/examples/${barlib}
fi
done
fi
}
================================================
FILE: contrib/luastatus.spec
================================================
Name: luastatus
Version: 0.3.0
Release: 1%{?dist}
Summary: universal statusbar content generator
License: LGPL3+
URL: https://github.com/shdown/luastatus
Source0: https://github.com/shdown/luastatus/archive/v%version.tar.gz
BuildRequires: cmake
BuildRequires: luajit-devel
BuildRequires: libxcb-devel
BuildRequires: yajl-devel
BuildRequires: alsa-lib-devel
BuildRequires: xcb-util-wm-devel
BuildRequires: xcb-util-devel
BuildRequires: glib2-devel
%description
a universal status bar content generator
%package plugins
Summary: luastatus plugins
Requires: %{name} = %{version}-%{release}
%description plugins
luastatus plugins
%prep
%setup -q
%build
%cmake -DWITH_LUA_LIBRARY=luajit .
%make_build
%install
%make_install
%files
%doc COPYING.txt COPYING.LESSER.txt README.md
%{_bindir}/luastatus
%{_bindir}/luastatus-i3-wrapper
%{_bindir}/luastatus-lemonbar-launcher
%{_mandir}/man1/luastatus.1*
%{_libdir}/luastatus/barlibs/*
%files plugins
%{_libdir}/luastatus/plugins/*
%changelog
================================================
FILE: debian/changelog
================================================
luastatus (1:9999) UNRELEASED; urgency=medium
* local build
-- Local User Sat, 08 Feb 2014 01:01:01 -0800
================================================
FILE: debian/compat
================================================
10
================================================
FILE: debian/control
================================================
Source: luastatus
Section: misc
Priority: optional
Maintainer: Viktor Krapivensky
Standards-Version: 4.5.0
Build-Depends:
cmake (>= 3.1.3),
debhelper (>= 10),
valgrind,
jq,
liblua5.3-dev,
pkg-config,
python3-docutils,
libyajl-dev,
libasound2-dev,
libglib2.0-dev,
libpulse-dev,
libudev-dev,
linux-libc-dev,
libnl-3-dev,
libnl-genl-3-dev,
libx11-dev,
libxcb1-dev,
libxcb-ewmh-dev,
libxcb-icccm4-dev,
libxcb-util0-dev
Homepage: https://github.com/shdown/luastatus
Rules-Requires-Root: no
Package: luastatus
Architecture: any
Depends: ${shlibs:Depends}, ${misc:Depends}
Recommends:
Suggests:
Description: Universal status bar content generator
luastatus is a universal status bar content generator. It allows the user to
configure the way the data from event sources is processed and shown, with
Lua. Its main feature is that the content can be updated immediately as some
event occurs.
.
This package contains the main binary and the following plugins: timer, fs,
inotify, udev, backlight-linux, battery-linux, cpu-usage-linux,
file-contents-linux, mem-usage-linux, pipe.
Package: luastatus-barlib-i3
Architecture: any
Depends: ${shlibs:Depends}, ${misc:Depends}, luastatus
Recommends:
Suggests: i3-wm
Description: i3 barlib for luastatus
This package contains the i3 barlib for luastatus. This barlib talks to i3bar
(part of the i3 window manager), or any other program compatible with it on
the protocol level. This package also provides luastatus-i3-wrapper script.
.
For more information, see "man 7 luastatus-barlib-i3".
Package: luastatus-barlib-dwm
Architecture: any
Depends: ${shlibs:Depends}, ${misc:Depends}, luastatus
Recommends:
Suggests: dwm
Description: dwm barlib for luastatus
This package contains the dwm barlib for luastatus. It updates the name of
the root window. Although named dwm, it can really work with any program that
outputs the root window name somewhere.
.
For more information, see "man 7 luastatus-barlib-dwm".
Package: luastatus-barlib-lemonbar
Architecture: any
Depends: ${shlibs:Depends}, ${misc:Depends}, luastatus
Recommends:
Suggests: lemonbar
Description: lemonbar barlib for luastatus
This package contains the lemonbar barlib for luastatus. The package also
provides luastatus-lemonbar-launcher script.
.
For more information, see "man 7 luastatus-barlib-lemonbar".
Package: luastatus-barlib-stdout
Architecture: any
Depends: ${shlibs:Depends}, ${misc:Depends}, luastatus
Recommends:
Suggests: dzen2 | xmobar | yabar | dvtm
Description: stdout barlib for luastatus
This is stdout barlib for luastatus. It simply outputs lines to given file
descriptor. It can be used with, for example, dzen2, xmobar, yabar, dvtm.
This package also provides luastatus-stdout-wrapper script.
.
For more information, see "man 7 luastatus-barlib-stdout".
Package: luastatus-plugin-alsa
Architecture: any
Depends: ${shlibs:Depends}, ${misc:Depends}, luastatus
Recommends:
Suggests:
Description: ALSA plugin for luastatus
This package contains the ALSA plugin for luastatus. It monitors volume and
mute state of an ALSA channel.
.
For more information, see "man 7 luastatus-plugin-alsa".
Package: luastatus-plugin-dbus
Architecture: any
Depends: ${shlibs:Depends}, ${misc:Depends}, luastatus
Recommends:
Suggests:
Description: D-Bus plugin for luastatus
This package contains the D-Bus plugin for luastatus. It subscribes to and
reports D-Bus signals.
.
For more information, see "man 7 luastatus-plugin-dbus".
Package: luastatus-plugin-mpd
Architecture: any
Depends: ${shlibs:Depends}, ${misc:Depends}, luastatus
Recommends:
Suggests: mpd
Description: MPD plugin for luastatus
This package contains the MPD plugin for luastatus. It monitors the state of
an MPD server.
.
For more information, see "man 7 luastatus-plugin-mpd".
Package: luastatus-plugin-network-linux
Architecture: any
Depends: ${shlibs:Depends}, ${misc:Depends}, luastatus
Recommends:
Suggests:
Description: network-linux plugin for luastatus
This package contains the network-linux plugin for luastatus. It monitors
network routing and link updates, and can report IP addresses used for
outgoing connections by various network interfaces, information about a
wireless connection, and speed of an ethernet connection.
.
For more information, see "man 7 luastatus-plugin-network-linux".
Package: luastatus-plugin-pulse
Architecture: any
Depends: ${shlibs:Depends}, ${misc:Depends}, luastatus
Recommends:
Suggests: pulseaudio
Description: PulseAudio plugin for luastatus
This package contains the PulseAudio plugin for luastatus. It monitors the
volume and mute status of a PulseAudio sink.
.
For more information, see "man 7 luastatus-plugin-pulse".
Package: luastatus-plugin-xkb
Architecture: any
Depends: ${shlibs:Depends}, ${misc:Depends}, luastatus
Recommends:
Suggests:
Description: XKB plugin for luastatus
This package contains the XKB plugin for luastatus. It monitors the current
keyboard layout, and, optionally, the state of LED indicators, such as
Caps Lock and Num Lock.
.
For more information, see "man 7 luastatus-plugin-xkb".
Package: luastatus-plugin-xtitle
Architecture: any
Depends: ${shlibs:Depends}, ${misc:Depends}, luastatus
Recommends:
Suggests:
Description: xtitle plugin for luastatus
This package conntains the xtitle plugin for luastatus. It monitors the
active window title.
.
For more information, see "man 7 luastatus-plugin-xtitle".
Package: luastatus-plugin-imap
Architecture: all
Depends: ${misc:Depends}, luastatus,
lua5.3-socket,
lua5.3-sec
Recommends:
Suggests:
Description: IMAP plugin for luastatus
This package contains the IMAP plugin for luastatus. It monitors the number
of unread mails in an IMAP mailbox.
.
For more information, see "man 7 luastatus-plugin-imap".
================================================
FILE: debian/copyright
================================================
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
Upstream-Name: luastatus
Upstream-Contact: Viktor Krapivensky
Source: https://github.com/shdown/luastatus
Files: *
Copyright: 2015-2021, luastatus developers
License: LGPL-3.0+
License: LGPL-3.0+
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
.
You should have received a copy of the GNU Lesser General Public License
along with this package. If not, see .
.
On Debian systems, the full text of the GNU Lesser General Public License
version 3 can be found in the file `/usr/share/common-licenses/LGPL-3'.
================================================
FILE: debian/rules
================================================
#! /usr/bin/make -f
export V:=1
export DEB_BUILD_MAINT_OPTIONS:=hardening=+all
BUILD_DIR := _deb_build
override_dh_auto_clean:
rm -rf $(BUILD_DIR)
override_dh_installexamples:
dh_installexamples examples/*
override_dh_installchangelogs:
set -e; if [ -e RELEASE_NOTES ]; then \
dh_installchangelogs RELEASE_NOTES; \
else \
dh_installchangelogs; \
fi
override_dh_auto_configure:
set -e; mkdir $(BUILD_DIR); cd $(BUILD_DIR); cmake \
-DCMAKE_INSTALL_PREFIX=/usr \
-DWITH_LUA_LIBRARY=lua5.3 \
-DBUILD_PLUGIN_PULSE=on \
-DBUILD_PLUGIN_UNIXSOCK=on \
-DBUILD_TESTS=on \
-S .. -B .
override_dh_auto_build:
$(MAKE) -C $(BUILD_DIR)
override_dh_auto_install:
# luastatus itself
$(MAKE) -C $(BUILD_DIR)/luastatus install \
DESTDIR=../../debian/luastatus
# barlibs
set -e; for x in dwm i3 lemonbar stdout; do \
$(MAKE) -C $(BUILD_DIR)/barlibs/$$x install \
DESTDIR=../../../debian/luastatus-barlib-$$x; \
done
# "core" plugins (that should go into luastatus pkg)
set -e; for x in \
fs timer inotify udev \
backlight-linux \
battery-linux \
cpu-usage-linux \
file-contents-linux \
mem-usage-linux \
pipe \
unixsock; \
do \
$(MAKE) -C $(BUILD_DIR)/plugins/$$x install \
DESTDIR=../../../debian/luastatus; \
done
# other plugins
set -e; for x in \
alsa dbus inotify mpd network-linux pulse xkb xtitle \
imap; \
do \
$(MAKE) -C $(BUILD_DIR)/plugins/$$x install \
DESTDIR=../../../debian/luastatus-plugin-$$x; \
done
# man page symlink: luastatus-i3-wrapper(1) -> luastatus-barlib-i3(7)
mkdir debian/luastatus-barlib-i3/usr/share/man/man1
ln -s ../man7/luastatus-barlib-i3.7 \
debian/luastatus-barlib-i3/usr/share/man/man1/luastatus-i3-wrapper.1
# man page symlink: luastatus-lemonbar-launcher(1) -> luastatus-barlib-lemonbar(7)
mkdir debian/luastatus-barlib-lemonbar/usr/share/man/man1
ln -s ../man7/luastatus-barlib-lemonbar.7 \
debian/luastatus-barlib-lemonbar/usr/share/man/man1/luastatus-lemonbar-launcher.1
# man page symlink: luastatus-stdout-wrapper(1) -> luastatus-barlib-stdout(7)
mkdir debian/luastatus-barlib-stdout/usr/share/man/man1
ln -s ../man7/luastatus-barlib-stdout.7 \
debian/luastatus-barlib-stdout/usr/share/man/man1/luastatus-stdout-wrapper.1
override_dh_auto_test:
ifeq (,$(filter nocheck,$(DEB_BUILD_OPTIONS)))
./tests/pt.sh $(BUILD_DIR) skip:plugin-dbus skip:plugin-pulse
./tests/torture.sh $(BUILD_DIR)
endif
%:
exec dh $@
================================================
FILE: examples/dwm/alsa-gauge.lua
================================================
local GAUGE_NCHARS = 10
local function mk_gauge(level, full, empty)
local nfull = math.floor(level * GAUGE_NCHARS + 0.5)
return full:rep(nfull) .. empty:rep(GAUGE_NCHARS - nfull)
end
widget = {
plugin = 'alsa',
cb = function(t)
local level = (t.vol.cur - t.vol.min) / (t.vol.max - t.vol.min)
if t.mute then
return mk_gauge(level, '×', '—')
else
return mk_gauge(level, '●', '○')
end
end,
}
================================================
FILE: examples/dwm/alsa.lua
================================================
widget = {
plugin = 'alsa',
cb = function(t)
if t.mute then
return '[mute]'
else
local percent = (t.vol.cur - t.vol.min) / (t.vol.max - t.vol.min) * 100
return string.format('[%3d%%]', math.floor(0.5 + percent))
end
end,
}
================================================
FILE: examples/dwm/backlight.lua
================================================
-- Note that this widget only shows backlight level when it changes.
widget = luastatus.require_plugin('backlight-linux').widget{
cb = function(level)
if level ~= nil then
return string.format('*%3.0f%%', level * 100)
end
end,
}
================================================
FILE: examples/dwm/battery.lua
================================================
widget = luastatus.require_plugin('battery-linux').widget{
period = 2,
cb = function(t)
local symbol = ({
Charging = '↑',
Discharging = '↓',
})[t.status] or ' '
local rem_seg
if t.rem_time then
local h = math.floor(t.rem_time)
local m = math.floor(60 * (t.rem_time - h))
rem_seg = string.format('%2dh %02dm', h, m)
end
return {
string.format('%3d%%%s', t.capacity, symbol),
rem_seg,
}
end,
}
================================================
FILE: examples/dwm/bluetooth.lua
================================================
-- A widget to display currently connected and paired bluetooth devices.
-- To change output format modify reprint_devices function.
if not luastatus.execute('command -v bluetoothctl >/dev/null') then
error('"bluetoothctl" command, which is required for this widget to work, was not found')
end
local separator = " "
-- Object paths look like /org/bluez/hci0/dev_XX_XX_XX_XX_XX_XX/somethingsomething
local function get_device_mac_address(device_object_path)
return device_object_path:gsub("/.*/dev_", ""):gsub("/.*", ""):gsub("_", ":")
end
-- For reference bluetoothctl devices output looks like that:
-- Device XX:XX:XX:XX:XX:XX JBL T450BT
-- Device YY:YY:YY:YY:YY:YY Redmi 8
--
-- Function returns mac addresses of all devices.
local function get_devices()
local devices = {}
local handle = io.popen(string.format("bluetoothctl devices"))
for line in handle:lines() do
local match = string.match(line, "Device ([%x:]+)")
if match then
table.insert(devices, math)
end
end
handle:close()
return devices
end
-- For reference bluetoothctl info output looks like that:
-- Device XX:XX:XX:XX:XX:XX (public)
-- Name: JBL T450BT
-- Alias: JBL T450BT
-- Class: 0xFFFFFFFF
-- Icon: audio-card
-- Paired: yes
-- Trusted: yes
-- Blocked: no
-- Connected: yes
-- LegacyPairing: no
-- UUID: Headset (xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)
-- ...
-- UUID: Handsfree (xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)
--
-- Given this input function returns a following table:
-- [alias] string JBL T450BT
-- [blocked] boolean false
-- [class] string 0x00240404
-- [connected] boolean true
-- [icon] string audio
-- [legacypairing] boolean false
-- [name] string JBL T450BT
-- [paired] boolean true
-- [trusted] boolean true
local function get_device_info(mac_address)
if mac_address == nil then
mac_address = ""
end
assert(string.match(mac_address, '^[%x:]*$') ~= nil)
local device_info = {}
local handle = io.popen(string.format("bluetoothctl info %s", mac_address))
for line in handle:lines() do
local key, value = string.match(line, "(%w+): (.*)")
-- Filter junk
if key ~= "UUID" and key ~= nil and value ~= nil then
key = string.lower(key)
if key ~= "name" and key ~= "alias" and key ~= "icon" then
if value == "yes" then
value = true
end
if value == "no" then
value = false
end
end
device_info[key] = value
end
end
handle:close()
return device_info
end
local devices = {}
local function reprint_devices()
local t = {}
for mac_address, device in pairs(devices) do
table.insert(t, string.format("%s(%s)", device["name"], mac_address))
end
return table.concat(t, separator)
end
widget = {
plugin = "dbus",
opts = {
greet = true,
-- https://git.kernel.org/pub/scm/bluetooth/bluez.git/tree/doc/device-api.txt
signals = {
{
sender = "org.bluez",
interface = "org.freedesktop.DBus.Properties",
signal = "PropertiesChanged",
arg0 = "org.bluez.Device1",
bus = "system"
}
}
},
cb = function(t)
if t.what == "hello" then
local mac_addresses = get_devices()
for _, mac_address in ipairs(mac_addresses) do
local device = get_device_info(mac_address)
if device["connected"] and device["paired"] then
devices[mac_address] = device
end
end
elseif t.what == "signal" then
-- For reference message from dbus looks like that:
-- table
-- [1] string org.bluez.Device1
-- [2] table
-- [2] [1] table
-- [2] [1] [1] string SomethingSomething
-- [2] [1] [2] boolean false
-- [2] [2] table
-- [2] [2] [1] string Connected
-- [2] [2] [2] boolean true
-- [3] table
if t.signal == "PropertiesChanged" then
for _, message in pairs(t.parameters[2]) do
if message[1] == "Connected" or message[1] == "Paired" then
local mac_address = get_device_mac_address(t.object_path)
if message[2] then
local device = get_device_info(mac_address)
if device["paired"] then
devices[mac_address] = device
end
else
devices[mac_address] = nil
end
end
end
end
end
return reprint_devices()
end
}
================================================
FILE: examples/dwm/btc-price.lua
================================================
-- Bitcoin price widget.
-- Updates on click and every 5 minutes.
local custom_sleep_amt = nil
local function check_error(t)
if t.error then
-- Low-level libcurl error
return t.error
end
if t.status < 200 or t.status > 299 then
-- Bad HTTP status
return string.format('HTTP status %d', t.status)
end
-- Everything's OK
return nil
end
widget = {
plugin = 'web',
opts = {
planner = function()
while true do
coroutine.yield({action = 'request', params = {
url = 'https://api.binance.com/api/v3/ticker/price?symbol=BTCUSDT',
timeout = 5,
}})
local period = custom_sleep_amt or (5 * 60)
custom_sleep_amt = nil
coroutine.yield({action = 'sleep', period = period})
end
end,
make_self_pipe = true,
},
cb = function(t)
local text
local err_msg = check_error(t)
if err_msg then
print(string.format('WARNING: luastatus: btc-price widget: %s'), err_msg)
text = '......'
custom_sleep_amt = 5
else
local obj = assert(luastatus.plugin.json_decode(t.body))
text = obj.price:match('[^.]+')
end
return string.format('[$%s]', text)
end,
event = function(t)
if t.button == 1 then
luastatus.plugin.wake_up()
end
end,
}
================================================
FILE: examples/dwm/cpu-freq.lua
================================================
local VBLOCKS = {'▂', '▃', '▄', '▅', '▆', '▇', '█'}
local function make_chunk(entry)
local num = entry.cur - entry.min
local denom = entry.max - entry.min
local ratio
if denom ~= 0 then
ratio = num / denom
else
-- If max_freq == min_freq, set ratio to zero.
ratio = 0
end
local vblock_idx = math.min(
1 + math.floor(0.5 + ratio * #VBLOCKS),
#VBLOCKS)
return VBLOCKS[vblock_idx]
end
local plugin_data = {}
local plugin_params = {
timer_opts = {
period = 2,
},
cb = function(t)
if t == nil then
return nil
end
local r = {}
for _, entry in ipairs(t) do
r[#r + 1] = make_chunk(entry)
end
return table.concat(r)
end,
event = function(t)
if t.button == 1 then
plugin_data.please_reload = true
end
end,
}
widget = luastatus.require_plugin('cpu-freq-linux').widget(plugin_params, plugin_data)
================================================
FILE: examples/dwm/cpu-temperature.lua
================================================
local plugin_data = {}
local plugin_params = {
timer_opts = {
period = 2,
},
cb = function(t)
if not t then
return nil
end
local r = {}
for _, entry in ipairs(t) do
r[#r + 1] = string.format('%.0f°', entry.value)
end
return r
end,
event = function(t)
if t.button == 1 then
plugin_data.please_reload = true
end
end,
}
widget = luastatus.require_plugin('cpu-temp-linux').widget(plugin_params, plugin_data)
================================================
FILE: examples/dwm/cpu-usage.lua
================================================
widget = luastatus.require_plugin('cpu-usage-linux').widget{
cb = function(usage)
if usage ~= nil then
return string.format('[%5.1f%%]', usage * 100)
end
end,
}
================================================
FILE: examples/dwm/disk-io.lua
================================================
widget = luastatus.require_plugin('disk-io-linux').widget{
period = 2,
cb = function(t)
-- Sort by name for determinism
table.sort(t, function(a, b) return a.name < b.name end)
local segments = {}
for _, entry in ipairs(t) do
local R = entry.read_bytes
local W = entry.written_bytes
if (R >= 0) and (W >= 0) then
segments[#segments + 1] = string.format(
'%s: %.0fk↓ %.0fk↑',
entry.name,
R / 1024,
W / 1024
)
end
end
return segments
end,
}
================================================
FILE: examples/dwm/file-contents.lua
================================================
widget = luastatus.require_plugin('file-contents-linux').widget{
filename = os.getenv('HOME') .. '/status',
cb = function(f)
-- show the first line of the file
return f:read('*line')
end,
}
================================================
FILE: examples/dwm/fs.lua
================================================
local function sorted_keys(tbl)
local keys = {}
for k, _ in pairs(tbl) do
keys[#keys + 1] = k
end
table.sort(keys)
return keys
end
widget = {
plugin = 'fs',
opts = {
paths = {'/', '/home'},
},
cb = function(t)
-- Sort for determinism
local keys = sorted_keys(t)
local res = {}
for _, k in ipairs(keys) do
local v = t[k]
table.insert(res, string.format('%s %.0f%%', k,
(1 - v.avail / v.total) * 100))
end
return res
end,
}
================================================
FILE: examples/dwm/gmail.lua
================================================
--[[
-- Expects 'credentials.lua' to be present in the current directory; it may contain, e.g.,
-- return {
-- gmail = {
-- login = 'john.smith',
-- password = 'qwerty'
-- }
-- }
--]]
local credentials = require 'credentials'
widget = luastatus.require_plugin('imap').widget{
verbose = false,
host = 'imap.gmail.com',
port = 993,
mailbox = 'Inbox',
use_ssl = true,
timeout = 2 * 60,
handshake_timeout = 10,
login = credentials.gmail.login,
password = credentials.gmail.password,
error_sleep_period = 60,
cb = function(unseen)
if unseen == nil or unseen == 0 then
return nil
else
return string.format('[%d unseen]', unseen)
end
end,
}
================================================
FILE: examples/dwm/ip.lua
================================================
widget = {
plugin = 'network-linux',
cb = function(t)
local r = {}
for iface, params in pairs(t) do
local iface_clean = iface
local addr = params.ipv6 or params.ipv4
if addr then
-- strip out "label" from the interface name
iface_clean = iface_clean:gsub(':.*', '')
-- strip out "zone index" from the address
addr = addr:gsub('%%.*', '')
if iface_clean ~= 'lo' then
r[#r + 1] = string.format('[%s: %s]', iface_clean, addr)
end
end
end
return r
end,
}
================================================
FILE: examples/dwm/loadavg-linux.lua
================================================
local function get_ncpus()
local f = assert(io.open('/proc/cpuinfo', 'r'))
local n = 0
for line in f:lines() do
if line:match('^processor\t') then
n = n + 1
end
end
f:close()
return n
end
local function avg2str(x)
assert(x >= 0)
if x >= 1000 then
return '↑↑↑'
end
return string.format('%3.0f', x)
end
widget = {
plugin = 'timer',
opts = {
period = 2,
},
cb = function(_)
local f = io.open('/proc/loadavg', 'r')
local avg1, avg5, avg15 = f:read('*number', '*number', '*number')
f:close()
assert(avg1 and avg5 and avg15)
local ncpus = get_ncpus()
return string.format(
'[%s%% %s%% %s%%]',
avg2str(avg1 / ncpus * 100),
avg2str(avg5 / ncpus * 100),
avg2str(avg15 / ncpus * 100)
)
end,
}
================================================
FILE: examples/dwm/media-player-mpris.lua
================================================
local PLAYER = 'clementine'
local PLAYBACK_STATUS_ICONS = {
Playing = '▶',
Paused = '◆',
Stopped = '—',
}
local function fetch_metadata_field(t, key)
if t.Metadata then
return t.Metadata[key]
else
return nil
end
end
widget = luastatus.require_plugin('mpris').widget{
player = PLAYER,
cb = function(t)
if not t.PlaybackStatus then
return nil
end
local title = fetch_metadata_field(t, 'xesam:title')
title = title or ''
title = luastatus.libwidechar.make_valid_and_printable(title, '?')
title = luastatus.libwidechar.truncate_to_width(title, 40)
title = title or ''
local icon = PLAYBACK_STATUS_ICONS[t.PlaybackStatus] or '?'
local result = icon
if title ~= '' then
result = result .. ' ' .. title
end
return result
end,
}
================================================
FILE: examples/dwm/mem-usage.lua
================================================
widget = luastatus.require_plugin('mem-usage-linux').widget{
timer_opts = {period = 2},
cb = function(t)
local used_kb = t.total.value - t.avail.value
return string.format('[%3.2f GiB]', used_kb / 1024 / 1024)
end,
}
================================================
FILE: examples/dwm/mpd.lua
================================================
local titlewidth = 40
widget = {
plugin = 'mpd',
cb = function(t)
if t.what == 'update' then
local title
if t.song.Title then
title = t.song.Title
if t.song.Artist then
title = t.song.Artist .. ': ' .. title
end
else
title = t.song.file or ''
end
title = luastatus.libwidechar.make_valid(title, '?')
if assert(luastatus.libwidechar.width(title)) > titlewidth then
title = luastatus.libwidechar.truncate_to_width(title, titlewidth - 1) .. '…'
end
return string.format('%s %s',
({play = '▶', pause = '‖', stop = '■'})[t.status.state],
title
)
else
-- 'connecting' or 'error'
return t.what
end
end
}
================================================
FILE: examples/dwm/network-rate.lua
================================================
local function make_segment(iface, R, S)
return string.format('[%s %.0fk↓ %.0fk↑]', iface, R / 1000, S / 1000)
end
widget = luastatus.require_plugin('network-rate-linux').widget{
iface_except = 'lo',
period = 3,
in_array_form = true,
cb = function(t)
local r = {}
for _, PQ in ipairs(t) do
local iface = PQ[1]
local R, S = PQ[2].R, PQ[2].S
r[#r + 1] = make_segment(iface, R, S)
end
return r
end,
}
================================================
FILE: examples/dwm/pulse-gauge.lua
================================================
local GAUGE_NCHARS = 10
local function mk_gauge(level, full, empty)
local nfull = math.floor(level * GAUGE_NCHARS + 0.5)
return full:rep(nfull) .. empty:rep(GAUGE_NCHARS - nfull)
end
widget = {
plugin = 'pulse',
cb = function(t)
local level = t.cur / t.norm
if t.mute then
return mk_gauge(level, '×', '—')
else
return mk_gauge(level, '●', '○')
end
end,
}
================================================
FILE: examples/dwm/pulse.lua
================================================
widget = {
plugin = 'pulse',
cb = function(t)
if t.mute then
return '[mute]'
end
local percent = (t.cur / t.norm) * 100
return string.format('[%3d%%]', math.floor(0.5 + percent))
end,
}
================================================
FILE: examples/dwm/systemd-unit.lua
================================================
local function make_output(text)
return string.format('[Tor: %s]', text)
end
widget = luastatus.require_plugin('systemd-unit').widget{
unit_name = 'tor.service',
cb = function(state)
if state == 'active' then
return make_output('✓')
elseif state == 'reloading' or state == 'activating' then
return make_output('•')
elseif state == 'inactive' or state == 'deactivating' then
return make_output('-')
elseif state == 'failed' then
return make_output('x')
else
return make_output('?')
end
end,
}
================================================
FILE: examples/dwm/time-battery-combined.lua
================================================
local function get_bat_seg(t)
if not t then
return '[--×--]'
end
if t.status == 'Unknown' or t.status == 'Full' or t.status == 'Not charging' then
return nil
end
local sym = '?'
if t.status == 'Discharging' then
sym = '↓'
elseif t.status == 'Charging' then
sym = '↑'
end
return string.format('[%3d%%%s]', t.capacity, sym)
end
widget = luastatus.require_plugin('battery-linux').widget{
period = 2,
cb = function(t)
return {
os.date('[%H:%M]'),
get_bat_seg(t),
}
end,
}
================================================
FILE: examples/dwm/time-date.lua
================================================
local months = {
'января',
'февраля',
'марта',
'апреля',
'мая',
'июня',
'июля',
'августа',
'сентября',
'октября',
'ноября',
'декабря',
}
widget = {
plugin = 'timer',
cb = function()
local d = os.date('*t')
return {
string.format('%d %s', d.day, months[d.month]),
string.format('%02d:%02d', d.hour, d.min),
}
end,
}
================================================
FILE: examples/dwm/tor.lua
================================================
-- Trivial but somewhat useful widget showing if the Tor daemon is running.
widget = {
plugin = 'timer',
opts = {period = 5},
cb = function()
local f = io.open('/var/run/tor/tor.pid', 'r')
if f then
f:close()
return '[TOR]'
end
end,
}
================================================
FILE: examples/dwm/uptime-linux.lua
================================================
local SUFFIXES_AND_DIVISORS = {
{'m', 60},
{'h', 60},
{'d', 24},
{'W', 7},
{'M', 30},
{'Y', 365 / 30},
}
local function seconds_to_human_readable_time(sec)
local prev_suffix = 's'
local found_suffix
for _, PQ in ipairs(SUFFIXES_AND_DIVISORS) do
local suffix = PQ[1]
local divisor = PQ[2]
if sec < divisor then
found_suffix = prev_suffix
break
end
sec = sec / divisor
prev_suffix = suffix
end
if not found_suffix then
found_suffix = prev_suffix
end
return string.format('%.0f%s', sec, found_suffix)
end
widget = {
plugin = 'timer',
opts = {
period = 2,
},
cb = function(_)
local f = io.open('/proc/uptime', 'r')
local sec, _ = f:read('*number', '*number')
f:close()
assert(sec)
return string.format('[uptime: %s]', seconds_to_human_readable_time(sec))
end,
}
================================================
FILE: examples/dwm/weather-rus.lua
================================================
local LOCATION_NAME = 'Moscow'
local TIMEOUT = 10
local INTERVAL = 9
local SLEEP_AFTER_INITIAL = 5
local coordinates = nil
local function planner()
-- Get coordinates of LOCATION_NAME.
while true do
-- Form the URL
local url = string.format(
'https://geocoding-api.open-meteo.com/v1/search?name=%s&count=1',
luastatus.plugin.urlencode(LOCATION_NAME)
)
-- Make request
coroutine.yield({action = 'request', params = {
url = url,
timeout = TIMEOUT,
}})
-- If successful, break
if coordinates ~= nil then
break
end
-- Failure; sleep and retry
coroutine.yield({action = 'sleep', period = INTERVAL})
end
-- Coordinates fetched, sleep for SLEEP_AFTER_INITIAL.
coroutine.yield({action = 'sleep', period = SLEEP_AFTER_INITIAL})
-- Form the weather URL
local weather_url = string.format(
'https://api.open-meteo.com/v1/forecast' ..
'?latitude=%f' ..
'&longitude=%f' ..
'¤t_weather=true',
coordinates.latitude,
coordinates.longitude
)
-- Fetch weather, periodically
while true do
-- Make request
coroutine.yield({action = 'request', params = {
url = weather_url,
timeout = TIMEOUT,
}})
-- Sleep and repeat
coroutine.yield({action = 'sleep', period = INTERVAL})
end
end
local function check_error(t)
if t.error then
-- Low-level libcurl error
return t.error
end
if t.status < 200 or t.status > 299 then
-- Bad HTTP status
return string.format('HTTP status %d', t.status)
end
-- Everything's OK
return nil
end
local WEATHER_CODES = {
[0] = 'Ясно',
[1] = 'Преимущественно ясно',
[2] = 'Переменная облачность',
[3] = 'Пасмурно',
[45] = 'Туман',
[48] = 'Осаждение инея и тумана',
[51] = 'Изморось, лёгкая',
[53] = 'Изморось, умеренная',
[55] = 'Изморось, сильная',
[56] = 'Ледяная изморось, лёгкая',
[57] = 'Ледяная изморось, сильная',
[61] = 'Дождь, лёгкий',
[63] = 'Дождь, умеренный',
[65] = 'Дождь, сильный',
[66] = 'Град, лёгкий',
[67] = 'Град, сильный',
[71] = 'Снегопад, лёгкий',
[73] = 'Снегопад, умеренный',
[75] = 'Снегопад, сильный',
[77] = 'Крупинки снега',
[80] = 'Ливень, лёгкий',
[81] = 'Ливень, умеренный',
[82] = 'Ливень, сильный',
[85] = 'Снеговые осадки, лёгкие',
[87] = 'Снеговые осадки, сильные',
[95] = 'Гроза',
[96] = 'Гроза, небольшой град',
[99] = 'Гроза, сильный град',
}
local function fmt_weather(temp, _, _, weather_code)
local segment1 = string.format('%.0f°', temp)
-- segment2 is either string or nil
local segment2 = WEATHER_CODES[weather_code]
return {segment1, segment2}
end
widget = {
plugin = 'web',
opts = {
planner = planner,
},
cb = function(t)
local err_msg = check_error(t)
if err_msg then
print(string.format('WARNING: luastatus: weather widget: %s'), err_msg)
return ''
end
if coordinates == nil then
local obj = assert(luastatus.plugin.json_decode(t.body))
local result = assert(obj.results[1])
local latitude = assert(result.latitude)
local longitude = assert(result.longitude)
coordinates = {latitude = latitude, longitude = longitude}
return '...'
end
local obj = assert(luastatus.plugin.json_decode(t.body))
local cw = assert(obj.current_weather)
local temp = assert(cw.temperature)
local wind_speed = assert(cw.windspeed)
-- is_day is an integer, either 0 or 1
local is_day = assert(cw.is_day) ~= 0
local weather_code = assert(cw.weathercode)
return fmt_weather(temp, wind_speed, is_day, weather_code)
end,
}
================================================
FILE: examples/dwm/weather.lua
================================================
local LOCATION_NAME = 'Moscow'
local TIMEOUT = 10
local INTERVAL = 9
local SLEEP_AFTER_INITIAL = 5
local coordinates = nil
local function planner()
-- Get coordinates of LOCATION_NAME.
while true do
-- Form the URL
local url = string.format(
'https://geocoding-api.open-meteo.com/v1/search?name=%s&count=1',
luastatus.plugin.urlencode(LOCATION_NAME)
)
-- Make request
coroutine.yield({action = 'request', params = {
url = url,
timeout = TIMEOUT,
}})
-- If successful, break
if coordinates ~= nil then
break
end
-- Failure; sleep and retry
coroutine.yield({action = 'sleep', period = INTERVAL})
end
-- Coordinates fetched, sleep for SLEEP_AFTER_INITIAL.
coroutine.yield({action = 'sleep', period = SLEEP_AFTER_INITIAL})
-- Form the weather URL
local weather_url = string.format(
'https://api.open-meteo.com/v1/forecast' ..
'?latitude=%f' ..
'&longitude=%f' ..
'¤t_weather=true',
coordinates.latitude,
coordinates.longitude
)
-- Fetch weather, periodically
while true do
-- Make request
coroutine.yield({action = 'request', params = {
url = weather_url,
timeout = TIMEOUT,
}})
-- Sleep and repeat
coroutine.yield({action = 'sleep', period = INTERVAL})
end
end
local function check_error(t)
if t.error then
-- Low-level libcurl error
return t.error
end
if t.status < 200 or t.status > 299 then
-- Bad HTTP status
return string.format('HTTP status %d', t.status)
end
-- Everything's OK
return nil
end
local WEATHER_CODES = {
[0] = 'Clear sky',
[1] = 'Mainly clear',
[2] = 'Partly cloudy',
[3] = 'Overcast',
[45] = 'Fog',
[48] = 'Depositing rime fog',
[51] = 'Drizzle, light',
[53] = 'Drizzle, moderate',
[55] = 'Drizzle, dense',
[56] = 'Freezing drizzle, light',
[57] = 'Freezing drizzle, dense',
[61] = 'Rain, slight',
[63] = 'Rain, moderate',
[65] = 'Rain, heavy',
[66] = 'Freezing rain, light',
[67] = 'Freezing rain, heavy',
[71] = 'Snow fall, slight',
[73] = 'Snow fall, moderate',
[75] = 'Snow fall, heavy',
[77] = 'Snow grains',
[80] = 'Rain shower, slight',
[81] = 'Rain shower, moderate',
[82] = 'Rain shower, violent',
[85] = 'Snow showers, slight',
[87] = 'Snow showers, heavy',
[95] = 'Thunderstorm',
[96] = 'Thunderstorm, slight hail',
[99] = 'Thunderstorm, heavy hail',
}
local function fmt_weather(temp, _, _, weather_code)
local segment1 = string.format('%.0f°', temp)
-- segment2 is either string or nil
local segment2 = WEATHER_CODES[weather_code]
return {segment1, segment2}
end
widget = {
plugin = 'web',
opts = {
planner = planner,
},
cb = function(t)
local err_msg = check_error(t)
if err_msg then
print(string.format('WARNING: luastatus: weather widget: %s'), err_msg)
return ''
end
if coordinates == nil then
local obj = assert(luastatus.plugin.json_decode(t.body))
local result = assert(obj.results[1])
local latitude = assert(result.latitude)
local longitude = assert(result.longitude)
coordinates = {latitude = latitude, longitude = longitude}
return '...'
end
local obj = assert(luastatus.plugin.json_decode(t.body))
local cw = assert(obj.current_weather)
local temp = assert(cw.temperature)
local wind_speed = assert(cw.windspeed)
-- is_day is an integer, either 0 or 1
local is_day = assert(cw.is_day) ~= 0
local weather_code = assert(cw.weathercode)
return fmt_weather(temp, wind_speed, is_day, weather_code)
end,
}
================================================
FILE: examples/dwm/wireless.lua
================================================
local MIN_DBM, MAX_DBM = -90, -20
local NGAUGE = 5
local function round(x)
return math.floor(x + 0.5)
end
local function make_wifi_gauge(dbm)
if dbm < MIN_DBM then dbm = MIN_DBM end
if dbm > MAX_DBM then dbm = MAX_DBM end
local nbright = round(NGAUGE * (1 - 0.7 * (MAX_DBM - dbm) / (MAX_DBM - MIN_DBM)))
return ('●'):rep(nbright) .. ('○'):rep(NGAUGE - nbright)
end
local function sorted_keys(tbl)
local keys = {}
for k, _ in pairs(tbl) do
keys[#keys + 1] = k
end
table.sort(keys)
return keys
end
widget = {
plugin = 'network-linux',
opts = {
wireless = true,
timeout = 10,
},
cb = function(t)
if not t then
return nil
end
-- Sort for determinism
local ifaces = sorted_keys(t)
local r = {}
for _, iface in ipairs(ifaces) do
local params = t[iface]
if params.wireless then
if params.wireless.ssid then
r[#r + 1] = params.wireless.ssid
end
if params.wireless.signal_dbm then
r[#r + 1] = make_wifi_gauge(params.wireless.signal_dbm)
end
elseif iface ~= 'lo' and (params.ipv4 or params.ipv6) then
r[#r + 1] = string.format('[%s]', iface)
end
end
return r
end,
}
================================================
FILE: examples/dwm/xkb.lua
================================================
widget = {
plugin = 'xkb',
cb = function(t)
if t.name then
local base_layout = t.name:match('[^(]+')
if base_layout == 'gb' or base_layout == 'us' then
return '[En]'
elseif base_layout == 'ru' then
return '[Ru]'
else
return '[' .. base_layout:sub(1, 1):upper() .. base_layout:sub(2) .. ']'
end
else
return '[? ID ' .. t.id .. ']'
end
end,
}
================================================
FILE: examples/i3/alsa-gauge.lua
================================================
local GAUGE_NCHARS = 10
local function mk_gauge(level, full, empty)
local nfull = math.floor(level * GAUGE_NCHARS + 0.5)
return full:rep(nfull) .. empty:rep(GAUGE_NCHARS - nfull)
end
widget = {
plugin = 'alsa',
cb = function(t)
local level = (t.vol.cur - t.vol.min) / (t.vol.max - t.vol.min)
if t.mute then
return {full_text=mk_gauge(level, '×', '—'), color='#e03838'}
else
return {full_text=mk_gauge(level, '●', '○'), color='#718ba6'}
end
end,
}
================================================
FILE: examples/i3/alsa-interactive-gauge.lua
================================================
-- So, it looks like this:
--
-- ••• | ██████████████ | [ 70%] | •••
--
-- ^ gauge block ^ text block
--
-- The gauge block is initially hidden; click on the text block to toggle its visibility.
-- Click anywhere at the gauge block to change the volume.
-- Click with the right mouse button on either block to toggle mute state.
local CARD = 'default'
local CHANNEL = 'Master'
local HBLOCKS = {' ', '▏', '▎', '▍', '▌', '▋', '▊', '▉', '█'}
local GAUGE_NCHARS = 20
-- i3bar adds the separator width to "relative_x" and "width" properties of a click event.
--
-- So let's do some multivariable equations (we assume a monospace font is used).
--
-- Let:
-- * `x' be the separator width in pixels (unknown);
-- * `y' be the width of one character in pixels (unknown);
-- * `U' be the number of characters in the text block (known);
-- * `V' be the number of characters in the gauge block (known);
-- * `A' be the width, in pixels, of the text block (known when clicked);
-- * `B' be the width, in pixels, on the gauge block (known when clicked).
--
-- We have:
-- { x + U*y = A;
-- { x + V*y = B.
-- So,
-- y = (A - B) / (U - V);
-- x = A - U*y.
local last_t = nil
local text_block_nchars, text_block_width = nil, nil
local gauge = false
local function round(x)
return math.floor(x + 0.5)
end
local function mk_gauge(level)
local rel_level = level * GAUGE_NCHARS
local nfull = math.floor(rel_level)
local filled = HBLOCKS[#HBLOCKS]:rep(nfull)
if nfull == GAUGE_NCHARS then
return filled
end
local mid_idx = round((rel_level - nfull) * (#HBLOCKS - 1))
return filled .. HBLOCKS[1 + mid_idx] .. HBLOCKS[1]:rep(GAUGE_NCHARS - nfull - 1)
end
widget = {
plugin = 'alsa',
opts = {
card = CARD,
channel = CHANNEL,
make_self_pipe = true,
},
cb = function(t)
last_t = t
local level = (t.vol.cur - t.vol.min) / (t.vol.max - t.vol.min)
local r = {}
if t.mute then
r[2] = {full_text='[mute]', color='#e03838'}
else
r[2] = {full_text=string.format('[%3d%%]', round(level * 100)), color='#718ba6'}
end
text_block_nchars = #r[2].full_text -- please note this does not work with Unicode.
if gauge then
local fg, bg = '#dcdcdc', '#444444'
if t.mute then
fg, bg = '#e03838', '#4a1414'
end
r[1] = {full_text=mk_gauge(level), color=fg, background=bg, instance='gauge'}
end
return r
end,
event = function(t)
if t.button == 1 then -- left mouse button
if t.instance == 'gauge' then
local char_width = round((t.width - text_block_width)
/ (GAUGE_NCHARS - text_block_nchars))
local sep_width = text_block_width - text_block_nchars * char_width
local x = t.relative_x - sep_width
if x < 0 then
return
end
local rawvol = round(
last_t.vol.min +
x / (t.width - sep_width) * (last_t.vol.max - last_t.vol.min))
assert(luastatus.execute(
string.format('amixer -D "%s" set "%s" %s >/dev/null', CARD, CHANNEL, rawvol)))
else
gauge = not gauge
text_block_width = t.width
luastatus.plugin.wake_up()
end
elseif t.button == 3 then -- right mouse button
assert(luastatus.execute(string.format(
'amixer -D "%s" set "%s" toggle >/dev/null', CARD, CHANNEL)))
end
end,
}
================================================
FILE: examples/i3/alsa.lua
================================================
widget = {
plugin = 'alsa',
cb = function(t)
if t.mute then
return {full_text = '[mute]', color = '#e03838'}
else
local percent = (t.vol.cur - t.vol.min) / (t.vol.max - t.vol.min) * 100
return {full_text = string.format('[%3d%%]', math.floor(0.5 + percent)), color = '#718ba6'}
end
end,
}
================================================
FILE: examples/i3/backlight.lua
================================================
-- Note that this widget only shows backlight level when it changes.
widget = luastatus.require_plugin('backlight-linux').widget{
cb = function(level)
if level ~= nil then
return {full_text = string.format('*%3.0f%%', level * 100)}
end
end,
}
================================================
FILE: examples/i3/battery.lua
================================================
widget = luastatus.require_plugin('battery-linux').widget{
period = 2,
cb = function(t)
local symbol = ({
Charging = '↑',
Discharging = '↓',
})[t.status] or ' '
local rem_seg
if t.rem_time then
local h = math.floor(t.rem_time)
local m = math.floor(60 * (t.rem_time - h))
rem_seg = {full_text = string.format('%2dh %02dm', h, m), color = '#595959'}
end
return {
{full_text = string.format('%3d%%%s', t.capacity, symbol)},
rem_seg,
}
end,
}
================================================
FILE: examples/i3/bluetooth.lua
================================================
-- A widget to display currently connected and paired bluetooth devices.
-- To change output format modify reprint_devices function.
if not luastatus.execute('command -v bluetoothctl >/dev/null') then
error('"bluetoothctl" command, which is required for this widget to work, was not found')
end
local separator = " "
-- Object paths look like /org/bluez/hci0/dev_XX_XX_XX_XX_XX_XX/somethingsomething
local function get_device_mac_address(device_object_path)
return device_object_path:gsub("/.*/dev_", ""):gsub("/.*", ""):gsub("_", ":")
end
-- For reference bluetoothctl devices output looks like that:
-- Device XX:XX:XX:XX:XX:XX JBL T450BT
-- Device YY:YY:YY:YY:YY:YY Redmi 8
--
-- Function returns mac addresses of all devices.
local function get_devices()
local devices = {}
local handle = io.popen(string.format("bluetoothctl devices"))
for line in handle:lines() do
local match = string.match(line, "Device ([%x:]+)")
if match then
table.insert(devices, match)
end
end
handle:close()
return devices
end
-- For reference bluetoothctl info output looks like that:
-- Device XX:XX:XX:XX:XX:XX (public)
-- Name: JBL T450BT
-- Alias: JBL T450BT
-- Class: 0xFFFFFFFF
-- Icon: audio-card
-- Paired: yes
-- Trusted: yes
-- Blocked: no
-- Connected: yes
-- LegacyPairing: no
-- UUID: Headset (xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)
-- ...
-- UUID: Handsfree (xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)
--
-- Given this input function returns a following table:
-- [alias] string JBL T450BT
-- [blocked] boolean false
-- [class] string 0x00240404
-- [connected] boolean true
-- [icon] string audio
-- [legacypairing] boolean false
-- [name] string JBL T450BT
-- [paired] boolean true
-- [trusted] boolean true
local function get_device_info(mac_address)
if mac_address == nil then
mac_address = ""
end
assert(string.match(mac_address, '^[%x:]*$') ~= nil)
local device_info = {}
local handle = io.popen(string.format("bluetoothctl info %s", mac_address))
for line in handle:lines() do
local key, value = string.match(line, "(%w+): (.*)")
-- Filter junk
if key ~= "UUID" and key ~= nil and value ~= nil then
key = string.lower(key)
if key ~= "name" and key ~= "alias" and key ~= "icon" then
if value == "yes" then
value = true
end
if value == "no" then
value = false
end
end
device_info[key] = value
end
end
handle:close()
return device_info
end
local devices = {}
local function reprint_devices()
local t = {}
for mac_address, device in pairs(devices) do
table.insert(t, string.format("%s(%s)", device["name"], mac_address))
end
return table.concat(t, separator)
end
widget = {
plugin = "dbus",
opts = {
greet = true,
-- https://git.kernel.org/pub/scm/bluetooth/bluez.git/tree/doc/device-api.txt
signals = {
{
sender = "org.bluez",
interface = "org.freedesktop.DBus.Properties",
signal = "PropertiesChanged",
arg0 = "org.bluez.Device1",
bus = "system"
}
}
},
cb = function(t)
if t.what == "hello" then
local mac_addresses = get_devices()
for _, mac_address in ipairs(mac_addresses) do
local device = get_device_info(mac_address)
if device["connected"] and device["paired"] then
devices[mac_address] = device
end
end
elseif t.what == "signal" then
-- For reference message from dbus looks like that:
-- table
-- [1] string org.bluez.Device1
-- [2] table
-- [2] [1] table
-- [2] [1] [1] string SomethingSomething
-- [2] [1] [2] boolean false
-- [2] [2] table
-- [2] [2] [1] string Connected
-- [2] [2] [2] boolean true
-- [3] table
if t.signal == "PropertiesChanged" then
for _, message in pairs(t.parameters[2]) do
if message[1] == "Connected" or message[1] == "Paired" then
local mac_address = get_device_mac_address(t.object_path)
if message[2] then
local device = get_device_info(mac_address)
if device["paired"] then
devices[mac_address] = device
end
else
devices[mac_address] = nil
end
end
end
end
end
return {full_text = reprint_devices()}
end
}
================================================
FILE: examples/i3/btc-price.lua
================================================
-- Bitcoin price widget.
-- Updates on click and every 5 minutes.
local custom_sleep_amt = nil
local function check_error(t)
if t.error then
-- Low-level libcurl error
return t.error
end
if t.status < 200 or t.status > 299 then
-- Bad HTTP status
return string.format('HTTP status %d', t.status)
end
-- Everything's OK
return nil
end
widget = {
plugin = 'web',
opts = {
planner = function()
while true do
coroutine.yield({action = 'request', params = {
url = 'https://api.binance.com/api/v3/ticker/price?symbol=BTCUSDT',
timeout = 5,
}})
local period = custom_sleep_amt or (5 * 60)
custom_sleep_amt = nil
coroutine.yield({action = 'sleep', period = period})
end
end,
make_self_pipe = true,
},
cb = function(t)
local text
local err_msg = check_error(t)
if err_msg then
print(string.format('WARNING: luastatus: btc-price widget: %s'), err_msg)
text = '......'
custom_sleep_amt = 5
else
local obj = assert(luastatus.plugin.json_decode(t.body))
text = obj.price:match('[^.]+')
end
return {
full_text = string.format(
'[$%s]',
luastatus.barlib.pango_escape(text)
),
color = '#586A4B',
markup = 'pango',
}
end,
event = function(t)
if t.button == 1 then
luastatus.plugin.wake_up()
end
end,
}
================================================
FILE: examples/i3/cpu-freq.lua
================================================
local VBLOCKS = {'▂', '▃', '▄', '▅', '▆', '▇', '█'}
local function make_color(ratio)
local red = math.floor(0.5 + 255 * ratio)
return string.format('#%02x%02x00', red, 255 - red)
end
local function make_chunk(entry)
local num = entry.cur - entry.min
local denom = entry.max - entry.min
local ratio
if denom ~= 0 then
ratio = num / denom
else
-- If max_freq == min_freq, set ratio to zero.
ratio = 0
end
local vblock_idx = math.min(
1 + math.floor(0.5 + ratio * #VBLOCKS),
#VBLOCKS)
return string.format("%s", make_color(ratio), VBLOCKS[vblock_idx])
end
local plugin_data = {}
local plugin_params = {
timer_opts = {
period = 2,
},
cb = function(t)
if t == nil then
return nil
end
local r = {}
for _, entry in ipairs(t) do
r[#r + 1] = make_chunk(entry)
end
return {full_text = table.concat(r), markup = 'pango'}
end,
event = function(t)
if t.button == 1 then
plugin_data.please_reload = true
end
end,
}
widget = luastatus.require_plugin('cpu-freq-linux').widget(plugin_params, plugin_data)
================================================
FILE: examples/i3/cpu-temperature.lua
================================================
local COOL_TEMP = 50
local HEAT_TEMP = 75
local function getcolor(temp)
local t = (temp - COOL_TEMP) / (HEAT_TEMP - COOL_TEMP)
if t < 0 then t = 0 end
if t > 1 then t = 1 end
local red = math.floor(t * 255 + 0.5)
return string.format('#%02x%02x00', red, 255 - red)
end
local plugin_data = {}
local plugin_params = {
timer_opts = {
period = 2,
},
cb = function(t)
if not t then
return nil
end
local r = {}
for _, entry in ipairs(t) do
local temp = entry.value
r[#r + 1] = {full_text = string.format('%.0f°', temp), color = getcolor(temp)}
end
return r
end,
event = function(t)
if t.button == 1 then
plugin_data.please_reload = true
end
end,
}
widget = luastatus.require_plugin('cpu-temp-linux').widget(plugin_params, plugin_data)
================================================
FILE: examples/i3/cpu-usage.lua
================================================
widget = luastatus.require_plugin('cpu-usage-linux').widget{
cb = function(usage)
if usage ~= nil then
return {full_text = string.format('[%5.1f%%]', usage * 100)}
end
end,
}
================================================
FILE: examples/i3/disk-io.lua
================================================
widget = luastatus.require_plugin('disk-io-linux').widget{
period = 2,
cb = function(t)
-- Sort by name for determinism
table.sort(t, function(a, b) return a.name < b.name end)
local segments = {}
for _, entry in ipairs(t) do
local R = entry.read_bytes
local W = entry.written_bytes
if (R >= 0) and (W >= 0) then
segments[#segments + 1] = {
full_text = string.format(
'%s: %.0fk↓%.0fk↑',
entry.name,
R / 1024,
W / 1024
),
markup = 'pango',
}
end
end
return segments
end,
}
================================================
FILE: examples/i3/file-contents.lua
================================================
widget = luastatus.require_plugin('file-contents-linux').widget{
filename = os.getenv('HOME') .. '/status',
cb = function(f)
-- show the first line of the file
return {full_text = f:read('*line')}
end,
}
================================================
FILE: examples/i3/fs.lua
================================================
local function sorted_keys(tbl)
local keys = {}
for k, _ in pairs(tbl) do
keys[#keys + 1] = k
end
table.sort(keys)
return keys
end
widget = {
plugin = 'fs',
opts = {
paths = {'/', '/home'},
},
cb = function(t)
-- Sort for determinism
local keys = sorted_keys(t)
local res = {}
for _, k in ipairs(keys) do
local v = t[k]
table.insert(res, {
full_text = string.format('%s %.0f%%',
k, (1 - v.avail / v.total) * 100),
instance = k,
})
end
return res
end,
}
================================================
FILE: examples/i3/gmail.lua
================================================
--[[
-- Expects 'credentials.lua' to be present in the current directory; it may contain, e.g.,
-- return {
-- gmail = {
-- login = 'john.smith',
-- password = 'qwerty'
-- }
-- }
--]]
local credentials = require 'credentials'
widget = luastatus.require_plugin('imap').widget{
verbose = false,
host = 'imap.gmail.com',
port = 993,
mailbox = 'Inbox',
use_ssl = true,
timeout = 2 * 60,
handshake_timeout = 10,
login = credentials.gmail.login,
password = credentials.gmail.password,
error_sleep_period = 60,
cb = function(unseen)
if unseen == nil then
return nil
elseif unseen == 0 then
return {full_text = '[-]', color = '#595959'}
else
return {full_text = string.format('[%d unseen]', unseen)}
end
end,
event = [[
local t = ...
if t.button == 1 then
os.execute('xdg-open https://gmail.com &')
end
]],
}
================================================
FILE: examples/i3/ip.lua
================================================
widget = {
plugin = 'network-linux',
cb = function(t)
local r = {}
for iface, params in pairs(t) do
local iface_clean = iface
local addr = params.ipv6 or params.ipv4
if addr then
-- strip out "label" from the interface name
iface_clean = iface_clean:gsub(':.*', '')
-- strip out "zone index" from the address
addr = addr:gsub('%%.*', '')
if iface_clean ~= 'lo' then
r[#r + 1] = {
full_text = string.format('[%s: %s]', iface_clean, addr),
color = '#709080',
}
end
end
end
return r
end,
}
================================================
FILE: examples/i3/loadavg-linux.lua
================================================
local function get_ncpus()
local f = assert(io.open('/proc/cpuinfo', 'r'))
local n = 0
for line in f:lines() do
if line:match('^processor\t') then
n = n + 1
end
end
f:close()
return n
end
local function avg2str(x)
assert(x >= 0)
if x >= 1000 then
return '↑↑↑'
end
return string.format('%3.0f', x)
end
widget = {
plugin = 'timer',
opts = {
period = 2,
},
cb = function(_)
local f = io.open('/proc/loadavg', 'r')
local avg1, avg5, avg15 = f:read('*number', '*number', '*number')
f:close()
assert(avg1 and avg5 and avg15)
local ncpus = get_ncpus()
return {full_text = string.format(
'[%s%% %s%% %s%%]',
avg2str(avg1 / ncpus * 100),
avg2str(avg5 / ncpus * 100),
avg2str(avg15 / ncpus * 100)
)}
end,
}
================================================
FILE: examples/i3/media-player-mpris.lua
================================================
local PLAYER = 'clementine'
local PLAYBACK_STATUS_ICONS = {
Playing = '▶',
Paused = '◆',
Stopped = '—',
}
local COLOR_BRIGHT = '#60b177'
local COLOR_DIM = '#9fb15d'
local function fetch_metadata_field(t, key)
if t.Metadata then
return t.Metadata[key]
else
return nil
end
end
local function do_call_method(method_name)
local is_ok, err_msg = luastatus.plugin.call_method_str({
bus = 'session',
dest = 'org.mpris.MediaPlayer2.' .. PLAYER,
object_path = '/org/mpris/MediaPlayer2',
interface = 'org.mpris.MediaPlayer2.Player',
method = method_name,
-- no arg_str
})
if not is_ok then
print(string.format(
'WARNING: luastatus: mpris widget: cannot call method "%s": %s',
method_name, err_msg
))
end
end
widget = luastatus.require_plugin('mpris').widget{
player = PLAYER,
cb = function(t)
if not t.PlaybackStatus then
return nil
end
local title = fetch_metadata_field(t, 'xesam:title')
title = title or ''
title = luastatus.libwidechar.make_valid_and_printable(title, '?')
title = luastatus.libwidechar.truncate_to_width(title, 40)
title = title or ''
local icon = PLAYBACK_STATUS_ICONS[t.PlaybackStatus] or '?'
local segments = {
{full_text = '←', color = COLOR_DIM, instance = 'prev'},
{full_text = icon, color = COLOR_BRIGHT, instance = 'status'},
{full_text = '→', color = COLOR_DIM, instance = 'next'},
}
if title ~= '' then
table.insert(segments, {
full_text = title,
color = COLOR_BRIGHT,
})
end
return segments
end,
event = function(t)
if t.button ~= 1 then
return
end
if t.instance == 'status' then
do_call_method('PlayPause')
elseif t.instance == 'prev' then
do_call_method('Previous')
elseif t.instance == 'next' then
do_call_method('Next')
end
end,
}
================================================
FILE: examples/i3/mem-usage.lua
================================================
widget = luastatus.require_plugin('mem-usage-linux').widget{
timer_opts = {period = 2},
cb = function(t)
local used_kb = t.total.value - t.avail.value
return {full_text = string.format('[%3.2f GiB]', used_kb / 1024 / 1024), color = '#af8ec3'}
end,
}
================================================
FILE: examples/i3/mpd-progressbar.lua
================================================
local text, time, total, is_playing = nil, nil, nil, false
local timeout = 2
local titlewidth = 40
local function W_split(str, boundary)
local head, _ = luastatus.libwidechar.truncate_to_width(str, boundary)
assert(head)
local tail = str:sub(1 + #head)
return head, tail
end
widget = {
plugin = 'mpd',
opts = {
timeout = timeout,
},
cb = function(t)
if t.what == 'update' then
-- build title
local title
if t.song.Title then
title = t.song.Title
if t.song.Artist then
title = t.song.Artist .. ': ' .. title
end
else
title = t.song.file or ''
end
title = luastatus.libwidechar.make_valid(title, '?')
if assert(luastatus.libwidechar.width(title)) > titlewidth then
title = luastatus.libwidechar.truncate_to_width(title, titlewidth - 1) .. '…'
end
-- build text
text = string.format('%s %s',
({play = '▶', pause = '‖', stop = '■'})[t.status.state],
title
)
-- update other globals
if t.status.time then
time, total = t.status.time:match('(%d+):(%d+)')
time, total = tonumber(time), tonumber(total)
else
time, total = nil, nil
end
is_playing = t.status.state == 'play'
elseif t.what == 'timeout' then
if is_playing then
time = math.min(time + timeout, total)
end
else
-- 'connecting' or 'error'
return {full_text = t.what}
end
-- calc progress
local width = assert(luastatus.libwidechar.width(text))
-- 'time' and 'total' can be nil here, we check for 'total' only
local ulpos = (total and total ~= 0)
and math.floor(width / total * time + 0.5)
or 0
local head, tail = W_split(text, ulpos)
return {full_text = '' .. luastatus.barlib.pango_escape(head) ..
'' .. luastatus.barlib.pango_escape(tail),
markup = 'pango'}
end
}
================================================
FILE: examples/i3/network-rate.lua
================================================
local function make_segment(iface, R, S)
return {
full_text = string.format('[%s %.0fk↓%.0fk↑]',
iface,
R / 1000,
S / 1000
),
markup = 'pango',
}
end
widget = luastatus.require_plugin('network-rate-linux').widget{
iface_except = 'lo',
period = 3,
in_array_form = true,
cb = function(t)
local r = {}
for _, PQ in ipairs(t) do
local iface = PQ[1]
local R, S = PQ[2].R, PQ[2].S
r[#r + 1] = make_segment(iface, R, S)
end
return r
end,
}
================================================
FILE: examples/i3/pulse-gauge.lua
================================================
local GAUGE_NCHARS = 10
local function mk_gauge(level, full, empty)
local nfull = math.floor(level * GAUGE_NCHARS + 0.5)
return full:rep(nfull) .. empty:rep(GAUGE_NCHARS - nfull)
end
widget = {
plugin = 'pulse',
cb = function(t)
local level = t.cur / t.norm
if t.mute then
return {full_text=mk_gauge(level, '×', '—'), color='#e03838'}
else
return {full_text=mk_gauge(level, '●', '○'), color='#718ba6'}
end
end,
}
================================================
FILE: examples/i3/pulse-interactive-gauge.lua
================================================
-- So, it looks like this:
--
-- ••• | ██████████████ | [ 70%] | •••
--
-- ^ gauge block ^ text block
--
-- The gauge block is initially hidden; click on the text block to toggle its visibility.
-- Click anywhere at the gauge block to change the volume.
-- Click with the right mouse button on either block to toggle mute state.
local SINK = '@DEFAULT_SINK@'
local HBLOCKS = {' ', '▏', '▎', '▍', '▌', '▋', '▊', '▉', '█'}
local GAUGE_NCHARS = 20
-- i3bar adds the separator width to "relative_x" and "width" properties of a click event.
--
-- So let's do some multivariable equations (we assume a monospace font is used).
--
-- Let:
-- * `x' be the separator width in pixels (unknown);
-- * `y' be the width of one character in pixels (unknown);
-- * `U' be the number of characters in the text block (known);
-- * `V' be the number of characters in the gauge block (known);
-- * `A' be the width, in pixels, of the text block (known when clicked);
-- * `B' be the width, in pixels, on the gauge block (known when clicked).
--
-- We have:
-- { x + U*y = A;
-- { x + V*y = B.
-- So,
-- y = (A - B) / (U - V);
-- x = A - U*y.
local last_t = nil
local text_block_nchars, text_block_width = nil, nil
local gauge = false
local function round(x)
return math.floor(x + 0.5)
end
local function mk_gauge(level)
local rel_level = level * GAUGE_NCHARS
local nfull = math.floor(rel_level)
local filled = HBLOCKS[#HBLOCKS]:rep(nfull)
if nfull == GAUGE_NCHARS then
return filled
end
local mid_idx = round((rel_level - nfull) * (#HBLOCKS - 1))
return filled .. HBLOCKS[1 + mid_idx] .. HBLOCKS[1]:rep(GAUGE_NCHARS - nfull - 1)
end
widget = {
plugin = 'pulse',
opts = {
sink = SINK,
make_self_pipe = true,
},
cb = function(t)
if t == nil then
t = last_t
else
last_t = t
end
local level = t.cur / t.norm
local r = {}
if t.mute then
r[2] = {full_text='[mute]', color='#e03838'}
else
r[2] = {full_text=string.format('[%3d%%]', round(level * 100)), color='#718ba6'}
end
text_block_nchars = #r[2].full_text -- please note this does not work with Unicode.
if gauge then
local fg, bg = '#dcdcdc', '#444444'
if t.mute then
fg, bg = '#e03838', '#4a1414'
end
r[1] = {full_text=mk_gauge(level), color=fg, background=bg, instance='gauge'}
end
return r
end,
event = function(t)
if t.button == 1 then -- left mouse button
if t.instance == 'gauge' then
local char_width = round((t.width - text_block_width)
/ (GAUGE_NCHARS - text_block_nchars))
local sep_width = text_block_width - text_block_nchars * char_width
local x = t.relative_x - sep_width
if x < 0 then
return
end
local rawvol = round(x / (t.width - sep_width) * last_t.norm)
os.execute(string.format(
'pactl set-sink-volume "%s" %s', SINK, rawvol))
else
gauge = not gauge
text_block_width = t.width
luastatus.plugin.wake_up()
end
elseif t.button == 3 then -- right mouse button
os.execute(string.format(
'pactl set-sink-mute "%s" toggle', SINK))
end
end,
}
================================================
FILE: examples/i3/pulse.lua
================================================
widget = {
plugin = 'pulse',
cb = function(t)
if t.mute then
return {full_text = '[mute]', color = '#e03838'}
end
local percent = (t.cur / t.norm) * 100
return {full_text = string.format('[%3d%%]', math.floor(0.5 + percent)), color = '#718ba6'}
end,
}
================================================
FILE: examples/i3/systemd-unit.lua
================================================
local COLORS = {
pink = '#ec93d3',
green = '#60b48a',
yellow = '#dfaf8f',
red = '#dca3a3',
dim = '#909090',
}
local function make_output(text, color_name)
local color = assert(COLORS[color_name])
local body = string.format('[Tor: %s]', color, text)
return {full_text = body, markup = 'pango'}
end
widget = luastatus.require_plugin('systemd-unit').widget{
unit_name = 'tor.service',
cb = function(state)
if state == 'active' then
return make_output('✓', 'green')
elseif state == 'reloading' or state == 'activating' then
return make_output('•', 'yellow')
elseif state == 'inactive' or state == 'deactivating' then
return make_output('-', 'dim')
elseif state == 'failed' then
return make_output('x', 'red')
else
return make_output('?', 'pink')
end
end,
}
================================================
FILE: examples/i3/time-battery-combined.lua
================================================
local function get_bat_seg(t)
if not t then
return {full_text = '[--×--]'}
end
if t.status == 'Unknown' or t.status == 'Full' or t.status == 'Not charging' then
return nil
end
local sym, color = '?', '#dcdcdc'
if t.status == 'Discharging' then
sym = '↓'
color = '#dca3a3'
elseif t.status == 'Charging' then
sym = '↑'
color = '#60b48a'
end
return {full_text = string.format('[%3d%%%s]', t.capacity, sym), color = color}
end
widget = luastatus.require_plugin('battery-linux').widget{
period = 2,
cb = function(t)
return {
{full_text = os.date('[%H:%M]'), color = '#dc8cc3'},
get_bat_seg(t),
}
end,
}
================================================
FILE: examples/i3/time-date.lua
================================================
local months = {'января', 'февраля', 'марта', 'апреля', 'мая', 'июня', 'июля', 'августа', 'сентября',
'октября', 'ноября', 'декабря'}
widget = {
plugin = 'timer',
cb = function()
local d = os.date('*t')
return {
{full_text = string.format('%d %s', d.day, months[d.month])},
{full_text = string.format('%02d:%02d', d.hour, d.min)},
}
end,
}
================================================
FILE: examples/i3/tor.lua
================================================
-- Trivial but somewhat useful widget showing if the Tor daemon is running.
widget = {
plugin = 'timer',
opts = {period = 5},
cb = function()
local f = io.open('/var/run/tor/tor.pid', 'r')
if f then
f:close()
return {full_text = '[TOR]'}
else
return nil
end
end,
}
================================================
FILE: examples/i3/update-on-click.lua
================================================
widget = {
plugin = 'timer',
opts = {
make_self_pipe = true,
period = 2.0,
},
cb = function(t)
if t == 'self_pipe' then
return {full_text = 'Thanks!'}
else
return {full_text = 'Click me'}
end
end,
event = function(_)
luastatus.plugin.wake_up()
end,
}
================================================
FILE: examples/i3/uptime-linux.lua
================================================
local SUFFIXES_AND_DIVISORS = {
{'m', 60},
{'h', 60},
{'d', 24},
{'W', 7},
{'M', 30},
{'Y', 365 / 30},
}
local function seconds_to_human_readable_time(sec)
local prev_suffix = 's'
local found_suffix
for _, PQ in ipairs(SUFFIXES_AND_DIVISORS) do
local suffix = PQ[1]
local divisor = PQ[2]
if sec < divisor then
found_suffix = prev_suffix
break
end
sec = sec / divisor
prev_suffix = suffix
end
if not found_suffix then
found_suffix = prev_suffix
end
return string.format('%.0f%s', sec, found_suffix)
end
widget = {
plugin = 'timer',
opts = {
period = 2,
},
cb = function(_)
local f = io.open('/proc/uptime', 'r')
local sec, _ = f:read('*number', '*number')
f:close()
assert(sec)
return {full_text = string.format(
'[uptime: %s]',
seconds_to_human_readable_time(sec)
)}
end,
}
================================================
FILE: examples/i3/weather-rus.lua
================================================
local LOCATION_NAME = 'Moscow'
local TIMEOUT = 10
local INTERVAL = 9
local SLEEP_AFTER_INITIAL = 5
local coordinates = nil
local function planner()
-- Get coordinates of LOCATION_NAME.
while true do
-- Form the URL
local url = string.format(
'https://geocoding-api.open-meteo.com/v1/search?name=%s&count=1',
luastatus.plugin.urlencode(LOCATION_NAME)
)
-- Make request
coroutine.yield({action = 'request', params = {
url = url,
timeout = TIMEOUT,
}})
-- If successful, break
if coordinates ~= nil then
break
end
-- Failure; sleep and retry
coroutine.yield({action = 'sleep', period = INTERVAL})
end
-- Coordinates fetched, sleep for SLEEP_AFTER_INITIAL.
coroutine.yield({action = 'sleep', period = SLEEP_AFTER_INITIAL})
-- Form the weather URL
local weather_url = string.format(
'https://api.open-meteo.com/v1/forecast' ..
'?latitude=%f' ..
'&longitude=%f' ..
'¤t_weather=true',
coordinates.latitude,
coordinates.longitude
)
-- Fetch weather, periodically
while true do
-- Make request
coroutine.yield({action = 'request', params = {
url = weather_url,
timeout = TIMEOUT,
}})
-- Sleep and repeat
coroutine.yield({action = 'sleep', period = INTERVAL})
end
end
local function check_error(t)
if t.error then
-- Low-level libcurl error
return t.error
end
if t.status < 200 or t.status > 299 then
-- Bad HTTP status
return string.format('HTTP status %d', t.status)
end
-- Everything's OK
return nil
end
local WEATHER_CODES = {
[0] = 'Ясно',
[1] = 'Преимущественно ясно',
[2] = 'Переменная облачность',
[3] = 'Пасмурно',
[45] = 'Туман',
[48] = 'Осаждение инея и тумана',
[51] = 'Изморось, лёгкая',
[53] = 'Изморось, умеренная',
[55] = 'Изморось, сильная',
[56] = 'Ледяная изморось, лёгкая',
[57] = 'Ледяная изморось, сильная',
[61] = 'Дождь, лёгкий',
[63] = 'Дождь, умеренный',
[65] = 'Дождь, сильный',
[66] = 'Град, лёгкий',
[67] = 'Град, сильный',
[71] = 'Снегопад, лёгкий',
[73] = 'Снегопад, умеренный',
[75] = 'Снегопад, сильный',
[77] = 'Крупинки снега',
[80] = 'Ливень, лёгкий',
[81] = 'Ливень, умеренный',
[82] = 'Ливень, сильный',
[85] = 'Снеговые осадки, лёгкие',
[87] = 'Снеговые осадки, сильные',
[95] = 'Гроза',
[96] = 'Гроза, небольшой град',
[99] = 'Гроза, сильный град',
}
local function fmt_weather(temp, _, _, weather_code)
local segment1 = {full_text = string.format('%.0f°', temp)}
local segment2 = nil
local weather = WEATHER_CODES[weather_code]
if weather then
segment2 = {full_text = weather}
end
return {segment1, segment2}
end
widget = {
plugin = 'web',
opts = {
planner = planner,
},
cb = function(t)
local err_msg = check_error(t)
if err_msg then
print(string.format('WARNING: luastatus: weather widget: %s'), err_msg)
return {full_text = '...', color = '#e03838'}
end
if coordinates == nil then
local obj = assert(luastatus.plugin.json_decode(t.body))
local result = assert(obj.results[1])
local latitude = assert(result.latitude)
local longitude = assert(result.longitude)
coordinates = {latitude = latitude, longitude = longitude}
return {full_text = '...', color = '#709080'}
end
local obj = assert(luastatus.plugin.json_decode(t.body))
local cw = assert(obj.current_weather)
local temp = assert(cw.temperature)
local wind_speed = assert(cw.windspeed)
-- is_day is an integer, either 0 or 1
local is_day = assert(cw.is_day) ~= 0
local weather_code = assert(cw.weathercode)
return fmt_weather(temp, wind_speed, is_day, weather_code)
end,
}
================================================
FILE: examples/i3/weather.lua
================================================
local LOCATION_NAME = 'Moscow'
local TIMEOUT = 10
local INTERVAL = 9
local SLEEP_AFTER_INITIAL = 5
local coordinates = nil
local function planner()
-- Get coordinates of LOCATION_NAME.
while true do
-- Form the URL
local url = string.format(
'https://geocoding-api.open-meteo.com/v1/search?name=%s&count=1',
luastatus.plugin.urlencode(LOCATION_NAME)
)
-- Make request
coroutine.yield({action = 'request', params = {
url = url,
timeout = TIMEOUT,
}})
-- If successful, break
if coordinates ~= nil then
break
end
-- Failure; sleep and retry
coroutine.yield({action = 'sleep', period = INTERVAL})
end
-- Coordinates fetched, sleep for SLEEP_AFTER_INITIAL.
coroutine.yield({action = 'sleep', period = SLEEP_AFTER_INITIAL})
-- Form the weather URL
local weather_url = string.format(
'https://api.open-meteo.com/v1/forecast' ..
'?latitude=%f' ..
'&longitude=%f' ..
'¤t_weather=true',
coordinates.latitude,
coordinates.longitude
)
-- Fetch weather, periodically
while true do
-- Make request
coroutine.yield({action = 'request', params = {
url = weather_url,
timeout = TIMEOUT,
}})
-- Sleep and repeat
coroutine.yield({action = 'sleep', period = INTERVAL})
end
end
local function check_error(t)
if t.error then
-- Low-level libcurl error
return t.error
end
if t.status < 200 or t.status > 299 then
-- Bad HTTP status
return string.format('HTTP status %d', t.status)
end
-- Everything's OK
return nil
end
local WEATHER_CODES = {
[0] = 'Clear sky',
[1] = 'Mainly clear',
[2] = 'Partly cloudy',
[3] = 'Overcast',
[45] = 'Fog',
[48] = 'Depositing rime fog',
[51] = 'Drizzle, light',
[53] = 'Drizzle, moderate',
[55] = 'Drizzle, dense',
[56] = 'Freezing drizzle, light',
[57] = 'Freezing drizzle, dense',
[61] = 'Rain, slight',
[63] = 'Rain, moderate',
[65] = 'Rain, heavy',
[66] = 'Freezing rain, light',
[67] = 'Freezing rain, heavy',
[71] = 'Snow fall, slight',
[73] = 'Snow fall, moderate',
[75] = 'Snow fall, heavy',
[77] = 'Snow grains',
[80] = 'Rain shower, slight',
[81] = 'Rain shower, moderate',
[82] = 'Rain shower, violent',
[85] = 'Snow showers, slight',
[87] = 'Snow showers, heavy',
[95] = 'Thunderstorm',
[96] = 'Thunderstorm, slight hail',
[99] = 'Thunderstorm, heavy hail',
}
local function fmt_weather(temp, _, _, weather_code)
local segment1 = {full_text = string.format('%.0f°', temp)}
local segment2 = nil
local weather = WEATHER_CODES[weather_code]
if weather then
segment2 = {full_text = weather}
end
return {segment1, segment2}
end
widget = {
plugin = 'web',
opts = {
planner = planner,
},
cb = function(t)
local err_msg = check_error(t)
if err_msg then
print(string.format('WARNING: luastatus: weather widget: %s'), err_msg)
return {full_text = '...', color = '#e03838'}
end
if coordinates == nil then
local obj = assert(luastatus.plugin.json_decode(t.body))
local result = assert(obj.results[1])
local latitude = assert(result.latitude)
local longitude = assert(result.longitude)
coordinates = {latitude = latitude, longitude = longitude}
return {full_text = '...', color = '#709080'}
end
local obj = assert(luastatus.plugin.json_decode(t.body))
local cw = assert(obj.current_weather)
local temp = assert(cw.temperature)
local wind_speed = assert(cw.windspeed)
-- is_day is an integer, either 0 or 1
local is_day = assert(cw.is_day) ~= 0
local weather_code = assert(cw.weathercode)
return fmt_weather(temp, wind_speed, is_day, weather_code)
end,
}
================================================
FILE: examples/i3/wireless.lua
================================================
local ORIGIN = '•' -- '●' '○' '◌' '◍' '⬤'
local RAY = '}' -- '›' '〉' '❭' '❯' '❱' '⟩' '⟫' '》'
local MIN_DBM, MAX_DBM = -90, -20
local NGAUGE = 5
local COLOR_DIM = '#709080'
local function round(x)
return math.floor(x + 0.5)
end
local function make_wifi_gauge(dbm)
if dbm < MIN_DBM then dbm = MIN_DBM end
if dbm > MAX_DBM then dbm = MAX_DBM end
local nbright = round(NGAUGE * (1 - 0.7 * (MAX_DBM - dbm) / (MAX_DBM - MIN_DBM)))
return {
full_text = string.format(
'%s%s%s',
ORIGIN, RAY:rep(nbright), COLOR_DIM, RAY:rep(NGAUGE - nbright)
),
markup = 'pango',
}
end
local function sorted_keys(tbl)
local keys = {}
for k, _ in pairs(tbl) do
keys[#keys + 1] = k
end
table.sort(keys)
return keys
end
widget = {
plugin = 'network-linux',
opts = {
wireless = true,
timeout = 10,
},
cb = function(t)
if not t then
return nil
end
-- Sort for determinism
local ifaces = sorted_keys(t)
local r = {}
for _, iface in ipairs(ifaces) do
local params = t[iface]
if params.wireless then
if params.wireless.ssid then
r[#r + 1] = {
full_text = params.wireless.ssid,
color = COLOR_DIM,
}
end
if params.wireless.signal_dbm then
r[#r + 1] = make_wifi_gauge(params.wireless.signal_dbm)
end
elseif iface ~= 'lo' and (params.ipv4 or params.ipv6) then
r[#r + 1] = {
full_text = string.format('[%s]', iface),
color = COLOR_DIM,
}
end
end
return r
end,
}
================================================
FILE: examples/i3/xkb.lua
================================================
widget = {
plugin = 'xkb',
cb = function(t)
if t.name then
local base_layout = t.name:match('[^(]+')
if base_layout == 'gb' or base_layout == 'us' then
return {full_text = '[En]', color = '#9c9c9c'}
elseif base_layout == 'ru' then
return {full_text = '[Ru]', color = '#eab93d'}
else
return {full_text = '[' .. base_layout:sub(1, 1):upper() .. base_layout:sub(2) .. ']'}
end
else
return {full_text = '[? ID ' .. t.id .. ']'}
end
end,
}
================================================
FILE: examples/lemonbar/alsa-gauge.lua
================================================
local GAUGE_NCHARS = 10
local function mk_gauge(level, full, empty)
local nfull = math.floor(level * GAUGE_NCHARS + 0.5)
return full:rep(nfull) .. empty:rep(GAUGE_NCHARS - nfull)
end
widget = {
plugin = 'alsa',
cb = function(t)
local level = (t.vol.cur - t.vol.min) / (t.vol.max - t.vol.min)
if t.mute then
return "%{F#e03838}" .. mk_gauge(level, '×', '—') .. "%{F-}"
else
return "%{F#718ba6}" .. mk_gauge(level, '●', '○') .. "%{F-}"
end
end,
}
================================================
FILE: examples/lemonbar/alsa.lua
================================================
widget = {
plugin = 'alsa',
cb = function(t)
if t.mute then
return "%{F#e03838}" .. '[mute]' .. "%{F-}"
else
local percent = (t.vol.cur - t.vol.min) / (t.vol.max - t.vol.min) * 100
local body = string.format('[%3d%%]', math.floor(0.5 + percent))
return "%{F#718ba6}" .. luastatus.barlib.escape(body) .. "%{F-}"
end
end,
}
================================================
FILE: examples/lemonbar/backlight.lua
================================================
-- Note that this widget only shows backlight level when it changes.
widget = luastatus.require_plugin('backlight-linux').widget{
cb = function(level)
if level ~= nil then
local body = string.format('*%3.0f%%', level * 100)
return luastatus.barlib.escape(body)
end
end,
}
================================================
FILE: examples/lemonbar/battery.lua
================================================
widget = luastatus.require_plugin('battery-linux').widget{
period = 2,
cb = function(t)
local symbol = ({
Charging = '↑',
Discharging = '↓',
})[t.status] or ' '
local rem_seg
if t.rem_time then
local h = math.floor(t.rem_time)
local m = math.floor(60 * (t.rem_time - h))
rem_seg = "%{F#595959}" .. string.format('%2dh %02dm', h, m) .. "%{F-}"
end
return {
luastatus.barlib.escape(string.format('%3d%%%s', t.capacity, symbol)),
rem_seg,
}
end,
}
================================================
FILE: examples/lemonbar/bluetooth.lua
================================================
-- A widget to display currently connected and paired bluetooth devices.
-- To change output format modify reprint_devices function.
if not luastatus.execute('command -v bluetoothctl >/dev/null') then
error('"bluetoothctl" command, which is required for this widget to work, was not found')
end
local separator = " "
-- Object paths look like /org/bluez/hci0/dev_XX_XX_XX_XX_XX_XX/somethingsomething
local function get_device_mac_address(device_object_path)
return device_object_path:gsub("/.*/dev_", ""):gsub("/.*", ""):gsub("_", ":")
end
-- For reference bluetoothctl devices output looks like that:
-- Device XX:XX:XX:XX:XX:XX JBL T450BT
-- Device YY:YY:YY:YY:YY:YY Redmi 8
--
-- Function returns mac addresses of all devices.
local function get_devices()
local devices = {}
local handle = io.popen(string.format("bluetoothctl devices"))
for line in handle:lines() do
local match = string.match(line, "Device ([%x:]+)")
if match then
table.insert(devices, match)
end
end
handle:close()
return devices
end
-- For reference bluetoothctl info output looks like that:
-- Device XX:XX:XX:XX:XX:XX (public)
-- Name: JBL T450BT
-- Alias: JBL T450BT
-- Class: 0xFFFFFFFF
-- Icon: audio-card
-- Paired: yes
-- Trusted: yes
-- Blocked: no
-- Connected: yes
-- LegacyPairing: no
-- UUID: Headset (xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)
-- ...
-- UUID: Handsfree (xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)
--
-- Given this input function returns a following table:
-- [alias] string JBL T450BT
-- [blocked] boolean false
-- [class] string 0x00240404
-- [connected] boolean true
-- [icon] string audio
-- [legacypairing] boolean false
-- [name] string JBL T450BT
-- [paired] boolean true
-- [trusted] boolean true
local function get_device_info(mac_address)
if mac_address == nil then
mac_address = ""
end
assert(string.match(mac_address, '^[%x:]*$') ~= nil)
local device_info = {}
local handle = io.popen(string.format("bluetoothctl info %s", mac_address))
for line in handle:lines() do
local key, value = string.match(line, "(%w+): (.*)")
-- Filter junk
if key ~= "UUID" and key ~= nil and value ~= nil then
key = string.lower(key)
if key ~= "name" and key ~= "alias" and key ~= "icon" then
if value == "yes" then
value = true
end
if value == "no" then
value = false
end
end
device_info[key] = value
end
end
handle:close()
return device_info
end
local devices = {}
local function reprint_devices()
local t = {}
for mac_address, device in pairs(devices) do
table.insert(t, string.format("%s(%s)", device["name"], mac_address))
end
return table.concat(t, separator)
end
widget = {
plugin = "dbus",
opts = {
greet = true,
-- https://git.kernel.org/pub/scm/bluetooth/bluez.git/tree/doc/device-api.txt
signals = {
{
sender = "org.bluez",
interface = "org.freedesktop.DBus.Properties",
signal = "PropertiesChanged",
arg0 = "org.bluez.Device1",
bus = "system"
}
}
},
cb = function(t)
if t.what == "hello" then
local mac_addresses = get_devices()
for _, mac_address in ipairs(mac_addresses) do
local device = get_device_info(mac_address)
if device["connected"] and device["paired"] then
devices[mac_address] = device
end
end
elseif t.what == "signal" then
-- For reference message from dbus looks like that:
-- table
-- [1] string org.bluez.Device1
-- [2] table
-- [2] [1] table
-- [2] [1] [1] string SomethingSomething
-- [2] [1] [2] boolean false
-- [2] [2] table
-- [2] [2] [1] string Connected
-- [2] [2] [2] boolean true
-- [3] table
if t.signal == "PropertiesChanged" then
for _, message in pairs(t.parameters[2]) do
if message[1] == "Connected" or message[1] == "Paired" then
local mac_address = get_device_mac_address(t.object_path)
if message[2] then
local device = get_device_info(mac_address)
if device["paired"] then
devices[mac_address] = device
end
else
devices[mac_address] = nil
end
end
end
end
end
return luastatus.barlib.escape(reprint_devices())
end
}
================================================
FILE: examples/lemonbar/btc-price.lua
================================================
-- Bitcoin price widget.
-- Updates on click and every 5 minutes.
local custom_sleep_amt = nil
local function check_error(t)
if t.error then
-- Low-level libcurl error
return t.error
end
if t.status < 200 or t.status > 299 then
-- Bad HTTP status
return string.format('HTTP status %d', t.status)
end
-- Everything's OK
return nil
end
local function stylize(styles, fmt, ...)
local pos_args = {...}
local res, _ = fmt:gsub('([@%%])([0-9])', function(sigil, digit)
local i = tonumber(digit)
if sigil == '@' then
if i == 0 then
return '%{F-}'
end
return styles[i]
else
assert(i ~= 0)
return pos_args[i]
end
end)
return res
end
widget = {
plugin = 'web',
opts = {
planner = function()
while true do
coroutine.yield({action = 'request', params = {
url = 'https://api.binance.com/api/v3/ticker/price?symbol=BTCUSDT',
timeout = 5,
}})
local period = custom_sleep_amt or (5 * 60)
custom_sleep_amt = nil
coroutine.yield({action = 'sleep', period = period})
end
end,
make_self_pipe = true,
},
cb = function(t)
local text
local err_msg = check_error(t)
if err_msg then
print(string.format('WARNING: luastatus: btc-price widget: %s'), err_msg)
text = '......'
custom_sleep_amt = 5
else
local obj = assert(luastatus.plugin.json_decode(t.body))
text = obj.price:match('[^.]+')
end
return stylize(
{'%{F#586A4B}', '%{F#C0863F}'},
'@1[@2$@1%1]@0',
luastatus.barlib.escape(text)
)
end,
event = function(t)
if t.button == 1 then
luastatus.plugin.wake_up()
end
end,
}
================================================
FILE: examples/lemonbar/cpu-freq.lua
================================================
local VBLOCKS = {'▂', '▃', '▄', '▅', '▆', '▇', '█'}
local function make_color(ratio)
local red = math.floor(0.5 + 255 * ratio)
return string.format('#%02x%02x00', red, 255 - red)
end
local function make_chunk(entry)
local num = entry.cur - entry.min
local denom = entry.max - entry.min
local ratio
if denom ~= 0 then
ratio = num / denom
else
-- If max_freq == min_freq, set ratio to zero.
ratio = 0
end
local vblock_idx = math.min(
1 + math.floor(0.5 + ratio * #VBLOCKS),
#VBLOCKS)
return '%{F' .. make_color(ratio) .. '}' .. VBLOCKS[vblock_idx]
end
local plugin_data = {}
local plugin_params = {
timer_opts = {
period = 2,
},
cb = function(t)
if t == nil then
return nil
end
local r = {}
for _, entry in ipairs(t) do
r[#r + 1] = make_chunk(entry)
end
return table.concat(r) .. '%{F-}'
end,
}
widget = luastatus.require_plugin('cpu-freq-linux').widget(plugin_params, plugin_data)
================================================
FILE: examples/lemonbar/cpu-temperature.lua
================================================
local COOL_TEMP = 50
local HEAT_TEMP = 75
local function getcolor(temp)
local t = (temp - COOL_TEMP) / (HEAT_TEMP - COOL_TEMP)
if t < 0 then t = 0 end
if t > 1 then t = 1 end
local red = math.floor(t * 255 + 0.5)
return string.format('#%02x%02x00', red, 255 - red)
end
local plugin_data = {}
local plugin_params = {
timer_opts = {
period = 2,
},
cb = function(t)
if not t then
return nil
end
local r = {}
for _, entry in ipairs(t) do
local temp = entry.value
local body = string.format('%.0f°', temp)
r[#r + 1] = '%{F' .. getcolor(temp) .. '}' .. body .. '%{F-}'
end
return r
end,
}
widget = luastatus.require_plugin('cpu-temp-linux').widget(plugin_params, plugin_data)
================================================
FILE: examples/lemonbar/cpu-usage.lua
================================================
widget = luastatus.require_plugin('cpu-usage-linux').widget{
cb = function(usage)
if usage ~= nil then
local body = string.format('[%5.1f%%]', usage * 100)
return luastatus.barlib.escape(body)
end
end,
}
================================================
FILE: examples/lemonbar/disk-io.lua
================================================
local function stylize(styles, fmt, ...)
local pos_args = {...}
local res, _ = fmt:gsub('([@%%])([0-9])', function(sigil, digit)
local i = tonumber(digit)
if sigil == '@' then
if i == 0 then
return '%{F-}'
end
return styles[i]
else
assert(i ~= 0)
return pos_args[i]
end
end)
return res
end
widget = luastatus.require_plugin('disk-io-linux').widget{
period = 2,
cb = function(t)
-- Sort by name for determinism
table.sort(t, function(a, b) return a.name < b.name end)
local segments = {}
for _, entry in ipairs(t) do
local R = entry.read_bytes
local W = entry.written_bytes
if (R >= 0) and (W >= 0) then
segments[#segments + 1] = stylize(
{'%{F#DCA3A3}', '%{F#72D5A3}'},
'%1: @1%2k↓ @2%3k↑@0',
luastatus.barlib.escape(entry.name),
string.format('%.0f', R / 1024),
string.format('%.0f', W / 1024)
)
end
end
return segments
end,
}
================================================
FILE: examples/lemonbar/file-contents.lua
================================================
widget = luastatus.require_plugin('file-contents-linux').widget{
filename = os.getenv('HOME') .. '/status',
cb = function(f)
-- show the first line of the file
return luastatus.barlib.escape(f:read('*line'))
end,
}
================================================
FILE: examples/lemonbar/fs.lua
================================================
local function sorted_keys(tbl)
local keys = {}
for k, _ in pairs(tbl) do
keys[#keys + 1] = k
end
table.sort(keys)
return keys
end
widget = {
plugin = 'fs',
opts = {
paths = {'/', '/home'},
},
cb = function(t)
-- Sort for determinism
local keys = sorted_keys(t)
local res = {}
for _, k in ipairs(keys) do
local v = t[k]
local body = string.format('%s %.0f%%', k, (1 - v.avail / v.total) * 100)
table.insert(res, luastatus.barlib.escape(body))
end
return res
end,
}
================================================
FILE: examples/lemonbar/gmail.lua
================================================
--[[
-- Expects 'credentials.lua' to be present in the current directory; it may contain, e.g.,
-- return {
-- gmail = {
-- login = 'john.smith',
-- password = 'qwerty'
-- }
-- }
--]]
local credentials = require 'credentials'
widget = luastatus.require_plugin('imap').widget{
verbose = false,
host = 'imap.gmail.com',
port = 993,
mailbox = 'Inbox',
use_ssl = true,
timeout = 2 * 60,
handshake_timeout = 10,
login = credentials.gmail.login,
password = credentials.gmail.password,
error_sleep_period = 60,
cb = function(unseen)
if unseen == nil then
return nil
elseif unseen == 0 then
return "%{F#595959}" .. '[-]' .. "%{F-}"
else
return string.format('[%d unseen]', unseen)
end
end,
}
================================================
FILE: examples/lemonbar/ip.lua
================================================
widget = {
plugin = 'network-linux',
cb = function(t)
local r = {}
for iface, params in pairs(t) do
local iface_clean = iface
local addr = params.ipv6 or params.ipv4
if addr then
-- strip out "label" from the interface name
iface_clean = iface_clean:gsub(':.*', '')
-- strip out "zone index" from the address
addr = addr:gsub('%%.*', '')
if iface_clean ~= 'lo' then
local body = string.format('[%s: %s]', iface_clean, addr)
r[#r + 1] = "%{F#709080}" .. body .. "%{F-}"
end
end
end
return r
end,
}
================================================
FILE: examples/lemonbar/loadavg-linux.lua
================================================
local function get_ncpus()
local f = assert(io.open('/proc/cpuinfo', 'r'))
local n = 0
for line in f:lines() do
if line:match('^processor\t') then
n = n + 1
end
end
f:close()
return n
end
local function avg2str(x)
assert(x >= 0)
if x >= 1000 then
return '↑↑↑'
end
return string.format('%3.0f', x)
end
widget = {
plugin = 'timer',
opts = {
period = 2,
},
cb = function(_)
local f = io.open('/proc/loadavg', 'r')
local avg1, avg5, avg15 = f:read('*number', '*number', '*number')
f:close()
assert(avg1 and avg5 and avg15)
local ncpus = get_ncpus()
return luastatus.barlib.escape(string.format(
'[%s%% %s%% %s%%]',
avg2str(avg1 / ncpus * 100),
avg2str(avg5 / ncpus * 100),
avg2str(avg15 / ncpus * 100)
))
end,
}
================================================
FILE: examples/lemonbar/media-player-mpris.lua
================================================
local PLAYER = 'clementine'
local PLAYBACK_STATUS_ICONS = {
Playing = '▶',
Paused = '◆',
Stopped = '—',
}
local COLOR_BRIGHT = '#60b177'
local COLOR_DIM = '#9fb15d'
local function fetch_metadata_field(t, key)
if t.Metadata then
return t.Metadata[key]
else
return nil
end
end
local function do_call_method(method_name)
local is_ok, err_msg = luastatus.plugin.call_method_str({
bus = 'session',
dest = 'org.mpris.MediaPlayer2.' .. PLAYER,
object_path = '/org/mpris/MediaPlayer2',
interface = 'org.mpris.MediaPlayer2.Player',
method = method_name,
-- no arg_str
})
if not is_ok then
print(string.format(
'WARNING: luastatus: mpris widget: cannot call method "%s": %s',
method_name, err_msg
))
end
end
local function make_segment(text, color, event_text)
local color_begin = '%{F' .. color .. '}'
local color_end = '%{F-}'
local res = color_begin .. luastatus.barlib.escape(text) .. color_end
if event_text then
local a_begin = '%{A:' .. event_text .. ':}'
local a_end = '%{A}'
res = a_begin .. res .. a_end
end
return res
end
widget = luastatus.require_plugin('mpris').widget{
player = PLAYER,
cb = function(t)
if not t.PlaybackStatus then
return nil
end
local title = fetch_metadata_field(t, 'xesam:title')
title = title or ''
title = luastatus.libwidechar.make_valid_and_printable(title, '?')
title = luastatus.libwidechar.truncate_to_width(title, 40)
title = title or ''
local icon = PLAYBACK_STATUS_ICONS[t.PlaybackStatus] or '?'
local chunks = {
make_segment('←', COLOR_DIM, 'prev'),
make_segment(icon, COLOR_BRIGHT, 'status'),
make_segment('→', COLOR_DIM, 'next'),
}
if title ~= '' then
table.insert(chunks, make_segment(title, COLOR_BRIGHT, nil))
end
return table.concat(chunks, ' ')
end,
event = function(t)
if t == 'status' then
do_call_method('PlayPause')
elseif t == 'prev' then
do_call_method('Previous')
elseif t == 'next' then
do_call_method('Next')
end
end,
}
================================================
FILE: examples/lemonbar/mem-usage.lua
================================================
widget = luastatus.require_plugin('mem-usage-linux').widget{
timer_opts = {period = 2},
cb = function(t)
local used_kb = t.total.value - t.avail.value
local body = string.format('[%3.2f GiB]', used_kb / 1024 / 1024)
return "%{F#af8ec3}" .. body .. "%{F-}"
end,
}
================================================
FILE: examples/lemonbar/mpd-progressbar.lua
================================================
local text, time, total, is_playing = nil, nil, nil, false
local timeout = 2
local titlewidth = 40
local function W_split(str, boundary)
local head, _ = luastatus.libwidechar.truncate_to_width(str, boundary)
assert(head)
local tail = str:sub(1 + #head)
return head, tail
end
widget = {
plugin = 'mpd',
opts = {
timeout = timeout,
},
cb = function(t)
if t.what == 'update' then
-- build title
local title
if t.song.Title then
title = t.song.Title
if t.song.Artist then
title = t.song.Artist .. ': ' .. title
end
else
title = t.song.file or ''
end
title = luastatus.libwidechar.make_valid(title, '?')
if assert(luastatus.libwidechar.width(title)) > titlewidth then
title = luastatus.libwidechar.truncate_to_width(title, titlewidth - 1) .. '…'
end
-- build text
text = string.format('%s %s',
({play = '▶', pause = '‖', stop = '■'})[t.status.state],
title
)
-- update other globals
if t.status.time then
time, total = t.status.time:match('(%d+):(%d+)')
time, total = tonumber(time), tonumber(total)
else
time, total = nil, nil
end
is_playing = t.status.state == 'play'
elseif t.what == 'timeout' then
if is_playing then
time = math.min(time + timeout, total)
end
else
-- 'connecting' or 'error'
return {full_text = t.what}
end
-- calc progress
local width = assert(luastatus.libwidechar.width(text))
-- 'time' and 'total' can be nil here, we check for 'total' only
local ulpos = (total and total ~= 0)
and math.floor(width / total * time + 0.5)
or 0
local head, tail = W_split(text, ulpos)
return '%{u+}' .. luastatus.barlib.escape(head) ..
'%{u-}' .. luastatus.barlib.escape(tail)
end
}
================================================
FILE: examples/lemonbar/network-rate.lua
================================================
local function stylize(styles, fmt, ...)
local pos_args = {...}
local res, _ = fmt:gsub('([@%%])([0-9])', function(sigil, digit)
local i = tonumber(digit)
if sigil == '@' then
if i == 0 then
return '%{F-}'
end
return styles[i]
else
assert(i ~= 0)
return pos_args[i]
end
end)
return res
end
local function make_segment(iface, R, S)
return stylize(
{'%{F#DCA3A3}', '%{F#72D5A3}'},
'[%1 @1%2k @2%3k@0]',
luastatus.barlib.escape(iface),
string.format('%.0f↓', R / 1000),
string.format('%.0f↑', S / 1000)
)
end
widget = luastatus.require_plugin('network-rate-linux').widget{
iface_except = 'lo',
period = 3,
in_array_form = true,
cb = function(t)
local r = {}
for _, PQ in ipairs(t) do
local iface = PQ[1]
local R, S = PQ[2].R, PQ[2].S
r[#r + 1] = make_segment(iface, R, S)
end
return r
end,
}
================================================
FILE: examples/lemonbar/pulse-gauge.lua
================================================
local GAUGE_NCHARS = 10
local function mk_gauge(level, full, empty)
local nfull = math.floor(level * GAUGE_NCHARS + 0.5)
return full:rep(nfull) .. empty:rep(GAUGE_NCHARS - nfull)
end
widget = {
plugin = 'pulse',
cb = function(t)
local level = t.cur / t.norm
if t.mute then
return "%{F#e03838}" .. mk_gauge(level, '×', '—') .. "%{F-}"
else
return "%{F#718ba6}" .. mk_gauge(level, '●', '○') .. "%{F-}"
end
end,
}
================================================
FILE: examples/lemonbar/pulse.lua
================================================
widget = {
plugin = 'pulse',
cb = function(t)
if t.mute then
return "%{F#e03838}" .. '[mute]' .. "%{F-}"
end
local percent = (t.cur / t.norm) * 100
local body = string.format('[%3d%%]', math.floor(0.5 + percent))
return "%{F#718ba6}" .. luastatus.barlib.escape(body) .. "%{F-}"
end,
}
================================================
FILE: examples/lemonbar/systemd-unit.lua
================================================
local COLORS = {
pink = '#ec93d3',
green = '#60b48a',
yellow = '#dfaf8f',
red = '#dca3a3',
dim = '#909090',
}
local function make_output(text, color_name)
local color_begin = '%{F' .. assert(COLORS[color_name]) .. '}'
local color_end = '%{F-}'
return string.format('[Tor: %s%s%s]', color_begin, text, color_end)
end
widget = luastatus.require_plugin('systemd-unit').widget{
unit_name = 'tor.service',
cb = function(state)
if state == 'active' then
return make_output('✓', 'green')
elseif state == 'reloading' or state == 'activating' then
return make_output('•', 'yellow')
elseif state == 'inactive' or state == 'deactivating' then
return make_output('-', 'dim')
elseif state == 'failed' then
return make_output('x', 'red')
else
return make_output('?', 'pink')
end
end,
}
================================================
FILE: examples/lemonbar/time-battery-combined.lua
================================================
local function get_bat_seg(t)
if not t then
return '[--×--]'
end
if t.status == 'Unknown' or t.status == 'Full' or t.status == 'Not charging' then
return nil
end
local sym, color = '?', '#dcdcdc'
if t.status == 'Discharging' then
sym = '↓'
color = '#dca3a3'
elseif t.status == 'Charging' then
sym = '↑'
color = '#60b48a'
end
local body = string.format('[%3d%%%s]', t.capacity, sym)
return '%{F' .. color .. '}' .. luastatus.barlib.escape(body) .. '%{F-}'
end
widget = luastatus.require_plugin('battery-linux').widget{
period = 2,
cb = function(t)
return {
"%{F#dc8cc3}" .. os.date('[%H:%M]') .. "%{F-}",
get_bat_seg(t),
}
end,
}
================================================
FILE: examples/lemonbar/time-date.lua
================================================
local months = {
'января',
'февраля',
'марта',
'апреля',
'мая',
'июня',
'июля',
'августа',
'сентября',
'октября',
'ноября',
'декабря',
}
widget = {
plugin = 'timer',
cb = function()
local d = os.date('*t')
return {
string.format('%d %s', d.day, months[d.month]),
string.format('%02d:%02d', d.hour, d.min),
}
end,
}
================================================
FILE: examples/lemonbar/tor.lua
================================================
-- Trivial but somewhat useful widget showing if the Tor daemon is running.
widget = {
plugin = 'timer',
opts = {period = 5},
cb = function()
local f = io.open('/var/run/tor/tor.pid', 'r')
if f then
f:close()
return '[TOR]'
else
return nil
end
end,
}
================================================
FILE: examples/lemonbar/uptime-linux.lua
================================================
local SUFFIXES_AND_DIVISORS = {
{'m', 60},
{'h', 60},
{'d', 24},
{'W', 7},
{'M', 30},
{'Y', 365 / 30},
}
local function seconds_to_human_readable_time(sec)
local prev_suffix = 's'
local found_suffix
for _, PQ in ipairs(SUFFIXES_AND_DIVISORS) do
local suffix = PQ[1]
local divisor = PQ[2]
if sec < divisor then
found_suffix = prev_suffix
break
end
sec = sec / divisor
prev_suffix = suffix
end
if not found_suffix then
found_suffix = prev_suffix
end
return string.format('%.0f%s', sec, found_suffix)
end
widget = {
plugin = 'timer',
opts = {
period = 2,
},
cb = function(_)
local f = io.open('/proc/uptime', 'r')
local sec, _ = f:read('*number', '*number')
f:close()
assert(sec)
return string.format(
'[uptime: %s]',
seconds_to_human_readable_time(sec)
)
end,
}
================================================
FILE: examples/lemonbar/weather-rus.lua
================================================
local LOCATION_NAME = 'Moscow'
local TIMEOUT = 10
local INTERVAL = 9
local SLEEP_AFTER_INITIAL = 5
local coordinates = nil
local function planner()
-- Get coordinates of LOCATION_NAME.
while true do
-- Form the URL
local url = string.format(
'https://geocoding-api.open-meteo.com/v1/search?name=%s&count=1',
luastatus.plugin.urlencode(LOCATION_NAME)
)
-- Make request
coroutine.yield({action = 'request', params = {
url = url,
timeout = TIMEOUT,
}})
-- If successful, break
if coordinates ~= nil then
break
end
-- Failure; sleep and retry
coroutine.yield({action = 'sleep', period = INTERVAL})
end
-- Coordinates fetched, sleep for SLEEP_AFTER_INITIAL.
coroutine.yield({action = 'sleep', period = SLEEP_AFTER_INITIAL})
-- Form the weather URL
local weather_url = string.format(
'https://api.open-meteo.com/v1/forecast' ..
'?latitude=%f' ..
'&longitude=%f' ..
'¤t_weather=true',
coordinates.latitude,
coordinates.longitude
)
-- Fetch weather, periodically
while true do
-- Make request
coroutine.yield({action = 'request', params = {
url = weather_url,
timeout = TIMEOUT,
}})
-- Sleep and repeat
coroutine.yield({action = 'sleep', period = INTERVAL})
end
end
local function check_error(t)
if t.error then
-- Low-level libcurl error
return t.error
end
if t.status < 200 or t.status > 299 then
-- Bad HTTP status
return string.format('HTTP status %d', t.status)
end
-- Everything's OK
return nil
end
local WEATHER_CODES = {
[0] = 'Ясно',
[1] = 'Преимущественно ясно',
[2] = 'Переменная облачность',
[3] = 'Пасмурно',
[45] = 'Туман',
[48] = 'Осаждение инея и тумана',
[51] = 'Изморось, лёгкая',
[53] = 'Изморось, умеренная',
[55] = 'Изморось, сильная',
[56] = 'Ледяная изморось, лёгкая',
[57] = 'Ледяная изморось, сильная',
[61] = 'Дождь, лёгкий',
[63] = 'Дождь, умеренный',
[65] = 'Дождь, сильный',
[66] = 'Град, лёгкий',
[67] = 'Град, сильный',
[71] = 'Снегопад, лёгкий',
[73] = 'Снегопад, умеренный',
[75] = 'Снегопад, сильный',
[77] = 'Крупинки снега',
[80] = 'Ливень, лёгкий',
[81] = 'Ливень, умеренный',
[82] = 'Ливень, сильный',
[85] = 'Снеговые осадки, лёгкие',
[87] = 'Снеговые осадки, сильные',
[95] = 'Гроза',
[96] = 'Гроза, небольшой град',
[99] = 'Гроза, сильный град',
}
local function fmt_weather(temp, _, _, weather_code)
local segment1 = string.format('%.0f°', temp)
-- segment2 is either string or nil
local segment2 = WEATHER_CODES[weather_code]
return {segment1, segment2}
end
widget = {
plugin = 'web',
opts = {
planner = planner,
},
cb = function(t)
local err_msg = check_error(t)
if err_msg then
print(string.format('WARNING: luastatus: weather widget: %s'), err_msg)
return '%{F#e03838}...%{F-}'
end
if coordinates == nil then
local obj = assert(luastatus.plugin.json_decode(t.body))
local result = assert(obj.results[1])
local latitude = assert(result.latitude)
local longitude = assert(result.longitude)
coordinates = {latitude = latitude, longitude = longitude}
return '%{F#709080}...%{F-}'
end
local obj = assert(luastatus.plugin.json_decode(t.body))
local cw = assert(obj.current_weather)
local temp = assert(cw.temperature)
local wind_speed = assert(cw.windspeed)
-- is_day is an integer, either 0 or 1
local is_day = assert(cw.is_day) ~= 0
local weather_code = assert(cw.weathercode)
return fmt_weather(temp, wind_speed, is_day, weather_code)
end,
}
================================================
FILE: examples/lemonbar/weather.lua
================================================
local LOCATION_NAME = 'Moscow'
local TIMEOUT = 10
local INTERVAL = 9
local SLEEP_AFTER_INITIAL = 5
local coordinates = nil
local function planner()
-- Get coordinates of LOCATION_NAME.
while true do
-- Form the URL
local url = string.format(
'https://geocoding-api.open-meteo.com/v1/search?name=%s&count=1',
luastatus.plugin.urlencode(LOCATION_NAME)
)
-- Make request
coroutine.yield({action = 'request', params = {
url = url,
timeout = TIMEOUT,
}})
-- If successful, break
if coordinates ~= nil then
break
end
-- Failure; sleep and retry
coroutine.yield({action = 'sleep', period = INTERVAL})
end
-- Coordinates fetched, sleep for SLEEP_AFTER_INITIAL.
coroutine.yield({action = 'sleep', period = SLEEP_AFTER_INITIAL})
-- Form the weather URL
local weather_url = string.format(
'https://api.open-meteo.com/v1/forecast' ..
'?latitude=%f' ..
'&longitude=%f' ..
'¤t_weather=true',
coordinates.latitude,
coordinates.longitude
)
-- Fetch weather, periodically
while true do
-- Make request
coroutine.yield({action = 'request', params = {
url = weather_url,
timeout = TIMEOUT,
}})
-- Sleep and repeat
coroutine.yield({action = 'sleep', period = INTERVAL})
end
end
local function check_error(t)
if t.error then
-- Low-level libcurl error
return t.error
end
if t.status < 200 or t.status > 299 then
-- Bad HTTP status
return string.format('HTTP status %d', t.status)
end
-- Everything's OK
return nil
end
local WEATHER_CODES = {
[0] = 'Clear sky',
[1] = 'Mainly clear',
[2] = 'Partly cloudy',
[3] = 'Overcast',
[45] = 'Fog',
[48] = 'Depositing rime fog',
[51] = 'Drizzle, light',
[53] = 'Drizzle, moderate',
[55] = 'Drizzle, dense',
[56] = 'Freezing drizzle, light',
[57] = 'Freezing drizzle, dense',
[61] = 'Rain, slight',
[63] = 'Rain, moderate',
[65] = 'Rain, heavy',
[66] = 'Freezing rain, light',
[67] = 'Freezing rain, heavy',
[71] = 'Snow fall, slight',
[73] = 'Snow fall, moderate',
[75] = 'Snow fall, heavy',
[77] = 'Snow grains',
[80] = 'Rain shower, slight',
[81] = 'Rain shower, moderate',
[82] = 'Rain shower, violent',
[85] = 'Snow showers, slight',
[87] = 'Snow showers, heavy',
[95] = 'Thunderstorm',
[96] = 'Thunderstorm, slight hail',
[99] = 'Thunderstorm, heavy hail',
}
local function fmt_weather(temp, _, _, weather_code)
local segment1 = string.format('%.0f°', temp)
-- segment2 is either string or nil
local segment2 = WEATHER_CODES[weather_code]
return {segment1, segment2}
end
widget = {
plugin = 'web',
opts = {
planner = planner,
},
cb = function(t)
local err_msg = check_error(t)
if err_msg then
print(string.format('WARNING: luastatus: weather widget: %s'), err_msg)
return '%{F#e03838}...%{F-}'
end
if coordinates == nil then
local obj = assert(luastatus.plugin.json_decode(t.body))
local result = assert(obj.results[1])
local latitude = assert(result.latitude)
local longitude = assert(result.longitude)
coordinates = {latitude = latitude, longitude = longitude}
return '%{F#709080}...%{F-}'
end
local obj = assert(luastatus.plugin.json_decode(t.body))
local cw = assert(obj.current_weather)
local temp = assert(cw.temperature)
local wind_speed = assert(cw.windspeed)
-- is_day is an integer, either 0 or 1
local is_day = assert(cw.is_day) ~= 0
local weather_code = assert(cw.weathercode)
return fmt_weather(temp, wind_speed, is_day, weather_code)
end,
}
================================================
FILE: examples/lemonbar/wireless.lua
================================================
local ORIGIN = '•' -- '●' '○' '◌' '◍' '⬤'
local RAY = '}' -- '›' '〉' '❭' '❯' '❱' '⟩' '⟫' '》'
local MIN_DBM, MAX_DBM = -90, -20
local NGAUGE = 5
local COLOR_DIM = '#709080'
local function round(x)
return math.floor(x + 0.5)
end
local function colorize_as_dim(s)
s = luastatus.barlib.escape(s)
return '%{F' .. COLOR_DIM .. '}' .. s .. '%{F-}'
end
local function make_wifi_gauge(dbm)
if dbm < MIN_DBM then dbm = MIN_DBM end
if dbm > MAX_DBM then dbm = MAX_DBM end
local nbright = round(NGAUGE * (1 - 0.7 * (MAX_DBM - dbm) / (MAX_DBM - MIN_DBM)))
return ORIGIN .. RAY:rep(nbright) .. colorize_as_dim(RAY:rep(NGAUGE - nbright))
end
local function sorted_keys(tbl)
local keys = {}
for k, _ in pairs(tbl) do
keys[#keys + 1] = k
end
table.sort(keys)
return keys
end
widget = {
plugin = 'network-linux',
opts = {
wireless = true,
timeout = 10,
},
cb = function(t)
if not t then
return nil
end
-- Sort for determinism
local ifaces = sorted_keys(t)
local r = {}
for _, iface in ipairs(ifaces) do
local params = t[iface]
if params.wireless then
if params.wireless.ssid then
r[#r + 1] = colorize_as_dim(params.wireless.ssid)
end
if params.wireless.signal_dbm then
r[#r + 1] = make_wifi_gauge(params.wireless.signal_dbm)
end
elseif iface ~= 'lo' and (params.ipv4 or params.ipv6) then
r[#r + 1] = colorize_as_dim(string.format('[%s]', iface))
end
end
return r
end,
}
================================================
FILE: examples/lemonbar/xkb.lua
================================================
widget = {
plugin = 'xkb',
cb = function(t)
if t.name then
local base_layout = t.name:match('[^(]+')
if base_layout == 'gb' or base_layout == 'us' then
return "%{F#9c9c9c}" .. '[En]' .. "%{F-}"
elseif base_layout == 'ru' then
return "%{F#eab93d}" .. '[Ru]' .. "%{F-}"
else
local body = string.format('[%s]', base_layout)
return luastatus.barlib.escape(body)
end
else
return '[? ID ' .. t.id .. ']'
end
end,
}
================================================
FILE: fuzz_utils/do_fuzz_everything.sh
================================================
#!/usr/bin/env bash
set -e
shopt -s nullglob
if [[ -z "$XXX_AFL_DIR" ]]; then
echo >&2 "You must set the 'XXX_AFL_DIR' environment variable."
exit 1
fi
case "$XXX_AFL_IS_PP" in
0)
;;
1)
;;
*)
echo >&2 "You must set 'XXX_AFL_IS_PP' environment variable to either 0 or 1."
echo >&2 "Set it to 0 if AFL is the original Google's version (not AFL++)."
echo >&2 "Set it to 1 if AFL is actually AFL++."
exit 1
;;
esac
cd -- "$(dirname "$(readlink "$0" || printf '%s\n' "$0")")"
cd ..
fuzz_dirs=(
plugins/*/fuzz*
barlibs/*/fuzz*
)
if (( XXX_AFL_IS_PP )); then
afl_cc="$XXX_AFL_DIR"/afl-cc
else
afl_cc="$XXX_AFL_DIR"/afl-gcc
fi
for d in "${fuzz_dirs[@]}"; do
[[ -d "$d" ]] || continue
"$d"/clear.sh
CC="$afl_cc" "$d"/build.sh
if [[ -x "$d"/fuzz_all.sh ]]; then
"$d"/fuzz_all.sh
else
"$d"/fuzz.sh
fi
done
================================================
FILE: fuzz_utils/find_interesting.sh
================================================
#!/usr/bin/env bash
set -e
shopt -s nullglob
cd -- "$(dirname "$(readlink "$0" || printf '%s\n' "$0")")"
cd ..
is_dir_empty() {
local -a files
files=( "$1"/* "$1"/.* )
(( "${#files[@]}" == 2 ))
}
say() {
printf '%s\n' "$*" >&2
}
fuzz_dirs=(
plugins/*/fuzz*
barlibs/*/fuzz*
)
results=()
for d1 in "${fuzz_dirs[@]}"; do
[[ -d "$d1" ]] || continue
for d2 in "$d1"/findgins*; do
[[ -d "$d2" ]] || continue
for d3 in "$d2"/crashes "$d2"/hangs; do
[[ -d "$d3" ]] || continue
if ! is_dir_empty "$d3"; then
results+=("$d3")
fi
done
done
done
if (( ${#results[@]} != 0 )); then
say 'The following "interesting" directories are non-empty:'
for res in "${results[@]}"; do
say " * $res"
done
exit 1
fi
say 'Everything is OK.'
exit 0
================================================
FILE: fuzz_utils/fuzz_utils.h
================================================
/*
* Copyright (C) 2025 luastatus developers
*
* This file is part of luastatus.
*
* luastatus is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* luastatus is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with luastatus. If not, see .
*/
#ifndef luastatus_fuzz_utils_h_
#define luastatus_fuzz_utils_h_
#include
#include
#include
#include
#include
// At least for fuzzing, please use a reasonably modern GNU C-compatible compiler.
#define FUZZ_UTILS_INHEADER \
static inline __attribute__((unused))
#define FUZZ_UTILS_ATTR_WARN_UNUSED \
__attribute__((warn_unused_result))
#define FUZZ_UTILS_OOM() \
do { \
fputs("fuzz_utils: out of memory.\n", stderr); \
abort(); \
} while (0)
typedef struct {
char *data;
size_t size;
size_t capacity;
} FuzzInput;
FUZZ_UTILS_INHEADER FuzzInput fuzz_input_new_prealloc(size_t prealloc)
{
if (!prealloc) {
return (FuzzInput) {0};
}
char *data = malloc(prealloc);
if (!data) {
FUZZ_UTILS_OOM();
}
return (FuzzInput) {
.data = data,
.size = 0,
.capacity = prealloc,
};
}
FUZZ_UTILS_INHEADER void fuzz_input__ensure1(FuzzInput *x)
{
if (x->size != x->capacity) {
return;
}
if (x->capacity) {
if (x->capacity > SIZE_MAX / 2) {
goto oom;
}
x->capacity *= 2;
} else {
x->capacity = 1;
}
if (!(x->data = realloc(x->data, x->capacity))) {
goto oom;
}
return;
oom:
FUZZ_UTILS_OOM();
}
FUZZ_UTILS_INHEADER FUZZ_UTILS_ATTR_WARN_UNUSED
int fuzz_input_read(int fd, FuzzInput *dst)
{
for (;;) {
fuzz_input__ensure1(dst);
ssize_t r = read(fd, dst->data + dst->size, dst->capacity - dst->size);
if (r < 0) {
return -1;
} else if (r == 0) {
return 0;
}
dst->size += r;
}
}
FUZZ_UTILS_INHEADER void fuzz_input_free(FuzzInput x)
{
free(x.data);
}
FUZZ_UTILS_INHEADER void fuzz_utils_used(const char *ptr, size_t len)
{
// This is a compiler barrier: there's a contract between the compiler and
// the user that the compiler doesn't try to guess what inline assembly does,
// even if its body is empty.
//
// So after this, the compiler conservately thinks the span is used, because
// the inline assembly might have dereferenced it and done something that has
// side effects (e.g. made a system call "write(1, ptr, len)").
__asm__ __volatile__ (
""
:
: "r"(ptr), "r"(len)
: "memory"
);
}
#endif
================================================
FILE: fuzz_utils/gen_testcases/.gitignore
================================================
__pycache__
================================================
FILE: fuzz_utils/gen_testcases/__init__.py
================================================
# Copyright (C) 2025 luastatus developers
#
# This file is part of luastatus.
#
# luastatus is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# luastatus is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with luastatus. If not, see .
================================================
FILE: fuzz_utils/gen_testcases/gen_testcases.py
================================================
#!/usr/bin/env python3
# Copyright (C) 2025 luastatus developers
#
# This file is part of luastatus.
#
# luastatus is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# luastatus is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with luastatus. If not, see .
import argparse
import sys
import random
import typing
import mod_generator
import mod_mutator
import mod_mutators
LENGTH_DEFAULT = 16
NUM_FILES_DEFAULT = 20
class ChunkSpec:
def __init__(self, weight: int, chunk: bytes, is_homogeneous: bool) -> None:
assert weight > 0
assert chunk
self.weight = weight
self.chunk = chunk
self.is_homogeneous = is_homogeneous
def make_chunk_spec_checked(weight: int, chunk: bytes, is_homogeneous: bool) -> ChunkSpec:
if weight <= 0:
raise ValueError('weight must be positive')
if not chunk:
raise ValueError('chunk is empty')
return ChunkSpec(weight=weight, chunk=chunk, is_homogeneous=is_homogeneous)
def into_h_w_and_rest(s: str) -> typing.Tuple[bool, int, str]:
is_homogeneous = False
if s.startswith('h:'):
is_homogeneous = True
s = s[2:]
weight_s, rest = s.split(':', maxsplit=1)
weight = int(weight_s)
return (is_homogeneous, weight, rest)
def parse_arg_ab(s: str) -> ChunkSpec:
is_homogeneous, weight, content = into_h_w_and_rest(s)
return make_chunk_spec_checked(
weight=weight,
chunk=content.encode(),
is_homogeneous=is_homogeneous)
def parse_arg_ab_range(s: str) -> ChunkSpec:
is_homogeneous, weight, content = into_h_w_and_rest(s)
first_s, last_s = content.split('-')
first, last = int(first_s), int(last_s)
chunk = bytes(list(range(first, last + 1)))
return make_chunk_spec_checked(
weight=weight,
chunk=chunk,
is_homogeneous=is_homogeneous)
def parse_arg_length(s: str) -> typing.Tuple[int, int]:
if '-' in s:
n_min_s, n_max_s = s.split('-')
n_min, n_max = int(n_min_s), int(n_max_s)
else:
n = int(s)
n_min, n_max = n, n
if (n_min > n_max) or (n_min < 0) or (n_max < 0):
raise ValueError('invalid length specifier')
return (n_min, n_max)
def parse_arg_num_files(s: str) -> int:
n = int(s)
if n < 3:
raise ValueError('--num-files value must be at least 3')
return n
def parse_arg_prefix(s: str) -> bytes:
return s.encode()
def parse_arg_substrings(s: str) -> typing.List[bytes]:
separator = s[0]
parts = s[1:].split(separator)
return [part.encode() for part in parts]
def parse_arg_extra_testcase(s: str) -> typing.Tuple[str, bytes]:
file_suffix, content_s = s.split(':', maxsplit=1)
return (file_suffix, content_s.encode())
def main() -> None:
ap = argparse.ArgumentParser()
ap.add_argument('dest_dir')
def _add_list_arg(spelling: str, **kwargs: typing.Any) -> None:
ap.add_argument(spelling, action='append', default=[], **kwargs)
_add_list_arg('--a', type=parse_arg_ab, metavar='')
_add_list_arg('--a-range', type=parse_arg_ab_range, metavar='')
_add_list_arg('--b', type=parse_arg_ab, metavar='')
_add_list_arg('--b-range', type=parse_arg_ab_range, metavar='')
ap.add_argument(
'--a-is-important',
action='store_true')
ap.add_argument(
'--length',
default=(LENGTH_DEFAULT, LENGTH_DEFAULT),
type=parse_arg_length,
metavar='|-')
ap.add_argument(
'--num-files',
default=NUM_FILES_DEFAULT,
type=parse_arg_num_files,
metavar='')
ap.add_argument(
'--mut-prefix',
type=parse_arg_prefix,
required=False)
_add_list_arg('--extra-testcase', type=parse_arg_extra_testcase)
ap.add_argument(
'--mut-substrings',
type=parse_arg_substrings,
required=False)
ap.add_argument(
'--file-prefix',
default='')
ap.add_argument(
'--random-seed',
type=int,
required=False,
metavar='')
args = ap.parse_args()
if args.random_seed is not None:
random.seed(args.random_seed, version=2)
g = mod_generator.Generator()
def _add_choices(chunk_specs: typing.List[ChunkSpec], key: str) -> None:
for chunk_spec in chunk_specs:
sheaf = mod_generator.Sheaf(
chunk_spec.chunk,
is_homogeneous=chunk_spec.is_homogeneous)
g.add_sheaf_for_key(
key=key,
sheaf=sheaf,
weight=chunk_spec.weight)
_add_choices(args.a, key='A')
_add_choices(args.a_range, key='A')
_add_choices(args.b, key='B')
_add_choices(args.b_range, key='B')
if not (g.has_choices_for_key('A') and g.has_choices_for_key('B')):
if not g.has_choices_for_key('A'):
print('Ether "--a" or "--a-range" must be specified.', file=sys.stderr)
else:
print('Ether "--b" or "--b-range" must be specified.', file=sys.stderr)
ap.print_help(sys.stderr)
sys.exit(2)
mutators: typing.List[mod_mutator.Mutator] = []
if args.mut_substrings is not None:
mutators.append(mod_mutators.SubstringMutator(args.mut_substrings))
if args.mut_prefix is not None:
mutators.append(mod_mutators.PrefixMutator(args.mut_prefix))
max_variants = max((m.get_number_of_variants() for m in mutators), default=-1)
if max_variants > args.num_files:
print(
'Specified mutators have too many variants (%d) for --num-files=%d' % (
max_variants, args.num_files
),
file=sys.stderr)
sys.exit(2)
def _gen_filename(suffix: str) -> str:
return f'{args.dest_dir}/{args.file_prefix}testcase_{suffix}'
length_min, length_max = args.length
if args.a_is_important:
whole_length = g.get_whole_length_for_key('A')
if length_min < whole_length:
print(
'Minimal length (%d) is less than number of whole bytes in A (%d)' % (
length_min, whole_length
),
file=sys.stderr)
sys.exit(2)
for i in range(args.num_files):
with open(_gen_filename(f'{i:03d}'), 'wb') as f:
length = random.randint(length_min, length_max)
include_all_of_A = (i == 0) and args.a_is_important
content = mod_mutator.generate_with_mutators(
g=g,
mutators=mutators,
length=length,
i=i,
n=args.num_files,
include_all_of_A=include_all_of_A)
f.write(content)
for file_suffix, content in args.extra_testcase:
with open(_gen_filename(file_suffix), 'wb') as f:
f.write(content)
if __name__ == '__main__':
main()
================================================
FILE: fuzz_utils/gen_testcases/mod_generator.py
================================================
# Copyright (C) 2025 luastatus developers
#
# This file is part of luastatus.
#
# luastatus is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# luastatus is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with luastatus. If not, see .
import random
import typing
def _clamp(x: int, min_value: int, max_value: int) -> int:
assert min_value <= max_value
if x < min_value:
return min_value
if x > max_value:
return max_value
return x
class Sheaf:
def __init__(self, b: bytes, is_homogeneous: bool) -> None:
assert b
self.b = b
self.is_homogeneous = is_homogeneous
def get_length(self) -> int:
return len(self.b)
def random_byte(self) -> bytes:
i = random.randint(0, len(self.b) - 1)
return self.b[i:i + 1]
def all_bytes(self) -> typing.Iterator[bytes]:
for i in range(len(self.b)):
yield self.b[i:i + 1]
class WeightedSheafList:
def __init__(self) -> None:
self.sheaves: typing.List[Sheaf] = []
self.weights: typing.List[int] = []
def add_sheaf(self, sheaf: Sheaf, weight: int) -> None:
assert weight > 0
self.sheaves.append(sheaf)
self.weights.append(weight)
def is_nonempty(self) -> bool:
return bool(self.sheaves)
def random_sheaf(self) -> Sheaf:
assert self.is_nonempty()
res, = random.choices(self.sheaves, weights=self.weights)
return res
def get_whole_length(self) -> int:
res = 0
for sheaf in self.sheaves:
if sheaf.is_homogeneous:
res += 1
else:
res += sheaf.get_length()
return res
def make_whole_iterator(self) -> typing.Iterator[bytes]:
for sheaf in self.sheaves:
if sheaf.is_homogeneous:
yield sheaf.random_byte()
else:
for byte in sheaf.all_bytes():
yield byte
while True:
yield self.random_sheaf().random_byte()
class Generator:
def __init__(self) -> None:
self.wsls = {'A': WeightedSheafList(), 'B': WeightedSheafList()}
def add_sheaf_for_key(self, key: str, sheaf: Sheaf, weight: int) -> None:
self.wsls[key].add_sheaf(sheaf, weight=weight)
def has_choices_for_key(self, key: str) -> bool:
return self.wsls[key].is_nonempty()
def get_whole_length_for_key(self, key: str) -> int:
return self.wsls[key].get_whole_length()
def random_bytes(self, length: int, i: int, n: int, include_all_of_A: bool) -> bytes:
assert length >= 0
assert n > 0
assert 0 <= i < n
ratio = i / (n - 1)
num_B = _clamp(round(ratio * length), min_value=0, max_value=length)
num_A = length - num_B
formula = list('A' * num_A + 'B' * num_B)
random.shuffle(formula)
A_iter = None
if include_all_of_A:
assert self.wsls['A'].get_whole_length() <= length
A_iter = self.wsls['A'].make_whole_iterator()
chunks = []
for letter in formula:
if letter == 'A' and A_iter is not None:
chunks.append(next(A_iter))
else:
wsl = self.wsls[letter]
sheaf = wsl.random_sheaf()
chunks.append(sheaf.random_byte())
return b''.join(chunks)
================================================
FILE: fuzz_utils/gen_testcases/mod_mutator.py
================================================
# Copyright (C) 2025 luastatus developers
#
# This file is part of luastatus.
#
# luastatus is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# luastatus is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with luastatus. If not, see .
import typing
import mod_generator
class Mutator:
def mutate(self, b: bytes, variant: int) -> bytes:
raise NotImplementedError()
def get_number_of_variants(self) -> int:
raise NotImplementedError()
def generate_with_mutators(
g: mod_generator.Generator,
mutators: typing.List[Mutator],
length: int,
i: int,
n: int,
include_all_of_A: bool
) -> bytes:
assert n > 0
assert 0 <= i < n
res = g.random_bytes(length=length, i=i, n=n, include_all_of_A=include_all_of_A)
for m in mutators:
variant = i % m.get_number_of_variants()
res = m.mutate(res, variant=variant)
return res
================================================
FILE: fuzz_utils/gen_testcases/mod_mutators.py
================================================
# Copyright (C) 2025 luastatus developers
#
# This file is part of luastatus.
#
# luastatus is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# luastatus is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with luastatus. If not, see .
import random
import typing
import mod_mutator
class PrefixMutator(mod_mutator.Mutator):
def __init__(self, prefix: bytes) -> None:
self.prefix = prefix
def mutate(self, b: bytes, variant: int) -> bytes:
return self.prefix[:variant] + b
def get_number_of_variants(self) -> int:
return len(self.prefix) + 1
class SubstringMutator(mod_mutator.Mutator):
def __init__(self, substr_variants: typing.List[bytes]) -> None:
self.substr_variants = substr_variants
def mutate(self, b: bytes, variant: int) -> bytes:
pos = random.randint(0, len(b))
substr = self.substr_variants[variant]
return b[:pos] + substr + b[pos:]
def get_number_of_variants(self) -> int:
return len(self.substr_variants)
================================================
FILE: generate-man.sh
================================================
#!/bin/sh
if [ "$#" -ne 2 ]; then
printf '%s\n' >&2 "USAGE: $0 "
exit 2
fi
has() {
command -v "$1" >/dev/null
}
if has rst2man; then
RST2MAN=rst2man
elif has rst2man.py; then
RST2MAN=rst2man.py
else
echo >&2 "ERROR: neither 'rst2man' nor 'rst2man.py' were found."
exit 1
fi
sed 's/^\.\. :X-man-page-only: \?//' -- "$1" | $RST2MAN > "$2"
================================================
FILE: include/barlib_data.h
================================================
/*
* Copyright (C) 2015-2025 luastatus developers
*
* This file is part of luastatus.
*
* luastatus is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* luastatus is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with luastatus. If not, see .
*/
#ifndef luastatus_include_barlib_data_h_
#define luastatus_include_barlib_data_h_
#include
#include
#include "common.h"
#ifndef LUASTATUS_BARLIB_SAYF_ATTRIBUTE
# ifdef __GNUC__
# define LUASTATUS_BARLIB_SAYF_ATTRIBUTE __attribute__((format(printf, 3, 4)))
# else
# define LUASTATUS_BARLIB_SAYF_ATTRIBUTE /*nothing*/
# endif
#endif
typedef LUASTATUS_BARLIB_SAYF_ATTRIBUTE void LuastatusBarlibSayf_v1(
void *userdata, int level, const char *fmt, ...);
typedef struct {
// This barlib's private data.
void *priv;
// A black-box user data.
void *userdata;
// Should be used for logging.
LuastatusBarlibSayf_v1 *sayf;
// See DOCS/design/map_get.md.
void ** (*map_get)(void *userdata, const char *key);
} LuastatusBarlibData_v1;
typedef struct {
lua_State *(*call_begin) (void *userdata, size_t widget_idx);
void (*call_end) (void *userdata, size_t widget_idx);
void (*call_cancel)(void *userdata, size_t widget_idx);
} LuastatusBarlibEWFuncs_v1;
typedef struct {
// This function should initialize a barlib by assigning something to /bd->priv/.
// You would typically do:
//
// typedef struct {
// ...
// } Priv;
// ...
// static
// int
// init(LuastatusBarlibData *bd, const char *const *opts, size_t nwidgets)
// {
// Priv *p = bd->priv = LS_XNEW(Priv, 1);
// ...
//
// /opts/ are options passed to luastatus with /-B/ switches, with a sentinel /NULL/ entry.
//
// /nwidgets/ is the number of widgets. It stays unchanged during the entire life cycle of a
// barlib.
//
// It should return:
//
// /LUASTATUS_ERR/ on failure;
//
// /LUASTATUS_OK/ on success.
//
int (*init)(LuastatusBarlibData_v1 *bd, const char *const *opts, size_t nwidgets);
// This function should register Lua functions provided by the barlib into the table on the top
// of /L/'s stack.
//
// These functions should be thread-safe!
//
// It is guaranteed that /L/ stack has at least 15 free stack slots.
//
// May be /NULL/.
//
void (*register_funcs)(LuastatusBarlibData_v1 *bd, lua_State *L);
// This function should update the widget with index /widget_idx/. The data is located on the
// top of /L/'s stack; its format defined by the barlib itself.
//
// This function may push elements onto /L/'s stack (to iterate over tables), but should not
// modify elements below the initial top, or interact with /L/ in any other way.
//
// It is guaranteed that /L/'s stack has at least 15 free slots.
//
// It must return:
//
// /LUASTATUS_OK/ on success.
// In this case, /L/'s stack must not contain any extra elements pushed onto it;
//
// /LUASTATUS_NONFATAL_ERR/ on a non-fatal error, e.g., if the format of the data on top of
// /L/'s stack is invalid.
// In this case, /L/'s stack may contain extra elements pushed onto it;
//
// /LUASTATUS_ERR/ on a fatal error, e.g. if the connection to the display has been lost.
// In this case, /L/'s stack may contain extra elements pushed onto it.
//
int (*set)(LuastatusBarlibData_v1 *bd, lua_State *L, size_t widget_idx);
// This function should update the widget with index /widget_idx/ and set its content to
// something that indicates an error.
//
// It must return:
//
// /LUASTATUS_OK/ on success;
//
// /LUASTATUS_ERR/ on a fatal error, e.g. if the connection to the display has been lost.
//
int (*set_error)(LuastatusBarlibData_v1 *bd, size_t widget_idx);
// This function should run barlib's event watcher. Once the barlib wants to report an event
// occurred on widget with index /i/, it should call
// /lua_State *L = funcs.call_begin(i, bd->userdata)/,
// thus obtaining a /lua_State */ object /L/. Then, it must either:
// 1. make the following call, with exactly one additional value pushed onto /L/'s stack:
// /funcs.call_end(i, bd->userdata)/,
// or
// 2. make the following call, with any number of additional values push onto /L/'s stack:
// /funcs.call_cancel(i, bd->userdata)/.
//
// Both /funcs.call_end/ and /funcs.call_cancel/ invalidate /L/ as a pointer.
//
// It is guaranteed that each /L/ object returned from /funcs.call_begin/ has at least 15 free
// stack slots.
//
// It must return:
// /LUASTATUS_NONFATAL_ERR/ if, in some reason other than a fatal error, no events will be
// reported anymore;
//
// /LUASTATUS_ERR/ on a fatal error, e.g. if the connection to the display has been lost.
//
// May be /NULL/ (and should be /NULL/ if events are not supported).
//
int (*event_watcher)(LuastatusBarlibData_v1 *bd, LuastatusBarlibEWFuncs_v1 funcs);
// This function must destroy a previously successfully initialized barlib.
void (*destroy)(LuastatusBarlibData_v1 *bd);
} LuastatusBarlibIface_v1;
#endif
================================================
FILE: include/barlib_data_v1.h
================================================
/*
* Copyright (C) 2015-2025 luastatus developers
*
* This file is part of luastatus.
*
* luastatus is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* luastatus is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with luastatus. If not, see .
*/
#ifndef luastatus_include_barlib_data_v1_h_
#define luastatus_include_barlib_data_v1_h_
#include "barlib_data.h"
#define LuastatusBarlibIface LuastatusBarlibIface_v1
#define LuastatusBarlibSayf LuastatusBarlibSayf_v1
#define LuastatusBarlibData LuastatusBarlibData_v1
#define LuastatusBarlibEWFuncs LuastatusBarlibEWFuncs_v1
#endif
================================================
FILE: include/barlib_v1.h
================================================
/*
* Copyright (C) 2015-2025 luastatus developers
*
* This file is part of luastatus.
*
* luastatus is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* luastatus is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with luastatus. If not, see .
*/
#ifndef luastatus_include_barlib_v1_h_
#define luastatus_include_barlib_v1_h_
#include
#include "barlib_data_v1.h"
#include "common.h"
const int LUASTATUS_BARLIB_LUA_VERSION_NUM = LUA_VERSION_NUM;
extern LuastatusBarlibIface_v1 luastatus_barlib_iface_v1;
#endif
================================================
FILE: include/common.h
================================================
/*
* Copyright (C) 2015-2025 luastatus developers
*
* This file is part of luastatus.
*
* luastatus is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* luastatus is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with luastatus. If not, see .
*/
#ifndef luastatus_include_common_h_
#define luastatus_include_common_h_
enum {
LUASTATUS_OK,
LUASTATUS_ERR,
LUASTATUS_NONFATAL_ERR,
};
enum {
LUASTATUS_LOG_FATAL,
LUASTATUS_LOG_ERR,
LUASTATUS_LOG_WARN,
LUASTATUS_LOG_INFO,
LUASTATUS_LOG_VERBOSE,
LUASTATUS_LOG_DEBUG,
LUASTATUS_LOG_TRACE,
LUASTATUS_LOG_LAST,
};
#endif
================================================
FILE: include/plugin_data.h
================================================
/*
* Copyright (C) 2015-2025 luastatus developers
*
* This file is part of luastatus.
*
* luastatus is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* luastatus is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with luastatus. If not, see .
*/
#ifndef luastatus_include_plugin_data_h_
#define luastatus_include_plugin_data_h_
#include
#include
#include "common.h"
#ifndef LUASTATUS_PLUGIN_SAYF_ATTRIBUTE
# ifdef __GNUC__
# define LUASTATUS_PLUGIN_SAYF_ATTRIBUTE __attribute__((format(printf, 3, 4)))
# else
# define LUASTATUS_PLUGIN_SAYF_ATTRIBUTE /*nothing*/
# endif
#endif
typedef LUASTATUS_PLUGIN_SAYF_ATTRIBUTE void LuastatusPluginSayf_v1(
void *userdata, int level, const char *fmt, ...);
typedef struct {
// This widget's private data.
void *priv;
// A black-box user data.
void *userdata;
// Should be used for logging.
LuastatusPluginSayf_v1 *sayf;
// See DOCS/design/map_get.md.
void ** (*map_get)(void *userdata, const char *key);
} LuastatusPluginData_v1;
typedef struct {
lua_State *(*call_begin) (void *userdata);
void (*call_end) (void *userdata);
void (*call_cancel)(void *userdata);
} LuastatusPluginRunFuncs_v1;
typedef struct {
// This function should initialize a widget by assigning something to /pd->priv/.
// You would typically do:
//
// typedef struct {
// ...
// } Priv;
// ...
// static
// int
// init(LuastatusPluginData *pd, lua_State *L) {
// Priv *p = pd->priv = LS_XNEW(Priv, 1);
// ...
//
// The options table is on the top of /L/'s stack.
//
// This function may push elements onto /L/'s stack to iterate over tables, but should not
// modify elements below the initial top, or interact with /L/ in any other way.
//
// It is guaranteed that /L/'s stack has at least 15 free slots.
//
// It should return:
//
// /LUASTATUS_OK/ on success.
// In this case, /L/'s stack should not contain any extra elements pushed onto it;
//
// /LUASTATUS_ERR/ on failure.
// In this case, /L/'s stack may contain extra elements pushed onto it.
//
int (*init)(LuastatusPluginData_v1 *pd, lua_State *L);
// This function should register Lua functions provided by the plugin into table on top of /L/'s
// stack.
//
// These functions, in current implementation, are not required to be thread-safe with respect
// to the widget instance, that is, /register_funcs/ in only called once per plugin instance,
// and while the widget is calling a function registered by this plugin instance, no other Lua
// state may do so.
//
// This may change in the future.
//
// Note that the above does not mean they are allowed to be genereally thread-unsafe.
//
// It is guaranteed that /L/'s stack has at least 15 free slots.
//
// May be /NULL/.
void (*register_funcs)(LuastatusPluginData_v1 *pd, lua_State *L);
// This function should run a widget. Once the plugin wants to update the widget, it should call
// /lua_State *L = funcs.call_begin(pd->userdata)/,
// thus obtaining a /lua_State */ object /L/. Then it must either:
// 1. make the following call, with exactly one additional value pushed onto /L/'s stack:
// /funcs.call_end(bd->userdata)/,
// or
// 2. make the following call, with any number of additional values push onto /L/'s stack:
// /funcs.call_cancel(bd->userdata)/.
//
// Both /funcs.call_end/ and /funcs.call_cancel/ invalidate /L/ as a pointer.
//
// It is guaranteed that each /L/ object returned from /funcs.call_begin/ has at least 15 free
// stack slots.
//
// It should only return on an unrecoverable failure.
void (*run)(LuastatusPluginData_v1 *pd, LuastatusPluginRunFuncs_v1 funcs);
// This function should destroy a previously successfully initialized widget.
void (*destroy)(LuastatusPluginData_v1 *pd);
} LuastatusPluginIface_v1;
#endif
================================================
FILE: include/plugin_data_v1.h
================================================
/*
* Copyright (C) 2015-2025 luastatus developers
*
* This file is part of luastatus.
*
* luastatus is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* luastatus is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with luastatus. If not, see .
*/
#ifndef luastatus_include_plugin_data_v1_h_
#define luastatus_include_plugin_data_v1_h_
#include "plugin_data.h"
#define LuastatusPluginIface LuastatusPluginIface_v1
#define LuastatusPluginSayf LuastatusPluginSayf_v1
#define LuastatusPluginData LuastatusPluginData_v1
#define LuastatusPluginRunFuncs LuastatusPluginRunFuncs_v1
#endif
================================================
FILE: include/plugin_v1.h
================================================
/*
* Copyright (C) 2015-2025 luastatus developers
*
* This file is part of luastatus.
*
* luastatus is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* luastatus is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with luastatus. If not, see .
*/
#ifndef luastatus_include_plugin_v1_h_
#define luastatus_include_plugin_v1_h_
#include
#include "plugin_data_v1.h"
#include "common.h"
const int LUASTATUS_PLUGIN_LUA_VERSION_NUM = LUA_VERSION_NUM;
extern LuastatusPluginIface_v1 luastatus_plugin_iface_v1;
#endif
================================================
FILE: include/sayf_macros.h
================================================
/*
* Copyright (C) 2015-2025 luastatus developers
*
* This file is part of luastatus.
*
* luastatus is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* luastatus is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with luastatus. If not, see .
*/
#ifndef luastatus_include_sayf_macros_
#define luastatus_include_sayf_macros_
#define LS_FATALF(Data_, ...) Data_->sayf(Data_->userdata, LUASTATUS_LOG_FATAL, __VA_ARGS__)
#define LS_ERRF(Data_, ...) Data_->sayf(Data_->userdata, LUASTATUS_LOG_ERR, __VA_ARGS__)
#define LS_WARNF(Data_, ...) Data_->sayf(Data_->userdata, LUASTATUS_LOG_WARN, __VA_ARGS__)
#define LS_INFOF(Data_, ...) Data_->sayf(Data_->userdata, LUASTATUS_LOG_INFO, __VA_ARGS__)
#define LS_VERBOSEF(Data_, ...) Data_->sayf(Data_->userdata, LUASTATUS_LOG_VERBOSE, __VA_ARGS__)
#define LS_DEBUGF(Data_, ...) Data_->sayf(Data_->userdata, LUASTATUS_LOG_DEBUG, __VA_ARGS__)
#define LS_TRACEF(Data_, ...) Data_->sayf(Data_->userdata, LUASTATUS_LOG_TRACE, __VA_ARGS__)
#endif
================================================
FILE: libhackyfix/CMakeLists.txt
================================================
file (GLOB sources "*.c")
add_library (hackyfix OBJECT ${sources})
target_compile_definitions (hackyfix PUBLIC -D_POSIX_C_SOURCE=200809L)
# link against dl
target_link_libraries (hackyfix PUBLIC ${CMAKE_DL_LIBS})
set_target_properties (ls PROPERTIES POSITION_INDEPENDENT_CODE ON)
================================================
FILE: libhackyfix/README.txt
================================================
Lua uses non-thread-safe functions all over the place and does another things
that are very problematic in multi-threaded environments.
Examples of things that are very problematic in multi-threaded environments:
* Calling "fflush(NULL)" before calling popen() in its implementation of
io.popen() function. The reasons why Lua does this are unknown to us.
fflush(NULL) traverses all open FILE* objects, including read-only ones,
acquires the lock on each one, flushes it, and then releases the lock.
The problem is that "fflush(NULL)" hangs if, at the time of the call, at
least one FILE* object is waiting on an input (e.g. blocked in fgetc(),
getline() or similar) or is blocked on writing (e.g. when writing to a
pipe).
It only returns once all blocking stdio functions that are in-flight
return, because all stdio functions (except unlocked_* ones) also lock the
file.
This makes using stdio in any of the threads very risky: Lua's io.popen()
in another thread now may block for indefinite time. Note that Lua's stdlib
also uses stdio all over the place.
Examples of usage of non-thread-safe (according to POSIX-2008) functions:
* exit(): only used in implementation of os.exit().
- we replace this Lua function in luastatus.
* setlocale(): only used in implementation of os.setlocale().
- we replace this Lua function in luastatus.
* system(): only used in implementation of os.execute().
- we replace this Lua function in luastatus.
* dlerror(): used internally!
* getenv(): used internally! And also in implementation of os.getenv().
* strerror(): used internally!
* localeconv(): used internally!
So this library replaces the following functions with thread-safe counterparts
and/or different semantics to work around things that are very problematic in
multi-threaded environments:
* fflush();
* strerror();
* getenv();
* localeconv();
* dlerror() (unless compiled with glibc, where it *is* thread-safe).
================================================
FILE: libhackyfix/fatal.c
================================================
/*
* Copyright (C) 2025 luastatus developers
*
* This file is part of luastatus.
*
* luastatus is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* luastatus is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with luastatus. If not, see .
*/
#include "fatal.h"
#include
#include
#include
#include
static void full_write(int fd, const char *buf, size_t nbuf)
{
size_t nwritten = 0;
while (nwritten < nbuf) {
ssize_t w = write(fd, buf + nwritten, nbuf - nwritten);
if (w < 0) {
break;
}
nwritten += w;
}
}
void libhackyfix_fatal(const char *msg)
{
full_write(2, msg, strlen(msg));
abort();
}
================================================
FILE: libhackyfix/fatal.h
================================================
/*
* Copyright (C) 2025 luastatus developers
*
* This file is part of luastatus.
*
* luastatus is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* luastatus is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with luastatus. If not, see .
*/
#ifndef libhackyfix_fatal_h_
#define libhackyfix_fatal_h_
void libhackyfix_fatal(const char *msg);
#endif
================================================
FILE: libhackyfix/get_rtld_next_handle.c
================================================
/*
* Copyright (C) 2025 luastatus developers
*
* This file is part of luastatus.
*
* luastatus is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* luastatus is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with luastatus. If not, see .
*/
#ifndef _GNU_SOURCE
# define _GNU_SOURCE
#endif
#include "get_rtld_next_handle.h"
#include
void *libhackyfix_get_rtld_next_handle(void)
{
return RTLD_NEXT;
}
================================================
FILE: libhackyfix/get_rtld_next_handle.h
================================================
/*
* Copyright (C) 2025 luastatus developers
*
* This file is part of luastatus.
*
* luastatus is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* luastatus is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with luastatus. If not, see .
*/
#ifndef libhackyfix_get_rtld_next_handle_h_
#define libhackyfix_get_rtld_next_handle_h_
void *libhackyfix_get_rtld_next_handle(void);
#endif
================================================
FILE: libhackyfix/libhackyfix.c
================================================
/*
* Copyright (C) 2025 luastatus developers
*
* This file is part of luastatus.
*
* luastatus is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* luastatus is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with luastatus. If not, see .
*/
#include
#include
#include
#include
#include
#include "get_rtld_next_handle.h"
#include "fatal.h"
// dlerror() is not thread-safe at least on NetBSD and musl.
// Let's assume other *BSDs and/or libcs have non-thread-safe dlerror() as well.
#if defined(__GLIBC__)
# define REPLACE_DLERROR 0
#else
# define REPLACE_DLERROR 1
#endif
static int (*orig_fflush)(FILE *f);
static struct lconv *(*orig_localeconv)(void);
static struct lconv my_lconv;
static char *xstrdup_or_null(const char *s)
{
if (!s) {
return NULL;
}
size_t ns = strlen(s);
char *res = malloc(ns + 1);
if (!res) {
libhackyfix_fatal("FATAL: libhackyfix: malloc() failed\n");
}
memcpy(res, s, ns + 1);
return res;
}
static void query_and_copy_lconv(void)
{
struct lconv *cur = orig_localeconv();
my_lconv = *cur;
#define copy(field) (my_lconv.field = xstrdup_or_null(cur->field))
copy(decimal_point);
copy(thousands_sep);
copy(grouping);
copy(int_curr_symbol);
copy(currency_symbol);
copy(mon_decimal_point);
copy(mon_thousands_sep);
copy(mon_grouping);
copy(positive_sign);
copy(negative_sign);
#undef copy
}
__attribute__((constructor))
static void initialize(void)
{
void *rtld_next_handle = libhackyfix_get_rtld_next_handle();
*(void **) &orig_fflush = dlsym(rtld_next_handle, "fflush");
if (!orig_fflush) {
libhackyfix_fatal("FATAL: libhackyfix: dlsym(..., \"fflush\") failed\n");
}
*(void **) &orig_localeconv = dlsym(rtld_next_handle, "localeconv");
if (!orig_localeconv) {
libhackyfix_fatal("FATAL: libhackyfix: dlsym(..., \"localeconv\") failed\n");
}
query_and_copy_lconv();
}
__attribute__((destructor))
static void deinitialize(void)
{
free(my_lconv.decimal_point);
free(my_lconv.thousands_sep);
free(my_lconv.grouping);
free(my_lconv.int_curr_symbol);
free(my_lconv.currency_symbol);
free(my_lconv.mon_decimal_point);
free(my_lconv.mon_thousands_sep);
free(my_lconv.mon_grouping);
free(my_lconv.positive_sign);
free(my_lconv.negative_sign);
}
// ==================================================
// Replacement for /fflush/
// ==================================================
int fflush(FILE *f)
{
if (!f) {
return 0;
}
return orig_fflush(f);
}
// ==================================================
// Replacement for /strerror/
// ==================================================
#if _POSIX_C_SOURCE < 200112L || defined(_GNU_SOURCE)
# error "Unsupported feature test macros."
#endif
static __thread char errbuf[512];
char *strerror(int errnum)
{
// We introduce an /int/ variable in order to get a compilation warning if /strerror_r()/ is
// still GNU-specific and returns a pointer to char.
int r = strerror_r(errnum, errbuf, sizeof(errbuf));
return r == 0 ? errbuf : (char *) "unknown error or truncated error message";
}
// ==================================================
// Replacement for /getenv/
// ==================================================
extern char **environ;
char *getenv(const char *name)
{
if (!environ) {
return NULL;
}
if ((strchr(name, '='))) {
return NULL;
}
size_t nname = strlen(name);
for (char **s = environ; *s; ++s) {
const char *entry = *s;
if (strncmp(entry, name, nname) == 0 && entry[nname] == '=') {
return (char *) (entry + nname + 1);
}
}
return NULL;
}
// ==================================================
// Replacement for /localeconv/
// ==================================================
struct lconv *localeconv(void)
{
return &my_lconv;
}
#if REPLACE_DLERROR
// ==================================================
// Replacement for /dlerror/
// ==================================================
char *dlerror(void)
{
return (char *) "(no error message for you because on your system dlerror() isn't thread-safe)";
}
#endif
================================================
FILE: libls/CMakeLists.txt
================================================
file (GLOB sources "*.c")
add_library (ls OBJECT ${sources})
include (CheckSymbolExists)
set (CMAKE_REQUIRED_DEFINITIONS "-D_GNU_SOURCE")
check_symbol_exists (pipe2 "fcntl.h;unistd.h" LS_HAVE_GNU_PIPE2)
check_symbol_exists (SOCK_CLOEXEC "sys/socket.h" LS_HAVE_GNU_SOCK_CLOEXEC)
configure_file ("ls_probes.in.h" "ls_probes.generated.h")
target_compile_definitions (ls PUBLIC -D_POSIX_C_SOURCE=200809L)
luastatus_target_compile_with (ls LUA)
target_include_directories (ls PUBLIC "${PROJECT_SOURCE_DIR}" "${CMAKE_CURRENT_BINARY_DIR}")
find_library (MATH_LIBRARY m)
if (MATH_LIBRARY)
target_link_libraries (ls PUBLIC ${MATH_LIBRARY})
endif ()
set_target_properties (ls PROPERTIES POSITION_INDEPENDENT_CODE ON)
================================================
FILE: libls/ls_algo.h
================================================
/*
* Copyright (C) 2015-2025 luastatus developers
*
* This file is part of luastatus.
*
* luastatus is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* luastatus is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with luastatus. If not, see .
*/
#ifndef ls_algo_h_
#define ls_algo_h_
#define LS_ARRAY_SIZE(X_) (sizeof(X_) / sizeof((X_)[0]))
#define LS_SWAP(Type_, X_, Y_) \
do { \
Type_ ls_swap_tmp_ = (X_); \
(X_) = (Y_); \
(Y_) = ls_swap_tmp_; \
} while (0)
#endif
================================================
FILE: libls/ls_alloc_utils.c
================================================
/*
* Copyright (C) 2015-2025 luastatus developers
*
* This file is part of luastatus.
*
* luastatus is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* luastatus is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with luastatus. If not, see .
*/
#include "ls_alloc_utils.h"
#include
#include
#include
#include "ls_panic.h"
static inline bool mul_zu(size_t *dst, size_t a, size_t b)
{
if (b && a > SIZE_MAX / b) {
return false;
}
*dst = a * b;
return true;
}
void *ls_xmalloc(size_t nelems, size_t elemsz)
{
size_t total_bytes;
if (!mul_zu(&total_bytes, nelems, elemsz)) {
goto oom;
}
void *r = malloc(total_bytes);
if (!r && total_bytes) {
goto oom;
}
return r;
oom:
ls_oom();
}
void *ls_xcalloc(size_t nelems, size_t elemsz)
{
void *r = calloc(nelems, elemsz);
if (nelems && elemsz && !r) {
ls_oom();
}
return r;
}
void *ls_xrealloc(void *p, size_t nelems, size_t elemsz)
{
size_t total_bytes;
if (!mul_zu(&total_bytes, nelems, elemsz)) {
goto oom;
}
if (!total_bytes) {
free(p);
return NULL;
}
void *r = realloc(p, total_bytes);
if (!r) {
goto oom;
}
return r;
oom:
ls_oom();
}
void *ls_x2realloc(void *p, size_t *pnelems, size_t elemsz)
{
LS_ASSERT(elemsz != 0);
if (*pnelems) {
if (!mul_zu(pnelems, *pnelems, 2)) {
ls_oom();
}
} else {
*pnelems = 1;
}
return ls_xrealloc(p, *pnelems, elemsz);
}
void *ls_xmemdup(const void *p, size_t n)
{
void *r = malloc(n);
if (n) {
if (!r) {
ls_oom();
}
memcpy(r, p, n);
}
return r;
}
================================================
FILE: libls/ls_alloc_utils.h
================================================
/*
* Copyright (C) 2015-2025 luastatus developers
*
* This file is part of luastatus.
*
* luastatus is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* luastatus is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with luastatus. If not, see .
*/
#ifndef ls_alloc_utils_h_
#define ls_alloc_utils_h_
#include
#include
#include "ls_panic.h"
#include "ls_compdep.h"
#define LS_XNEW(Type_, NElems_) \
((Type_ *) ls_xmalloc(NElems_, sizeof(Type_)))
#define LS_XNEW0(Type_, NElems_) \
((Type_ *) ls_xcalloc(NElems_, sizeof(Type_)))
#define LS_M_X2REALLOC(Ptr_, NElemsPtr_) \
((__typeof__(Ptr_)) ls_x2realloc((Ptr_), (NElemsPtr_), sizeof((Ptr_)[0])))
#define LS_M_XMEMDUP(Ptr_, N_) \
((__typeof__(Ptr_)) ls_xmemdup((Ptr_), ((size_t) (N_)) * sizeof((Ptr_)[0])))
#define LS_M_XREALLOC(Ptr_, N_) \
((__typeof__(Ptr_)) ls_xrealloc((Ptr_), (N_), sizeof((Ptr_)[0])))
// Out-of-memory handler; should be called when an allocation fails.
//
// Should not return.
#define ls_oom() LS_PANIC("out of memory")
// The behavior is same as calling
// /malloc(nelems * elemsz)/,
// except when the multiplication overflows, or the allocation fails. In these cases, this function
// panics.
LS_ATTR_WARN_UNUSED
void *ls_xmalloc(size_t nelems, size_t elemsz);
// The behavior is same as calling
// /calloc(nelems, elemsz)/,
// except when the allocation fails. In that case, this function panics.
LS_ATTR_WARN_UNUSED
void *ls_xcalloc(size_t nelems, size_t elemsz);
// The behavior is same as calling
// /realloc(p, nelems * elemsz)/,
// except when the multiplication overflows, or the reallocation fails. In these cases, this
// function panics.
//
// Zero /nelems/ and/or /elemsz/ are supported (even though C23 declared /realloc/ to zero size
// undefined behavior): if the total size requested is zero, this function /free/s the pointer
// and returns NULL.
LS_ATTR_WARN_UNUSED
void *ls_xrealloc(void *p, size_t nelems, size_t elemsz);
// The behavior is same as calling
// /realloc(p, (*pnelems = F(*pnelems)) * elemsz)/,
// where F(n) = max(1, 2 * n),
// except when a multiplication overflows, or the reallocation fails. In these cases, this function
// panics.
//
// Zero /elemsz/ is NOT supported: it isn't clear what the semantics of this function should be in
// this case: technically you can store any number of zero-sized elements in an allocation of any
// size, but /*pnelems/ is limited to /SIZE_MAX/ (should we alter /*pnelems/ at all? If yes, what
// should we do in case of an overflow?). Also, C99 says there can't be any zero-sized types: empty
// structs or unions, and arrays of size 0, are prohibited.
LS_ATTR_WARN_UNUSED
void *ls_x2realloc(void *p, size_t *pnelems, size_t elemsz);
// Duplicates (as if with /malloc/) /n/ bytes of memory at address /p/. Panics on failure.
LS_ATTR_WARN_UNUSED
void *ls_xmemdup(const void *p, size_t n);
// The behavior is same as calling
// /strdup(s)/,
// except when the allocation fails. In that case, this function panics.
LS_INHEADER LS_ATTR_WARN_UNUSED
char *ls_xstrdup(const char *s)
{
LS_ASSERT(s != NULL);
return ls_xmemdup(s, strlen(s) + 1);
}
#endif
================================================
FILE: libls/ls_compdep.h
================================================
/*
* Copyright (C) 2015-2025 luastatus developers
*
* This file is part of luastatus.
*
* luastatus is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* luastatus is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with luastatus. If not, see .
*/
#ifndef ls_compdep_h_
#define ls_compdep_h_
// ------------------------------------------------------------
// GCC version (set by clang to some very old version)
// ------------------------------------------------------------
#define LS_GCC_VERSION (__GNUC__ * 10000 + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__)
// ------------------------------------------------------------
// Clang stuff
// ------------------------------------------------------------
#if defined(__has_attribute)
# define LS_CLANG_HAS_ATTRIBUTE(X_) __has_attribute(X_)
#else
# define LS_CLANG_HAS_ATTRIBUTE(X_) 0
#endif
#if defined(__has_builtin)
# define LS_CLANG_HAS_BUILTIN(X_) __has_builtin(X_)
#else
# define LS_CLANG_HAS_BUILTIN(X_) 0
#endif
// ------------------------------------------------------------
// Attributes
// ------------------------------------------------------------
#if __GNUC__ >= 2
# define LS_ATTR_UNUSED __attribute__((unused))
# define LS_ATTR_PRINTF(N_, M_) __attribute__((format(printf, N_, M_)))
# define LS_ATTR_NORETURN __attribute__((noreturn))
#else
# define LS_ATTR_UNUSED /*nothing*/
# define LS_ATTR_PRINTF(N_, M_) /*nothing*/
# define LS_ATTR_NORETURN /*nothing*/
#endif
#if LS_GCC_VERSION >= 30406 || LS_CLANG_HAS_ATTRIBUTE(warn_unused_result)
# define LS_ATTR_WARN_UNUSED __attribute__((warn_unused_result))
#else
# define LS_ATTR_WARN_UNUSED /*nothing*/
#endif
// ------------------------------------------------------------
// LS_INHEADER
// ------------------------------------------------------------
#define LS_INHEADER static inline LS_ATTR_UNUSED
#endif
================================================
FILE: libls/ls_cstring_utils.c
================================================
/*
* Copyright (C) 2015-2025 luastatus developers
*
* This file is part of luastatus.
*
* luastatus is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* luastatus is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with luastatus. If not, see .
*/
#if _POSIX_C_SOURCE < 200112L || defined(_GNU_SOURCE)
# error "Unsupported feature test macros; either tune them or change the code."
#endif
#include "ls_cstring_utils.h"
#include
#include
const char *ls_strerror_r(int errnum, char *buf, size_t nbuf)
{
// luastatus-specific "fake" errno values
switch (errnum) {
case -EINVAL:
return "Not a FIFO";
}
// We introduce an /int/ variable in order to get a compilation warning if /strerror_r()/ is
// still GNU-specific and returns a pointer to char.
int r = strerror_r(errnum, buf, nbuf);
return r == 0 ? buf : "unknown error or truncated error message";
}
================================================
FILE: libls/ls_cstring_utils.h
================================================
/*
* Copyright (C) 2015-2025 luastatus developers
*
* This file is part of luastatus.
*
* luastatus is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* luastatus is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with luastatus. If not, see .
*/
#ifndef ls_cstring_utils_h_
#define ls_cstring_utils_h_
#include
#include
#include "ls_compdep.h"
#include "ls_panic.h"
// If zero-terminated string /str/ starts with zero-terminated string /prefix/, returns
// /str + strlen(prefix)/; otherwise, returns /NULL/.
LS_INHEADER const char *ls_strfollow(const char *str, const char *prefix)
{
LS_ASSERT(str != NULL);
LS_ASSERT(prefix != NULL);
size_t nprefix = strlen(prefix);
return strncmp(str, prefix, nprefix) == 0 ? str + nprefix : NULL;
}
// Behaves like the GNU-specific /strerror_r/: either fills /buf/ and returns it, or returns a
// pointer to a static string.
const char *ls_strerror_r(int errnum, char *buf, size_t nbuf);
#endif
================================================
FILE: libls/ls_evloop_lfuncs.h
================================================
/*
* Copyright (C) 2015-2025 luastatus developers
*
* This file is part of luastatus.
*
* luastatus is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* luastatus is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with luastatus. If not, see .
*/
#ifndef ls_evloop_lfuncs_h_
#define ls_evloop_lfuncs_h_
#include
#include
#include
#include
#include
#include
#include
#include "ls_compdep.h"
#include "ls_panic.h"
#include "ls_osdep.h"
#include "ls_io_utils.h"
#include "ls_time_utils.h"
// Some plugins provide a "push_timeout"/"push_period" function that allows a widget to specify the
// next timeout for an otherwise constant timeout-based plugin's event loop.
//
// The pushed timeout value has to be guarded with a lock as the "push_timeout"/"push_period"
// function may be called from the /event/ function of a widget (that is, asynchronously from the
// plugin's viewpoint).
//
// /LS_PushedTimeout/ is a structure containing the pushed timeout value (or an absence of such, as
// indicated by the value being negative), and a lock.
//
//
// This structure must reside at a constant address throughout its whole life; this is required for
// the Lua closure created with /ls_pushed_timeout_push_luafunc()/.
// !!!>
typedef struct {
double value;
pthread_mutex_t lock;
} LS_PushedTimeout;
// Initializes /p/ with an absence of pushed timeout value and a newly-created lock.
LS_INHEADER void ls_pushed_timeout_init(LS_PushedTimeout *p)
{
p->value = -1;
LS_PTH_CHECK(pthread_mutex_init(&p->lock, NULL));
}
// Does the following actions atomically:
// * checks if /p/ has a pushed timeout value;
// * if it does, clears it and returns the timeout value that /p/ has previously had;
// * if it does not, returns /alt/.
LS_INHEADER LS_TimeDelta ls_pushed_timeout_fetch(LS_PushedTimeout *p, LS_TimeDelta alt)
{
LS_TimeDelta r;
LS_PTH_CHECK(pthread_mutex_lock(&p->lock));
if (p->value < 0) {
r = alt;
} else {
r = ls_double_to_TD(p->value, LS_TD_FOREVER);
p->value = -1;
}
LS_PTH_CHECK(pthread_mutex_unlock(&p->lock));
return r;
}
LS_INHEADER int ls_pushed_timeout_lfunc(lua_State *L)
{
double arg = luaL_checknumber(L, 1);
if (!isgreaterequal(arg, 0.0)) {
return luaL_argerror(L, 1, "invalid timeout");
}
LS_PushedTimeout *p = lua_touserdata(L, lua_upvalueindex(1));
LS_PTH_CHECK(pthread_mutex_lock(&p->lock));
p->value = arg;
LS_PTH_CHECK(pthread_mutex_unlock(&p->lock));
return 0;
}
// Creates a "push_timeout" function (a "C closure" with /p/'s address, in Lua terminology) on /L/'s
// stack.
//
// The resulting Lua function takes one numeric argument (the timeout in seconds) and does not
// return anything. It may throw an error if an invalid argument is provided. Once called, it will
// (atomically) alter /p/'s timeout value.
//
// The caller must ensure that the /L/'s stack has at least 2 free slots.
LS_INHEADER void ls_pushed_timeout_push_luafunc(LS_PushedTimeout *p, lua_State *L)
{
lua_pushlightuserdata(L, p);
lua_pushcclosure(L, ls_pushed_timeout_lfunc, 1);
}
// Destroys /p/.
LS_INHEADER void ls_pushed_timeout_destroy(LS_PushedTimeout *p)
{
LS_PTH_CHECK(pthread_mutex_destroy(&p->lock));
}
// Some plugins provide the so-called self-pipe facility; that is, the ability to "wake up" the
// widget's event loop (and force a call to the widget's /cb()/ function) from within the widget's
// /event()/ function via a special "wake_up" Lua function.
//
// Such a pipe consists of an array, /int fds[2]/, of two file descriptors; the /fds[0]/ file
// descriptor is to be read from, and /fds[1]/ is to be written to.
//
//
// This array must reside at a constant address throughout its whole life; this is required for
// the Lua closure created with /ls_self_pipe_push_luafunc()/.
// !!!>
// Creates a new self-pipe.
//
// On success, /0/ is returned.
//
// On failure, /-1/ is returned, /fds[0]/ and /fds[1]/ are set to -1, and /errno/ is set.
LS_INHEADER int ls_self_pipe_open(int fds[2])
{
if (ls_cloexec_pipe(fds) < 0) {
fds[0] = -1;
fds[1] = -1;
return -1;
}
ls_make_nonblock(fds[0]);
ls_make_nonblock(fds[1]);
return 0;
}
LS_INHEADER int ls_self_pipe_lfunc(lua_State *L)
{
int *fds = lua_touserdata(L, lua_upvalueindex(1));
int fd = fds[1];
if (fd < 0) {
return luaL_error(L, "self-pipe has not been opened");
}
ssize_t unused = write(fd, "", 1); // write '\0'
(void) unused;
return 0;
}
// Creates a "wake_up" function (a "C closure" with /s/'s address, in Lua terminology) on /L/'s
// stack.
//
// The resulting Lua function takes no arguments and does not return anything. Once called, it will
// write a single byte to /fds[1]/ (in a thread-safe manner), or throw an error if the self-pipe
// has not been opened.
//
// The caller must ensure that the /L/'s stack has at least 2 free slots.
LS_INHEADER void ls_self_pipe_push_luafunc(int fds[2], lua_State *L)
{
lua_pushlightuserdata(L, fds);
lua_pushcclosure(L, ls_self_pipe_lfunc, 1);
}
#endif
================================================
FILE: libls/ls_fifo_device.h
================================================
/*
* Copyright (C) 2015-2025 luastatus developers
*
* This file is part of luastatus.
*
* luastatus is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* luastatus is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with luastatus. If not, see .
*/
#ifndef ls_fifo_device_h_
#define ls_fifo_device_h_
#include
#include "ls_compdep.h"
#include "ls_time_utils.h"
#include "ls_io_utils.h"
// A "FIFO device" is a device that emits an event whenever somebody does
// touch(1) (that is, opens for writing and then closes) on a FIFO file with
// a specified path.
//
// If specified path is NULL, no events are ever reported.
//
// The suggested usage is as follows:
//
// LS_FifoDevice dev = ls_fifo_device_new();
// for (;;) {
// // ...
// if (ls_fifo_device_open(&dev, fifo_path) < 0) {
// // ... handle error ...
// }
// if (ls_fifo_device_wait(&dev, timeout)) {
/// // ... somebody touched the FIFO, do something ...
// }
// // ...
// }
// ls_fifo_device_close(&dev);
typedef struct {
int fd;
} LS_FifoDevice;
LS_INHEADER LS_FifoDevice ls_fifo_device_new(void)
{
return (LS_FifoDevice) {-1};
}
LS_INHEADER int ls_fifo_device_open(LS_FifoDevice *dev, const char *path)
{
if (dev->fd >= 0) {
return 0;
}
if (!path) {
return 0;
}
dev->fd = ls_open_fifo(path);
if (dev->fd < 0) {
return -1;
}
return 0;
}
LS_INHEADER int ls_fifo_device_wait(LS_FifoDevice *dev, LS_TimeDelta tmo)
{
int r = ls_wait_input_on_fd(dev->fd, tmo);
if (r > 0) {
close(dev->fd);
dev->fd = -1;
}
return r;
}
LS_INHEADER void ls_fifo_device_close(LS_FifoDevice *dev)
{
ls_close(dev->fd);
}
LS_INHEADER void ls_fifo_device_reset(LS_FifoDevice *dev)
{
ls_fifo_device_close(dev);
*dev = ls_fifo_device_new();
}
#endif
================================================
FILE: libls/ls_freemem.h
================================================
/*
* Copyright (C) 2025 luastatus developers
*
* This file is part of luastatus.
*
* luastatus is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* luastatus is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with luastatus. If not, see .
*/
#ifndef ls_freemem_h_
#define ls_freemem_h_
#include
#include "ls_alloc_utils.h"
#include "ls_compdep.h"
#include "ls_panic.h"
// This function is called whenever a dynamic array is cleared.
// We want to "preserve" at most 1 Kb of the previous capacity.
LS_INHEADER LS_ATTR_WARN_UNUSED
void *ls_freemem(void *data, size_t *psize, size_t *pcapacity, size_t elemsz)
{
LS_ASSERT(elemsz != 0);
size_t new_capacity = 1024 / elemsz;
if (*pcapacity > new_capacity) {
data = ls_xrealloc(data, new_capacity, elemsz);
*pcapacity = new_capacity;
}
*psize = 0;
return data;
}
#define LS_M_FREEMEM(Data_, SizePtr_, CapacityPtr_) \
((__typeof__(Data_)) ls_freemem( \
(Data_), \
(SizePtr_), \
(CapacityPtr_), \
sizeof(*(Data_))))
#endif
================================================
FILE: libls/ls_getenv_r.c
================================================
/*
* Copyright (C) 2015-2025 luastatus developers
*
* This file is part of luastatus.
*
* luastatus is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* luastatus is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with luastatus. If not, see .
*/
#include "ls_getenv_r.h"
#include
#include
#include "ls_panic.h"
extern char **environ;
const char *ls_getenv_r(const char *name)
{
LS_ASSERT(name != NULL);
if (!environ) {
return NULL;
}
if ((strchr(name, '='))) {
return NULL;
}
size_t nname = strlen(name);
for (char **s = environ; *s; ++s) {
const char *entry = *s;
if (strncmp(entry, name, nname) == 0 && entry[nname] == '=') {
return entry + nname + 1;
}
}
return NULL;
}
================================================
FILE: libls/ls_getenv_r.h
================================================
/*
* Copyright (C) 2015-2025 luastatus developers
*
* This file is part of luastatus.
*
* luastatus is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* luastatus is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with luastatus. If not, see .
*/
#ifndef ls_getenv_r_h_
#define ls_getenv_r_h_
// Thread-safe /getenv/.
const char *ls_getenv_r(const char *name);
#endif
================================================
FILE: libls/ls_io_utils.c
================================================
/*
* Copyright (C) 2015-2025 luastatus developers
*
* This file is part of luastatus.
*
* luastatus is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* luastatus is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with luastatus. If not, see .
*/
#include "ls_io_utils.h"
#include
#include
#include
#include
#include
#include
#include "ls_time_utils.h"
#include "ls_panic.h"
static int poll_forever(struct pollfd *fds, size_t nfds)
{
int r;
while ((r = poll(fds, nfds, -1)) < 0 && errno == EINTR) {
// do nothing
}
return r;
}
int ls_poll(struct pollfd *fds, size_t nfds, LS_TimeDelta tmo)
{
if (nfds != (nfds_t) nfds) {
errno = EOVERFLOW;
return -1;
}
int tmo_ms = ls_TD_to_poll_ms_tmo(tmo);
if (tmo_ms < 0) {
return poll_forever(fds, nfds);
}
sigset_t allsigs;
LS_ASSERT(sigfillset(&allsigs) == 0);
sigset_t origmask;
LS_PTH_CHECK(pthread_sigmask(SIG_SETMASK, &allsigs, &origmask));
int r = poll(fds, nfds, tmo_ms);
int saved_errno = errno;
LS_PTH_CHECK(pthread_sigmask(SIG_SETMASK, &origmask, NULL));
errno = saved_errno;
return r;
}
int ls_open_fifo(const char *fifo)
{
LS_ASSERT(fifo != NULL);
int saved_errno;
int fd = open(fifo, O_RDONLY | O_CLOEXEC | O_NONBLOCK);
if (fd < 0) {
goto error;
}
struct stat sb;
if (fstat(fd, &sb) < 0) {
goto error;
}
if (!S_ISFIFO(sb.st_mode)) {
errno = -EINVAL;
goto error;
}
return fd;
error:
saved_errno = errno;
ls_close(fd);
errno = saved_errno;
return -1;
}
================================================
FILE: libls/ls_io_utils.h
================================================
/*
* Copyright (C) 2015-2025 luastatus developers
*
* This file is part of luastatus.
*
* luastatus is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* luastatus is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with luastatus. If not, see .
*/
#ifndef ls_io_utils_h_
#define ls_io_utils_h_
#include
#include
#include
#include
#include
#include "ls_compdep.h"
#include "ls_time_utils.h"
#if EAGAIN == EWOULDBLOCK
# define LS_IS_EAGAIN(E_) ((E_) == EAGAIN)
#else
# define LS_IS_EAGAIN(E_) ((E_) == EAGAIN || (E_) == EWOULDBLOCK)
#endif
// Makes a file descriptor close-on-exec.
// On success, /fd/ is returned.
// On failure, /-1/ is returned and /errno/ is set.
LS_INHEADER int ls_make_cloexec(int fd)
{
int flags = fcntl(fd, F_GETFD);
if (flags < 0) {
return -1;
}
flags |= FD_CLOEXEC;
if (fcntl(fd, F_SETFD, flags) < 0) {
return -1;
}
return fd;
}
// Makes a file descriptor non-blocking.
// On success, /fd/ is returned.
// On failure, /-1/ is returned and /errno/ is set.
LS_INHEADER int ls_make_nonblock(int fd)
{
int flags = fcntl(fd, F_GETFL);
if (flags < 0) {
return -1;
}
flags |= O_NONBLOCK;
if (fcntl(fd, F_SETFL, flags) < 0) {
return -1;
}
return fd;
}
// Close if non-negative: valgrind doesn't like /close()/ on negative FDs.
LS_INHEADER int ls_close(int fd)
{
if (fd < 0) {
return 0;
}
return close(fd);
}
// Poll restarting on /EINTR/ errors.
int ls_poll(struct pollfd *fds, size_t nfds, LS_TimeDelta tmo);
// Poll a single fd for /POLLIN/ events.
LS_INHEADER int ls_wait_input_on_fd(int fd, LS_TimeDelta tmo)
{
struct pollfd pfd = {.fd = fd, .events = POLLIN};
return ls_poll(&pfd, 1, tmo);
}
// Open a FIFO for reading, and check that this is really a FIFO.
int ls_open_fifo(const char *fifo);
#endif
================================================
FILE: libls/ls_lua_compat.h
================================================
/*
* Copyright (C) 2015-2025 luastatus developers
*
* This file is part of luastatus.
*
* luastatus is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* luastatus is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with luastatus. If not, see .
*/
#ifndef ls_lua_compat_h_
#define ls_lua_compat_h_
#include
#include
#include
#include
#include
#include
#include "ls_compdep.h"
#if LUA_VERSION_NUM >= 504
# define ls_lua_pushfail(L_) luaL_pushfail(L_)
#else
# define ls_lua_pushfail(L_) lua_pushnil(L_)
#endif
LS_INHEADER bool ls_lua_is_lua51(lua_State *L)
{
#if LUA_VERSION_NUM >= 502
(void) L;
return false;
#else
// LuaJIT, when compiled with -DLUAJIT_ENABLE_LUA52COMPAT, still defines
// LUA_VERSION_NUM to 501, but syntax parser, library functions and some
// aspect of language work as if it was Lua 5.2.
// L: ?
lua_getglobal(L, "rawlen"); // L: ? rawlen
bool ret = lua_isnil(L, -1);
lua_pop(L, 1); // L: ?
return ret;
#endif
}
LS_INHEADER size_t ls_lua_array_len(lua_State *L, int pos)
{
#if LUA_VERSION_NUM <= 501
return lua_objlen(L, pos);
#else
return lua_rawlen(L, pos);
#endif
}
#ifdef LUA_MAXINTEGER
# define LS_LUA_MAXI \
(LUA_MAXINTEGER > (SIZE_MAX - 1) ? (SIZE_MAX - 1) : LUA_MAXINTEGER)
#else
# define LS_LUA_MAXI INT_MAX
#endif
LS_INHEADER int ls_lua_num_prealloc(size_t n)
{
return n < (size_t) INT_MAX ? (int) n : INT_MAX;
}
#endif
================================================
FILE: libls/ls_osdep.c
================================================
/*
* Copyright (C) 2015-2025 luastatus developers
*
* This file is part of luastatus.
*
* luastatus is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* luastatus is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with luastatus. If not, see .
*/
#define _GNU_SOURCE
#include "ls_osdep.h"
#include
#include
#include
#include "ls_probes.generated.h"
int ls_cloexec_pipe(int pipefd[2])
{
#if LS_HAVE_GNU_PIPE2
return pipe2(pipefd, O_CLOEXEC);
#else
if (pipe(pipefd) < 0) {
return -1;
}
ls_make_cloexec(pipefd[0]);
ls_make_cloexec(pipefd[1]);
return 0;
#endif
}
int ls_cloexec_socket(int domain, int type, int protocol)
{
#if LS_HAVE_GNU_SOCK_CLOEXEC
return socket(domain, type | SOCK_CLOEXEC, protocol);
#else
int fd = socket(domain, type, protocol);
if (fd < 0) {
return -1;
}
ls_make_cloexec(fd);
return fd;
#endif
}
================================================
FILE: libls/ls_osdep.h
================================================
/*
* Copyright (C) 2015-2025 luastatus developers
*
* This file is part of luastatus.
*
* luastatus is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* luastatus is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with luastatus. If not, see .
*/
#ifndef ls_osdep_h_
#define ls_osdep_h_
// We can't define /_GNU_SOURCE/ here, nor can we include "probes.generated.h", as
// it may be located outside of the source tree: /libls/ is built with /libls/'s
// binary directory included, unlike everything that includes its headers.
// So no in-header functions.
// The behavior is same as calling /pipe(pipefd)/, except that both file descriptors are made
// close-on-exec. If the latter fails, the pipe is destroyed, /-1/ is returned and /errno/ is set.
int ls_cloexec_pipe(int pipefd[2]);
// The behavior is same as calling /socket(domain, type, protocol)/, except that the file
// descriptor is make close-on-exec. If the latter fails, the pipe is destroyed, /-1/ is reurned and
// /errno/ is set.
int ls_cloexec_socket(int domain, int type, int protocol);
#endif
================================================
FILE: libls/ls_panic.c
================================================
/*
* Copyright (C) 2015-2025 luastatus developers
*
* This file is part of luastatus.
*
* luastatus is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* luastatus is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with luastatus. If not, see .
*/
#include "ls_panic.h"
#include
#include
#include "ls_cstring_utils.h"
void ls__do_panic_impl__(
const char *macro_name,
const char *expr,
const char *msg,
const char *func, const char *file, int line)
{
fprintf(
stderr, "%s(%s) failed in %s at %s:%d: %s\n",
macro_name, expr,
func, file, line,
msg);
abort();
}
void ls__do_panic_with_errnum_impl__(
const char *macro_name,
const char *expr,
const char *msg,
int errnum,
const char *func, const char *file, int line)
{
char buf[512];
fprintf(
stderr, "%s(%s) failed in %s at %s:%d: %s: %s\n",
macro_name, expr,
func, file, line,
msg, ls_strerror_r(errnum, buf, sizeof(buf))
);
abort();
}
================================================
FILE: libls/ls_panic.h
================================================
/*
* Copyright (C) 2015-2025 luastatus developers
*
* This file is part of luastatus.
*
* luastatus is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* luastatus is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with luastatus. If not, see .
*/
#ifndef ls_panic_h_
#define ls_panic_h_
#include "ls_compdep.h"
LS_ATTR_NORETURN void ls__do_panic_impl__(
const char *macro_name,
const char *expr,
const char *msg,
const char *func, const char *file, int line);
LS_ATTR_NORETURN void ls__do_panic_with_errnum_impl__(
const char *macro_name,
const char *expr,
const char *msg,
int errnum,
const char *func, const char *file, int line);
#define LS__DO_PANIC__(MacroName_, Expr_, Msg_) \
ls__do_panic_impl__( \
MacroName_, \
Expr_, \
Msg_, \
__func__, __FILE__, __LINE__)
#define LS__DO_PANIC_WITH_ERRNUM__(MacroName_, Expr_, Msg_, Errnum_) \
ls__do_panic_with_errnum_impl__( \
MacroName_, \
Expr_, \
Msg_, \
Errnum_, \
__func__, __FILE__, __LINE__)
// Logs /Msg_/ and aborts.
#define LS_PANIC(Msg_) \
LS__DO_PANIC__("LS_PANIC", "", Msg_)
// Logs /Msg_/ with errno value /Errnum_/ and aborts.
#define LS_PANIC_WITH_ERRNUM(Msg_, Errnum_) \
LS__DO_PANIC_WITH_ERRNUM__("LS_PANIC_WITH_ERRNUM", "", Msg_, Errnum_)
// Asserts that a /pthread_*/ call was successful.
#define LS_PTH_CHECK(Expr_) \
do { \
int ls_pth_rc_ = (Expr_); \
if (ls_pth_rc_ != 0) { \
LS__DO_PANIC_WITH_ERRNUM__("LS_PTH_CHECK", #Expr_, "check failed", ls_pth_rc_); \
} \
} while (0)
// Asserts that /Expr_/ is true.
#define LS_ASSERT(Expr_) \
do { \
if (!(Expr_)) { \
LS__DO_PANIC__("LS_ASSERT", #Expr_, "assertion failed"); \
} \
} while (0)
// Just use it to tell the compiler some code is unreachable.
#define LS_MUST_BE_UNREACHABLE() LS_PANIC("LS_MUST_BE_UNREACHABLE()")
#endif
================================================
FILE: libls/ls_parse_int.c
================================================
/*
* Copyright (C) 2015-2025 luastatus developers
*
* This file is part of luastatus.
*
* luastatus is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* luastatus is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with luastatus. If not, see .
*/
#include "ls_parse_int.h"
#include
#include
int ls_strtou_b(const char *s, size_t ns, const char **endptr)
{
int ret = 0;
size_t i = 0;
for (; i != ns; ++i) {
int digit = ((int) s[i]) - '0';
if (digit < 0 || digit > 9) {
break;
}
if (ret > INT_MAX / 10) {
ret = -ERANGE;
break;
}
ret *= 10;
if (ret > INT_MAX - digit) {
ret = -ERANGE;
break;
}
ret += digit;
}
if (endptr) {
*endptr = s + i;
}
return ret;
}
int ls_full_strtou_b(const char *s, size_t ns)
{
if (!ns) {
return -EINVAL;
}
const char *endptr;
int r = ls_strtou_b(s, ns, &endptr);
if (r < 0) {
return r;
}
if (endptr != s + ns) {
return -EINVAL;
}
return r;
}
================================================
FILE: libls/ls_parse_int.h
================================================
/*
* Copyright (C) 2015-2025 luastatus developers
*
* This file is part of luastatus.
*
* luastatus is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* luastatus is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with luastatus. If not, see .
*/
#ifndef ls_parse_int_h_
#define ls_parse_int_h_
#include
#include
#include "ls_compdep.h"
#include "ls_panic.h"
// Parses (locale-independently) a decimal unsigned integer, inspecting no more than first /ns/
// characters of /s/. Once this limit is reached, or a non-digit character is found, this function
// stops, writes the current position to /*endptr/, unless it is /NULL/; and returns what has been
// parsed insofar (if nothing, /0/ is returned).
//
// If an overflow happens, /-ERANGE/ is returned.
int ls_strtou_b(const char *s, size_t ns, const char **endptr);
// Parses (locale-independently) a decimal unsigned integer using first /ns/ characters of /s/.
//
// If a non-digit character is found among them, or if /ns/ is /0/, /-EINVAL/ is returned.
// If an overflow happens, /-ERANGE/ is returned.
int ls_full_strtou_b(const char *s, size_t ns);
// Parses (locale-independently) a decimal unsigned integer from a zero-terminated string /s/.
//
// If a non-digit character is found in /s/, or if /s/ is empty, /-EINVAL/ is returned.
// If an overflow happens, /-ERANGE/ is returned.
LS_INHEADER int ls_full_strtou(const char *s)
{
LS_ASSERT(s != NULL);
return ls_full_strtou_b(s, strlen(s));
}
#endif
================================================
FILE: libls/ls_probes.in.h
================================================
#ifndef ls_probes_h_
#define ls_probes_h_
#cmakedefine01 LS_HAVE_GNU_PIPE2
#cmakedefine01 LS_HAVE_GNU_SOCK_CLOEXEC
#endif
================================================
FILE: libls/ls_strarr.h
================================================
/*
* Copyright (C) 2015-2025 luastatus developers
*
* This file is part of luastatus.
*
* luastatus is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* luastatus is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with luastatus. If not, see .
*/
#ifndef ls_strarr_h_
#define ls_strarr_h_
#include
#include
#include "ls_string.h"
#include "ls_alloc_utils.h"
#include "ls_compdep.h"
#include "ls_freemem.h"
#include "ls_panic.h"
// An array of constant strings on a single buffer. Panics on allocation failure.
typedef struct {
size_t *data;
size_t size;
size_t capacity;
} LS_StringArray_Offsets;
typedef struct {
LS_String buf;
LS_StringArray_Offsets offsets;
} LS_StringArray;
LS_INHEADER LS_StringArray ls_strarr_new(void)
{
return (LS_StringArray) {
.buf = ls_string_new(),
.offsets = {NULL, 0, 0},
};
}
LS_INHEADER LS_StringArray ls_strarr_new_reserve(size_t totlen, size_t nelems)
{
size_t *offsets_buf = LS_XNEW(size_t, nelems);
return (LS_StringArray) {
.buf = ls_string_new_reserve(totlen),
.offsets = {offsets_buf, 0, nelems},
};
}
LS_INHEADER void ls_strarr_append(LS_StringArray *sa, const char *buf, size_t nbuf)
{
LS_StringArray_Offsets *o = &sa->offsets;
if (o->size == o->capacity) {
o->data = LS_M_X2REALLOC(o->data, &o->capacity);
}
o->data[o->size++] = sa->buf.size;
ls_string_append_b(&sa->buf, buf, nbuf);
}
LS_INHEADER void ls_strarr_append_s(LS_StringArray *sa, const char *s)
{
LS_ASSERT(s != NULL);
ls_strarr_append(sa, s, strlen(s) + 1);
}
LS_INHEADER size_t ls_strarr_size(LS_StringArray sa)
{
return sa.offsets.size;
}
LS_INHEADER const char *ls_strarr_at(LS_StringArray sa, size_t i, size_t *n)
{
LS_StringArray_Offsets o = sa.offsets;
LS_ASSERT(i < o.size);
size_t begin = o.data[i];
size_t end = (i + 1 == o.size) ? sa.buf.size : o.data[i + 1];
if (n) {
*n = end - begin;
}
return sa.buf.data + begin;
}
LS_INHEADER void ls_strarr_clear(LS_StringArray *sa)
{
ls_string_clear(&sa->buf);
LS_StringArray_Offsets *o = &sa->offsets;
o->data = LS_M_FREEMEM(o->data, &o->size, &o->capacity);
}
LS_INHEADER void ls_strarr_destroy(LS_StringArray sa)
{
ls_string_free(sa.buf);
free(sa.offsets.data);
}
#endif
================================================
FILE: libls/ls_string.c
================================================
/*
* Copyright (C) 2015-2025 luastatus developers
*
* This file is part of luastatus.
*
* luastatus is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* luastatus is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with luastatus. If not, see .
*/
#include "ls_string.h"
#include
#include
#include
#include "ls_panic.h"
void ls_string_append_vf(LS_String *x, const char *fmt, va_list vl)
{
va_list vl2;
va_copy(vl2, vl);
size_t navail = x->capacity - x->size;
int r = vsnprintf(x->data + x->size, navail, fmt, vl);
if (r < 0) {
goto fail;
}
if (((size_t) r) >= navail) {
ls_string_ensure_avail(x, ((size_t) r) + 1);
if (vsnprintf(x->data + x->size, ((size_t) r) + 1, fmt, vl2) < 0) {
goto fail;
}
}
x->size += r;
va_end(vl2);
return;
fail:
LS_PANIC_WITH_ERRNUM("ls_string_append_vf: vsnprintf() failed", errno);
}
================================================
FILE: libls/ls_string.h
================================================
/*
* Copyright (C) 2015-2025 luastatus developers
*
* This file is part of luastatus.
*
* luastatus is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* luastatus is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with luastatus. If not, see .
*/
#ifndef ls_string_h_
#define ls_string_h_
#include
#include
#include
#include
#include "ls_compdep.h"
#include "ls_alloc_utils.h"
#include "ls_algo.h"
#include "ls_freemem.h"
#include "ls_panic.h"
typedef struct {
char *data;
size_t size;
size_t capacity;
} LS_String;
LS_INHEADER LS_String ls_string_new(void)
{
return (LS_String) {NULL, 0, 0};
}
LS_INHEADER LS_String ls_string_new_reserve(size_t n)
{
char *buf = LS_XNEW(char, n);
return (LS_String) {buf, 0, n};
}
LS_INHEADER void ls_string_reserve(LS_String *x, size_t n)
{
if (x->capacity < n) {
x->capacity = n;
x->data = LS_M_XREALLOC(x->data, n);
}
}
LS_INHEADER void ls_string_ensure_avail(LS_String *x, size_t n)
{
while (x->capacity - x->size < n) {
x->data = LS_M_X2REALLOC(x->data, &x->capacity);
}
}
LS_INHEADER void ls_string_clear(LS_String *x)
{
x->data = LS_M_FREEMEM(x->data, &x->size, &x->capacity);
}
LS_INHEADER void ls_string_assign_b(LS_String *x, const char *buf, size_t nbuf)
{
ls_string_reserve(x, nbuf);
// see DOCS/c_notes/empty-ranges-and-c-stdlib.md
if (nbuf) {
memcpy(x->data, buf, nbuf);
}
x->size = nbuf;
}
LS_INHEADER void ls_string_assign_s(LS_String *x, const char *s)
{
LS_ASSERT(s != NULL);
ls_string_assign_b(x, s, strlen(s));
}
LS_INHEADER void ls_string_append_b(LS_String *x, const char *buf, size_t nbuf)
{
ls_string_ensure_avail(x, nbuf);
// see DOCS/c_notes/empty-ranges-and-c-stdlib.md
if (nbuf) {
memcpy(x->data + x->size, buf, nbuf);
}
x->size += nbuf;
}
LS_INHEADER void ls_string_append_s(LS_String *x, const char *s)
{
LS_ASSERT(s != NULL);
ls_string_append_b(x, s, strlen(s));
}
LS_INHEADER void ls_string_append_c(LS_String *x, char c)
{
if (x->size == x->capacity) {
x->data = LS_M_X2REALLOC(x->data, &x->capacity);
}
x->data[x->size++] = c;
}
// Appends a formatted string to /x/.
LS_ATTR_PRINTF(2, 0)
void ls_string_append_vf(LS_String *x, const char *fmt, va_list vl);
// Appends a formatted string to /x/.
LS_INHEADER LS_ATTR_PRINTF(2, 3)
void ls_string_append_f(LS_String *x, const char *fmt, ...)
{
va_list vl;
va_start(vl, fmt);
ls_string_append_vf(x, fmt, vl);
va_end(vl);
}
LS_ATTR_PRINTF(2, 0)
LS_INHEADER void ls_string_assign_vf(LS_String *x, const char *fmt, va_list vl)
{
ls_string_clear(x);
ls_string_append_vf(x, fmt, vl);
}
LS_INHEADER LS_ATTR_PRINTF(2, 3)
void ls_string_assign_f(LS_String *s, const char *fmt, ...)
{
va_list vl;
va_start(vl, fmt);
ls_string_assign_vf(s, fmt, vl);
va_end(vl);
}
LS_INHEADER LS_String ls_string_new_from_s(const char *s)
{
LS_String x = ls_string_new();
ls_string_assign_s(&x, s);
return x;
}
LS_INHEADER LS_String ls_string_new_from_b(const char *buf, size_t nbuf)
{
LS_String x = ls_string_new();
ls_string_assign_b(&x, buf, nbuf);
return x;
}
LS_INHEADER LS_ATTR_PRINTF(1, 0)
LS_String ls_string_new_from_vf(const char *fmt, va_list vl)
{
LS_String x = ls_string_new();
ls_string_append_vf(&x, fmt, vl);
return x;
}
LS_INHEADER LS_ATTR_PRINTF(1, 2)
LS_String ls_string_new_from_f(const char *fmt, ...)
{
va_list vl;
va_start(vl, fmt);
LS_String x = ls_string_new_from_vf(fmt, vl);
va_end(vl);
return x;
}
LS_INHEADER bool ls_string_eq_b(LS_String x, const char *buf, size_t nbuf)
{
// We have to check that the size is not zero before calling /memcmp/:
// see DOCS/c_notes/empty-ranges-and-c-stdlib.md
return x.size == nbuf && (nbuf == 0 || memcmp(x.data, buf, nbuf) == 0);
}
LS_INHEADER bool ls_string_eq(LS_String x, LS_String y)
{
return ls_string_eq_b(x, y.data, y.size);
}
// Swaps two string efficiently (in O(1) time).
LS_INHEADER void ls_string_swap(LS_String *x, LS_String *y)
{
LS_SWAP(LS_String, *x, *y);
}
LS_INHEADER void ls_string_free(LS_String x)
{
free(x.data);
}
#endif
================================================
FILE: libls/ls_time_utils.h
================================================
/*
* Copyright (C) 2015-2025 luastatus developers
*
* This file is part of luastatus.
*
* luastatus is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* luastatus is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with luastatus. If not, see .
*/
#ifndef ls_time_utils_h_
#define ls_time_utils_h_
#include
#include
#include
#include
#include
#include
#include
#include "ls_compdep.h"
#include "ls_panic.h"
// Time deltas >= LS_TMO_MAX (in seconds) are treated as "forever".
#define LS_TMO_MAX 2147483647.0
// These are wrapped into structs so that it's hard to do the wrong thing.
// A time stamp. Can be either valid or "bad".
typedef struct {
double ts;
} LS_TimeStamp;
// A time delta. Can be either valid or "forever".
typedef struct {
double delta;
} LS_TimeDelta;
#define LS_TD_FOREVER ((LS_TimeDelta) {LS_TMO_MAX})
#define LS_TD_ZERO ((LS_TimeDelta) {0.0})
#define LS_TS_BAD ((LS_TimeStamp) {-1.0})
LS_INHEADER bool ls_double_is_good_time_delta(double x)
{
return isgreaterequal(x, 0.0);
}
LS_INHEADER bool ls_TD_is_forever(LS_TimeDelta TD)
{
return TD.delta >= LS_TMO_MAX;
}
LS_INHEADER bool ls_TS_is_bad(LS_TimeStamp TS)
{
return TS.ts < 0;
}
LS_INHEADER bool ls_double_to_TD_checked(double delta, LS_TimeDelta *out)
{
if (!ls_double_is_good_time_delta(delta)) {
return false;
}
if (delta > LS_TMO_MAX) {
delta = LS_TMO_MAX;
}
*out = (LS_TimeDelta) {delta};
return true;
}
LS_INHEADER LS_TimeDelta ls_double_to_TD(double delta, LS_TimeDelta if_bad)
{
LS_TimeDelta res;
if (ls_double_to_TD_checked(delta, &res)) {
return res;
}
return if_bad;
}
LS_INHEADER LS_TimeDelta ls_double_to_TD_or_die(double delta)
{
LS_TimeDelta res;
if (!ls_double_to_TD_checked(delta, &res)) {
LS_PANIC("ls_double_to_TD_or_die() failed");
}
return res;
}
LS_INHEADER double ls_timespec_to_raw_double(struct timespec ts)
{
double s = ts.tv_sec;
double ns = ts.tv_nsec;
return s + ns / 1e9;
}
LS_INHEADER LS_TimeStamp ls_timespec_to_TS(struct timespec ts)
{
return (LS_TimeStamp) {ls_timespec_to_raw_double(ts)};
}
LS_INHEADER LS_TimeDelta ls_timespec_to_TD(struct timespec ts)
{
return (LS_TimeDelta) {ls_timespec_to_raw_double(ts)};
}
LS_INHEADER struct timespec ls_now_timespec(void)
{
struct timespec ts;
int rc;
#if (!defined(_POSIX_MONOTONIC_CLOCK)) || (_POSIX_MONOTONIC_CLOCK < 0)
// CLOCK_MONOTONIC is not supported at compile-time.
rc = clock_gettime(CLOCK_REALTIME, &ts);
#elif _POSIX_MONOTONIC_CLOCK > 0
// CLOCK_MONOTONIC is supported both at compile-time and at run-time.
rc = clock_gettime(CLOCK_MONOTONIC, &ts);
#else
// CLOCK_MONOTONIC is supported at compile-time, but might or might not
// be supported at run-time.
rc = clock_gettime(CLOCK_MONOTONIC, &ts);
if (rc < 0) {
rc = clock_gettime(CLOCK_REALTIME, &ts);
}
#endif
if (rc < 0) {
LS_PANIC("clock_gettime() failed");
}
return ts;
}
LS_INHEADER LS_TimeStamp ls_now(void)
{
return ls_timespec_to_TS(ls_now_timespec());
}
LS_INHEADER struct timespec ls_TD_to_timespec(LS_TimeDelta TD)
{
LS_ASSERT(!ls_TD_is_forever(TD));
double delta = TD.delta;
return (struct timespec) {
.tv_sec = delta,
.tv_nsec = (delta - (time_t) delta) * 1e9,
};
}
LS_INHEADER struct timeval ls_TD_to_timeval(LS_TimeDelta TD)
{
LS_ASSERT(!ls_TD_is_forever(TD));
double delta = TD.delta;
return (struct timeval) {
.tv_sec = delta,
.tv_usec = (delta - (time_t) delta) * 1e6,
};
}
LS_INHEADER int ls_TD_to_poll_ms_tmo(LS_TimeDelta TD)
{
if (ls_TD_is_forever(TD)) {
return -1;
}
double ms = TD.delta * 1000;
return ms > INT_MAX ? INT_MAX : ms;
}
LS_INHEADER LS_TimeDelta ls_TS_minus_TS_nonneg(LS_TimeStamp a, LS_TimeStamp b)
{
LS_ASSERT(!ls_TS_is_bad(a));
LS_ASSERT(!ls_TS_is_bad(b));
double res = a.ts - b.ts;
return (LS_TimeDelta) {res < 0 ? 0 : res};
}
LS_INHEADER LS_TimeStamp ls_TS_plus_TD(LS_TimeStamp a, LS_TimeDelta b)
{
LS_ASSERT(!ls_TS_is_bad(a));
LS_ASSERT(!ls_TD_is_forever(b));
return (LS_TimeStamp) {a.ts + b.delta};
}
LS_INHEADER bool ls_TD_less(LS_TimeDelta a, LS_TimeDelta b)
{
if (ls_TD_is_forever(a) && ls_TD_is_forever(b)) {
return false;
}
return a.delta < b.delta;
}
LS_INHEADER void ls_sleep(LS_TimeDelta TD)
{
if (ls_TD_is_forever(TD)) {
for (;;) {
pause();
}
}
struct timespec ts = ls_TD_to_timespec(TD);
struct timespec rem;
while (nanosleep(&ts, &rem) < 0 && errno == EINTR) {
ts = rem;
}
}
LS_INHEADER void ls_sleep_simple(double seconds)
{
ls_sleep(ls_double_to_TD_or_die(seconds));
}
#endif
================================================
FILE: libls/ls_tls_ebuf.c
================================================
/*
* Copyright (C) 2015-2025 luastatus developers
*
* This file is part of luastatus.
*
* luastatus is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* luastatus is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with luastatus. If not, see .
*/
#include "ls_tls_ebuf.h"
static __thread char ebuf[LS_TLS_EBUF_N];
char *ls_tls_ebuf(void)
{
return ebuf;
}
================================================
FILE: libls/ls_tls_ebuf.h
================================================
/*
* Copyright (C) 2015-2025 luastatus developers
*
* This file is part of luastatus.
*
* luastatus is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* luastatus is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with luastatus. If not, see .
*/
#ifndef ls_tls_ebuf_h_
#define ls_tls_ebuf_h_
#include "ls_compdep.h"
#include "ls_cstring_utils.h"
// Thread-local storage buffer. Used mostly as a buffer for /ls_strerror_r/.
enum { LS_TLS_EBUF_N = 512 };
char *ls_tls_ebuf(void);
LS_INHEADER const char *ls_tls_strerror(int errnum)
{
return ls_strerror_r(errnum, ls_tls_ebuf(), LS_TLS_EBUF_N);
}
#endif
================================================
FILE: libls/ls_xallocf.c
================================================
/*
* Copyright (C) 2025 luastatus developers
*
* This file is part of luastatus.
*
* luastatus is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* luastatus is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with luastatus. If not, see .
*/
#include "ls_xallocf.h"
#include
#include
#include
#include
#include "ls_alloc_utils.h"
#include "ls_panic.h"
char *ls_xallocvf(const char *fmt, va_list vl)
{
va_list vl2;
va_copy(vl2, vl);
int n = vsnprintf(NULL, 0, fmt, vl);
if (n < 0) {
goto fail;
}
size_t nbuf = ((size_t) n) + 1;
char *r = LS_XNEW(char, nbuf);
if (vsnprintf(r, nbuf, fmt, vl2) < 0) {
goto fail;
}
va_end(vl2);
return r;
fail:
LS_PANIC_WITH_ERRNUM("ls_xallocvf: vsnprintf() failed", errno);
}
================================================
FILE: libls/ls_xallocf.h
================================================
/*
* Copyright (C) 2025 luastatus developers
*
* This file is part of luastatus.
*
* luastatus is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* luastatus is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with luastatus. If not, see .
*/
#ifndef ls_xallocf_h_
#define ls_xallocf_h_
#include
#include "ls_compdep.h"
// Functions below allocate a formatted C string (as with with 'malloc()').
// They panic on failure (including out-of-memory condition, bad format
// string, etc).
LS_ATTR_PRINTF(1, 0)
char *ls_xallocvf(const char *fmt, va_list vl);
LS_INHEADER LS_ATTR_PRINTF(1, 2)
char *ls_xallocf(const char *fmt, ...)
{
va_list vl;
va_start(vl, fmt);
char *r = ls_xallocvf(fmt, vl);
va_end(vl);
return r;
}
#endif
================================================
FILE: libmoonvisit/CMakeLists.txt
================================================
file (GLOB sources "*.c")
add_library (moonvisit OBJECT ${sources})
target_compile_definitions (moonvisit PUBLIC -D_POSIX_C_SOURCE=200809L)
luastatus_target_compile_with (moonvisit LUA)
find_library (MATH_LIBRARY m)
if (MATH_LIBRARY)
target_link_libraries (moonvisit PUBLIC ${MATH_LIBRARY})
endif ()
set_target_properties (moonvisit PROPERTIES POSITION_INDEPENDENT_CODE ON)
================================================
FILE: libmoonvisit/README.txt
================================================
Just a tiny library that helps with traversing complex Lua structures.
================================================
FILE: libmoonvisit/moonvisit.c
================================================
/*
* Copyright (C) 2015-2025 luastatus developers
*
* This file is part of luastatus.
*
* luastatus is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* luastatus is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with luastatus. If not, see .
*/
#include "moonvisit.h"
#include
#include
#include
#include
#include
#include
#include
#include
static void oom_handler(void)
{
fputs("FATAL ERROR: out of memory (libmoonvisit).\n", stderr);
abort();
}
// Copies zero-terminated 'src' into buffer 'dst' of size 'ndst', possibly truncating, but always
// zero-terminating the output (unless 'ndst' is 0, of course).
//
// Returns number of bytes written, not including terminating NUL.
static size_t write_str(char *dst, size_t ndst, const char *src)
{
if (!ndst) {
return 0;
}
size_t ncopy = strnlen(src, ndst - 1);
memcpy(dst, src, ncopy);
dst[ncopy] = '\0';
return ncopy;
}
bool moon_visit_err(MoonVisit *mv, const char *fmt, ...)
{
size_t off = 0;
if (mv->where) {
off += write_str(mv->errbuf + off, mv->nerrbuf - off, mv->where);
off += write_str(mv->errbuf + off, mv->nerrbuf - off, ": ");
}
va_list vl;
va_start(vl, fmt);
int r = vsnprintf(mv->errbuf + off, mv->nerrbuf - off, fmt, vl);
va_end(vl);
if (r < 0) {
mv->errbuf[0] = '\0';
return false;
}
return true;
}
int moon_visit_checktype_at(
MoonVisit *mv,
const char *what,
int pos,
int type)
{
int got_type = lua_type(mv->L, pos);
if (got_type != type) {
if (what) {
moon_visit_err(
mv, "%s: expected %s, found %s",
what,
lua_typename(mv->L, type),
lua_typename(mv->L, got_type));
} else {
moon_visit_err(
mv, "expected %s, found %s",
lua_typename(mv->L, type),
lua_typename(mv->L, got_type));
}
return -1;
}
return 0;
}
int moon_visit_str_f(
MoonVisit *mv,
int table_pos,
const char *key,
int (*f)(MoonVisit *mv, void *ud, const char *s, size_t ns),
void *ud,
bool skip_nil)
{
int top = lua_gettop(mv->L);
int r;
lua_getfield(mv->L, table_pos, key);
if (skip_nil && lua_isnil(mv->L, -1)) {
r = 0;
goto done;
}
if (moon_visit_checktype_at(mv, key, -1, LUA_TSTRING) < 0) {
r = -1;
goto done;
}
size_t ns;
const char *s = lua_tolstring(mv->L, -1, &ns);
const char *old_where = mv->where;
r = f(mv, ud, s, ns);
mv->where = old_where;
done:
lua_settop(mv->L, top);
return r;
}
int moon_visit_str(
MoonVisit *mv,
int table_pos,
const char *key,
char **ps,
size_t *pn,
bool skip_nil)
{
int r;
lua_getfield(mv->L, table_pos, key);
if (skip_nil && lua_isnil(mv->L, -1)) {
r = 0;
goto done;
}
if (moon_visit_checktype_at(mv, key, -1, LUA_TSTRING) < 0) {
r = -1;
goto done;
}
size_t ns;
const char *s = lua_tolstring(mv->L, -1, &ns);
char *buf = malloc(ns + 1);
if (!buf) {
oom_handler();
}
memcpy(buf, s, ns + 1);
*ps = buf;
if (pn) {
*pn = ns;
}
r = 1;
done:
lua_pop(mv->L, 1);
return r;
}
int moon_visit_num(
MoonVisit *mv,
int table_pos,
const char *key,
double *p,
bool skip_nil)
{
int r;
lua_getfield(mv->L, table_pos, key);
if (skip_nil && lua_isnil(mv->L, -1)) {
r = 0;
goto done;
}
if (moon_visit_checktype_at(mv, key, -1, LUA_TNUMBER) < 0) {
r = -1;
goto done;
}
*p = lua_tonumber(mv->L, -1);
r = 1;
done:
lua_pop(mv->L, 1);
return r;
}
int moon_visit_bool(
MoonVisit *mv,
int table_pos,
const char *key,
bool *p,
bool skip_nil)
{
int r;
lua_getfield(mv->L, table_pos, key);
if (skip_nil && lua_isnil(mv->L, -1)) {
r = 0;
goto done;
}
if (moon_visit_checktype_at(mv, key, -1, LUA_TBOOLEAN) < 0) {
r = -1;
goto done;
}
*p = lua_toboolean(mv->L, -1);
r = 1;
done:
lua_pop(mv->L, 1);
return r;
}
int moon_visit_table_f(
MoonVisit *mv,
int table_pos,
const char *key,
int (*f)(MoonVisit *mv, void *ud, int kpos, int vpos),
void *ud,
bool skip_nil)
{
int r;
lua_getfield(mv->L, table_pos, key);
if (skip_nil && lua_isnil(mv->L, -1)) {
r = 0;
goto done;
}
r = moon_visit_table_f_at(mv, key, -1, f, ud);
done:
lua_pop(mv->L, 1);
return r;
}
static inline bool aux_is_first_key_numeric(lua_State *L, int pos)
{
// L: ?
int adj_pos = pos < 0 ? (pos - 1) : pos;
lua_pushnil(L); // L: ? nil
if (!lua_next(L, adj_pos)) {
// L: ?
return false;
}
// L: ? key value
bool res = lua_isnumber(L, -2);
lua_pop(L, 2); // L: ?
return res;
}
static inline size_t aux_get_array_len(lua_State *L, int pos)
{
#if LUA_VERSION_NUM <= 501
return lua_objlen(L, pos);
#else
return lua_rawlen(L, pos);
#endif
}
int moon_visit_table_f_at(
MoonVisit *mv,
const char *what,
int pos,
int (*f)(MoonVisit *mv, void *ud, int kpos, int vpos),
void *ud)
{
int top = lua_gettop(mv->L);
int r;
if (moon_visit_checktype_at(mv, what, pos, LUA_TTABLE) < 0) {
r = -1;
goto done;
}
r = 0;
const char *old_where = mv->where;
if (aux_is_first_key_numeric(mv->L, pos)) {
int adj_pos = pos < 0 ? (pos - 1) : pos;
size_t len = aux_get_array_len(mv->L, pos);
// L: ?
for (size_t i = 1; i <= len; ++i) {
lua_pushinteger(mv->L, i); // mv->L: ? i
lua_rawgeti(mv->L, adj_pos, i); // mv->L: ? i value
r = f(mv, ud, -2, -1);
mv->where = old_where;
if (r < 0) {
break;
}
lua_pop(mv->L, 2); // mv->L: ?
}
} else {
int adj_pos = pos < 0 ? (pos - 1) : pos;
// mv->L: ?
lua_pushnil(mv->L); // mv->L: ? nil
while (lua_next(mv->L, adj_pos)) {
// mv->L: ? key value
r = f(mv, ud, -2, -1);
mv->where = old_where;
if (r < 0) {
break;
}
lua_pop(mv->L, 1); // mv->L: ? key
}
}
lua_settop(mv->L, top);
done:
return r;
}
int moon_visit_sint(
MoonVisit *mv,
int table_pos,
const char *key,
int64_t *p,
bool skip_nil)
{
double d;
int r = moon_visit_num(mv, table_pos, key, &d, skip_nil);
if (r > 0) {
if (isnan(d)) {
moon_visit_err(mv, "%s: is NaN", key);
return -1;
}
if (!(d >= -9223372036854775808.0 && d < 9223372036854775808.0)) {
moon_visit_err(mv, "%s: value out of range INT64_MIN...INT64_MAX", key);
return -1;
}
*p = d;
}
return r;
}
int moon_visit_uint(
MoonVisit *mv,
int table_pos,
const char *key,
uint64_t *p,
bool skip_nil)
{
double d;
int r = moon_visit_num(mv, table_pos, key, &d, skip_nil);
if (r > 0) {
if (isnan(d)) {
moon_visit_err(mv, "%s: is NaN", key);
return -1;
}
if (!(d >= 0.0 && d < 18446744073709551616.0)) {
moon_visit_err(mv, "%s: value out of range 0...UINT64_MAX", key);
return -1;
}
*p = d;
}
return r;
}
int moon_visit_scrutinize_table(
MoonVisit *mv,
int table_pos,
const char *key,
bool nil_ok)
{
lua_getfield(mv->L, table_pos, key);
int type = lua_type(mv->L, -1);
if (type == LUA_TNIL && nil_ok) {
return 0;
}
if (moon_visit_checktype_at(mv, key, -1, LUA_TTABLE) < 0) {
lua_pop(mv->L, 1);
return -1;
}
return 0;
}
int moon_visit_scrutinize_str(
MoonVisit *mv,
int table_pos,
const char *key,
const char **out,
size_t *out_len,
bool nil_ok)
{
lua_getfield(mv->L, table_pos, key);
int type = lua_type(mv->L, -1);
if (type == LUA_TNIL && nil_ok) {
*out = NULL;
if (out_len) {
*out_len = 0;
}
return 0;
}
if (moon_visit_checktype_at(mv, key, -1, LUA_TSTRING) < 0) {
lua_pop(mv->L, 1);
return -1;
}
if (out_len) {
*out = lua_tolstring(mv->L, -1, out_len);
} else {
*out = lua_tostring(mv->L, -1);
}
return 0;
}
================================================
FILE: libmoonvisit/moonvisit.h
================================================
/*
* Copyright (C) 2015-2025 luastatus developers
*
* This file is part of luastatus.
*
* luastatus is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* luastatus is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with luastatus. If not, see .
*/
#ifndef moonvisit_h_
#define moonvisit_h_
#include
#include
#include
#include
#if ! defined(MOON_VISIT_PRINTF_ATTR)
# if __GNUC__ >= 2
# define MOON_VISIT_PRINTF_ATTR(N, M) __attribute__((format(printf, N, M)))
# else
# define MOON_VISIT_PRINTF_ATTR(N, M) /*nothing*/
# endif
#endif
typedef struct {
// Filled by user, never altered (as a pointer) by library.
lua_State *L;
// Filled by user, never altered (as a pointer) by library.
//
// On error, this buffer is written to, assuming it has the size of 'nerrbuf', and is always
// zero-terminated (unless 'nerrbuf' is 0, of course).
char *errbuf;
// Filled by the user, never altered by library.
size_t nerrbuf;
// Filled by user, but might be altered (as a pointer) by the library in the process of
// saving/restoring old value of 'where'.
const char *where;
} MoonVisit;
// Prints a formatted string into 'mv->errbuf' (prepended with ": " if 'mv->where' is not
// NULL).
//
// Returns true on success, false on "encoding error".
MOON_VISIT_PRINTF_ATTR(2, 3)
bool moon_visit_err(MoonVisit *mv, const char *fmt, ...);
// Checks that the element at stack position 'pos' in 'mv->L' has type of 'type'.
// The fact that 'pos' is a valid stack index is *not* checked, simply assumed to be true.
//
// 'type' must be one of LUA_T* constants.
//
// 'what' must be string description of the value that is being tested.
//
// On success, returns 0.
//
// On failure, returns -1 and writes the error message into 'mv->errbuf'.
int moon_visit_checktype_at(
MoonVisit *mv,
const char *what,
int pos,
int type);
int moon_visit_str_f(
MoonVisit *mv,
int table_pos,
const char *key,
int (*f)(MoonVisit *mv, void *ud, const char *s, size_t ns),
void *ud,
bool skip_nil);
// Duplicates the string as if with 'malloc()', writing zero-terminated duplicated string into
// '*ps'; and, if 'pn' is not NULL, the length into '*pn'.
int moon_visit_str(
MoonVisit *mv,
int table_pos,
const char *key,
char **ps,
size_t *pn,
bool skip_nil);
int moon_visit_num(
MoonVisit *mv,
int table_pos,
const char *key,
double *p,
bool skip_nil);
int moon_visit_bool(
MoonVisit *mv,
int table_pos,
const char *key,
bool *p,
bool skip_nil);
int moon_visit_table_f(
MoonVisit *mv,
int table_pos,
const char *key,
int (*f)(MoonVisit *mv, void *ud, int kpos, int vpos),
void *ud,
bool skip_nil);
int moon_visit_table_f_at(
MoonVisit *mv,
const char *what,
int pos,
int (*f)(MoonVisit *mv, void *ud, int kpos, int vpos),
void *ud);
// Beware: current implementation can only faithfully (without precision loss) fetch integers that
// fit into 'double' (on virtually every platform it means integers with absolute value <= 2^53).
int moon_visit_sint(
MoonVisit *mv,
int table_pos,
const char *key,
int64_t *p,
bool skip_nil);
// Beware: current implementation can only faithfully (without precision loss) fetch integers that
// fit into 'double' (on virtually every platform it means values that are <= 2^53).
int moon_visit_uint(
MoonVisit *mv,
int table_pos,
const char *key,
uint64_t *p,
bool skip_nil);
// Pushes /t[key]/ on the stack, where /t/ is the table residing at stack position /table_pos/.
// Checks that the result is a table.
// If /nil_ok/ is true and the result is nil, pushes a nil instead of a table.
int moon_visit_scrutinize_table(
MoonVisit *mv,
int table_pos,
const char *key,
bool nil_ok);
// Pushes /t[key]/ on the stack, where /t/ is the table residing at stack position /table_pos/.
//
// Checks that the result is a string. If so, /*out/ is set to the contents of the string,
// and, if /out_len != NULL/, writes the length of the string into /*out_len/.
//
// If /nil_ok/ is true and the result is nil, pushes a nil instead of a table, sets /*out/ to
// NULL, and, if /out_len != NULL/, sets /*out_len/ to zero.
int moon_visit_scrutinize_str(
MoonVisit *mv,
int table_pos,
const char *key,
const char **out,
size_t *out_len,
bool nil_ok);
#endif
================================================
FILE: libprocalive/CMakeLists.txt
================================================
file (GLOB sources "*.c")
add_library (procalive OBJECT ${sources})
target_compile_definitions (procalive PUBLIC -D_POSIX_C_SOURCE=200809L)
luastatus_target_compile_with (procalive LUA)
find_library (MATH_LIBRARY m)
if (MATH_LIBRARY)
target_link_libraries (procalive PUBLIC ${MATH_LIBRARY})
endif ()
set_target_properties (procalive PROPERTIES POSITION_INDEPENDENT_CODE ON)
================================================
FILE: libprocalive/procalive_lfuncs.c
================================================
/*
* Copyright (C) 2015-2025 luastatus developers
*
* This file is part of luastatus.
*
* luastatus is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* luastatus is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with luastatus. If not, see .
*/
#include "procalive_lfuncs.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#if _POSIX_C_SOURCE < 200112L || defined(_GNU_SOURCE)
# error "Unsupported feature test macros."
#endif
static __thread char errbuf[512];
static const char *my_strerror(int errnum)
{
// We introduce an /int/ variable in order to get a compilation warning if /strerror_r()/ is
// still GNU-specific and returns a pointer to char.
int r = strerror_r(errnum, errbuf, sizeof(errbuf));
return r == 0 ? errbuf : "unknown error or truncated error message";
}
#ifdef LUA_MAXINTEGER
# define MAXI \
(LUA_MAXINTEGER > (SIZE_MAX - 1) ? (SIZE_MAX - 1) : LUA_MAXINTEGER)
#else
# define MAXI INT_MAX
#endif
int procalive_lfunc_access(lua_State *L)
{
const char *path = luaL_checkstring(L, 1);
if (access(path, F_OK) >= 0) {
lua_pushboolean(L, 1);
// L: true
lua_pushnil(L);
// L: true nil
} else {
int saved_errno = errno;
lua_pushboolean(L, 0);
// L: false
if (saved_errno == ENOENT) {
lua_pushnil(L);
// L: false nil
} else {
lua_pushstring(L, my_strerror(saved_errno));
// L: false str
}
}
return 2;
}
int procalive_lfunc_stat(lua_State *L)
{
const char *path = luaL_checkstring(L, 1);
struct stat sb;
if (stat(path, &sb) < 0) {
int saved_errno = errno;
lua_pushnil(L); // L: nil
lua_pushstring(L, my_strerror(saved_errno)); // L: nil err
return 2;
}
mode_t M = sb.st_mode;
const char *M_str;
if (S_ISREG(M)) {
M_str = "regular";
} else if (S_ISDIR(M)) {
M_str = "dir";
} else if (S_ISCHR(M)) {
M_str = "chardev";
} else if (S_ISBLK(M)) {
M_str = "blockdev";
} else if (S_ISFIFO(M)) {
M_str = "fifo";
} else if (S_ISLNK(M)) {
M_str = "symlink";
} else if (S_ISSOCK(M)) {
M_str = "socket";
} else {
M_str = "other";
}
lua_pushstring(L, M_str); // L: res
lua_pushnil(L); // L: res nil
return 2;
}
static inline int get_lua_num_prealloc(size_t n)
{
return n < (size_t) INT_MAX ? (int) n : INT_MAX;
}
static bool push_glob_t(lua_State *L, glob_t *g)
{
size_t n = g->gl_pathc;
if (n > (size_t) MAXI) {
return false;
}
lua_createtable(L, get_lua_num_prealloc(n), 0); // L: array
for (size_t i = 0; i < n; ++i) {
lua_pushstring(L, g->gl_pathv[i]); // L: array str
lua_rawseti(L, -2, i + 1); // L: array
}
return true;
}
int procalive_lfunc_glob(lua_State *L)
{
const char *pattern = luaL_checkstring(L, 1);
glob_t g = {0};
int rc = glob(pattern, GLOB_MARK | GLOB_NOSORT, NULL, &g);
int err_num;
switch (rc) {
case 0:
if (!push_glob_t(L, &g)) {
err_num = EOVERFLOW;
goto fail;
}
// L: res
goto ok;
case GLOB_NOMATCH:
lua_newtable(L); // L: res
goto ok;
case GLOB_ABORTED:
err_num = EIO;
goto fail;
case GLOB_NOSPACE:
err_num = ENOMEM;
goto fail;
default:
err_num = EINVAL;
goto fail;
}
ok:
globfree(&g);
// L: res
lua_pushnil(L); // L: res nil
return 2;
fail:
globfree(&g);
lua_pushnil(L); // L: nil
lua_pushstring(L, my_strerror(err_num)); // L: nil err
return 2;
}
static pid_t parse_pid(const char *s, const char **out_err_msg)
{
char *endptr;
errno = 0;
intmax_t mres = strtoimax(s, &endptr, 10);
if (errno != 0 || *s == '\0' || *endptr != '\0') {
*out_err_msg = "cannot parse into intmax_t";
return -1;
}
pid_t res = mres;
if (res <= 0 || res != mres) {
*out_err_msg = "outside of valid range for pid_t";
return -1;
}
return res;
}
int procalive_lfunc_is_process_alive(lua_State *L)
{
luaL_checkany(L, 1);
pid_t pid;
if (lua_isstring(L, 1)) {
const char *err_msg;
pid = parse_pid(lua_tostring(L, 1), &err_msg);
if (pid <= 0) {
return luaL_argerror(L, 1, err_msg);
}
} else if (lua_isnumber(L, 1)) {
double d = lua_tonumber(L, 1);
if (!isgreaterequal(d, 1.0)) {
return luaL_argerror(L, 1, "must be >= 1");
}
if (d > INT_MAX) {
return luaL_argerror(L, 1, "too large");
}
int i = (int) d;
pid = (pid_t) i;
if (pid <= 0 || pid != i) {
return luaL_argerror(L, 1, "outside of valid range for pid_t");
}
} else {
return luaL_argerror(L, 1, "neither number nor string");
}
bool res = (kill(pid, 0) >= 0 || errno == EPERM);
lua_pushboolean(L, res); // L: res
return 1;
}
void procalive_lfuncs_register_all(lua_State *L)
{
// L: table
lua_pushcfunction(L, procalive_lfunc_access); // L: table func
lua_setfield(L, -2, "access"); // L: table
lua_pushcfunction(L, procalive_lfunc_stat); // L: table func
lua_setfield(L, -2, "stat"); // L: table
lua_pushcfunction(L, procalive_lfunc_glob); // L: table func
lua_setfield(L, -2, "glob"); // L: table
lua_pushcfunction(L, procalive_lfunc_is_process_alive); // L: table func
lua_setfield(L, -2, "is_process_alive"); // L: table
}
================================================
FILE: libprocalive/procalive_lfuncs.h
================================================
/*
* Copyright (C) 2015-2025 luastatus developers
*
* This file is part of luastatus.
*
* luastatus is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* luastatus is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with luastatus. If not, see .
*/
#ifndef libprocalive_procalive_lfuncs_h_
#define libprocalive_procalive_lfuncs_h_
#include
// Lua function access(path):
// Checks if a given path exists, as if with 'access(path, F_OK)'. If it does
// exist, returns 'true, nil'. If it does not, returns 'false, nil'. If an error
// occurs, returns 'false, err_msg'.
int procalive_lfunc_access(lua_State *L);
// Lua function stat(path):
// Tries to get the type of the file at the given path. On success returns either of:
// * "regular",
// * "dir" (directory),
// * "chardev" (character device),
// * "blockdev" (block device),
// * "fifo",
// * "symlink",
// * "socket",
// * "other".
// On failure returns '`'nil, err_msg'.
int procalive_lfunc_stat(lua_State *L);
// Lua function glob(pattern):
// Performs glob expansion of pattern. A glob is a wildcard pattern like /tmp/*.txt
// that can be applied as a filter to a list of existing file names. Supported
// expansions are *, ? and [...]. Please refer to glob(7) for more information on
// wildcard patterns.
//
// Note also that the globbing is performed with GLOB_MARK flag, so that in output,
// directories have trailing slash appended to their name.
//
// Returns 'arr, nil' on success, where 'arr' is an array of strings; these are
// existing file names that matched the given pattern. The order is arbitrary.
//
// On failure, returns 'nil, err_msg'.
int procalive_lfunc_glob(lua_State *L);
// Lua function is_process_alive(pid):
// Checks if a process with PID 'pid' is currently alive. 'pid' must be either a
// or a string.
// Returns a boolean that indicates whether the process is alive.
int procalive_lfunc_is_process_alive(lua_State *L);
// Registers all functions described above into a table on the top of /L/'s stack.
void procalive_lfuncs_register_all(lua_State *L);
#endif
================================================
FILE: librunshell/CMakeLists.txt
================================================
file (GLOB sources "*.c")
add_library (runshell OBJECT ${sources})
target_compile_definitions (runshell PUBLIC -D_POSIX_C_SOURCE=200809L)
luastatus_target_compile_with (runshell LUA)
set_target_properties (runshell PROPERTIES POSITION_INDEPENDENT_CODE ON)
================================================
FILE: librunshell/README.txt
================================================
A thread-safe (modulo thread cancellation) version of system().
It also does not modify SIGQUIT/SIGINT signal dispositions.
================================================
FILE: librunshell/runshell.c
================================================
/*
* Copyright (C) 2025 luastatus developers
*
* This file is part of luastatus.
*
* luastatus is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* luastatus is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with luastatus. If not, see .
*/
#include "runshell.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#if LUA_VERSION_NUM >= 504
# define my_pushfail(L_) luaL_pushfail(L_)
#else
# define my_pushfail(L_) lua_pushnil(L_)
#endif
#define CANNOT_FAIL(Expr_) \
do { \
if ((Expr_) < 0) { \
perror("librunshell: " #Expr_ " failed"); \
abort(); \
} \
} while (0)
#define CANNOT_FAIL_PTH(Expr_) \
do { \
if ((errno = (Expr_)) != 0) { \
perror("librunshell: " #Expr_ " failed"); \
abort(); \
} \
} while (0)
static __thread char errbuf[512];
#if _POSIX_C_SOURCE < 200112L || defined(_GNU_SOURCE)
# error "Unsupported feature test macros."
#endif
static __thread char errbuf[512];
static const char *my_strerror(int errnum)
{
// We introduce an /int/ variable in order to get a compilation warning if /strerror_r()/ is
// still GNU-specific and returns a pointer to char.
int r = strerror_r(errnum, errbuf, sizeof(errbuf));
return r == 0 ? errbuf : "unknown error or truncated error message";
}
extern char **environ;
int runshell(const char *cmd)
{
if (!cmd) {
fputs("librunshell: passed cmd == NULL (this is not supported)\n", stderr);
abort();
}
sigset_t ss_new;
sigset_t ss_old;
CANNOT_FAIL(sigemptyset(&ss_new));
CANNOT_FAIL(sigaddset(&ss_new, SIGCHLD));
CANNOT_FAIL(sigprocmask(SIG_BLOCK, &ss_new, &ss_old));
posix_spawnattr_t attr;
CANNOT_FAIL_PTH(posix_spawnattr_init(&attr));
CANNOT_FAIL_PTH(posix_spawnattr_setsigmask(&attr, &ss_old));
CANNOT_FAIL_PTH(posix_spawnattr_setflags(&attr, POSIX_SPAWN_SETSIGMASK));
char *const argv[] = {
(char *) "sh",
(char *) "-c",
(char *) cmd,
NULL,
};
pid_t pid;
int rc = posix_spawn(
/*pid=*/ &pid,
/*path=*/ "/bin/sh",
/*file_actions=*/ NULL,
/*attrp=*/ &attr,
/*argv=*/ argv,
/*envp=*/ environ);
CANNOT_FAIL_PTH(posix_spawnattr_destroy(&attr));
int ret;
int saved_errno;
if (rc == 0) {
saved_errno = 0;
while (waitpid(pid, &ret, 0) < 0) {
if (errno != EINTR) {
ret = -1;
saved_errno = errno;
break;
}
}
} else {
ret = -1;
saved_errno = rc;
}
CANNOT_FAIL(sigprocmask(SIG_SETMASK, &ss_old, NULL));
errno = saved_errno;
return ret;
}
int runshell_l_os_execute_lua51ver(lua_State *L)
{
const char *cmd = luaL_optstring(L, 1, NULL);
// L: ?
if (!cmd) {
lua_pushinteger(L, 1); // L: ? 1
return 1;
}
lua_pushinteger(L, runshell(cmd)); // L: ? code
return 1;
}
int runshell_l_os_execute(lua_State *L)
{
const char *cmd = luaL_optstring(L, 1, NULL);
// L: ?
if (!cmd) {
lua_pushboolean(L, 1); // L: ? true
return 1;
}
int rc = runshell(cmd);
if (rc < 0) {
int saved_errno = errno;
my_pushfail(L); // L: ? fail
lua_pushstring(L, my_strerror(saved_errno)); // L: ? fail err_msg
lua_pushinteger(L, saved_errno); // L: ? fail err_msg errno
return 3;
}
int code;
bool normal_exit = true;
if (WIFEXITED(rc)) {
code = WEXITSTATUS(rc);
} else if (WIFSIGNALED(rc)) {
code = WTERMSIG(rc);
normal_exit = false;
} else {
code = rc;
}
if (normal_exit && code == 0) {
lua_pushboolean(L, 1); // L: ? true
} else {
my_pushfail(L); // L: ? fail
}
lua_pushstring(L, normal_exit ? "exit" : "signal"); // L: ? is_ok what
lua_pushinteger(L, code); // L: ? is_ok what code
return 3;
}
================================================
FILE: librunshell/runshell.h
================================================
/*
* Copyright (C) 2025 luastatus developers
*
* This file is part of luastatus.
*
* luastatus is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* luastatus is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with luastatus. If not, see