[
  {
    "path": ".circleci/config.yml",
    "content": "version: 2.1\nworkflows:\n  nanodesu:\n    jobs:\n      - subetedesu:\n          matrix:\n            parameters:\n              luaver: [\"lua5.1/liblua5.1-dev\", \"luajit/libluajit-5.1-dev\", \"lua5.2/liblua5.2-dev\", \"lua5.3/liblua5.3-dev\"]\n\njobs:\n  subetedesu:\n    docker:\n      - image: cimg/base:2021.01-20.04\n    parameters:\n      luaver:\n        type: string\n    steps:\n      - checkout\n      - run: ./check_style.sh\n      - run: sudo apt-get update\n      - 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\n      - run: v=<< parameters.luaver >>; cmake -DWITH_LUA_LIBRARY=${v%/*} -DBUILD_PLUGIN_PULSE=ON -DBUILD_PLUGIN_UNIXSOCK=ON -DBUILD_PLUGIN_WEB=ON -DBUILD_TESTS=ON .\n      - run: make -j\n      - run: ./tests/torture.sh .\n      - run: v=<< parameters.luaver >>; LUAVER=$v PLUGIN_DBUS_OPTIONAL=1 ./.circleci/run-pt.sh\n"
  },
  {
    "path": ".circleci/run-pt.sh",
    "content": "#!/usr/bin/env bash\n\nset -e\n\natexit=()\n\ntrap '\n    # We need to run finalizers in reversed order.\n    for (( i = ${#atexit[@]} - 1; i >= 0; --i )); do\n        func=${atexit[$i]}\n        $func\n    done\n' EXIT\n\npa_start() {\n    atexit+=(pa_end)\n\n    pulseaudio --daemonize=no --disallow-exit=yes --exit-idle-time=-1 &\n    while ! pactl info; do\n        echo >&2 \"Waiting for PulseAudio daemon...\"\n        sleep 1\n    done\n}\n\npa_end() {\n    pulseaudio --kill || true\n}\n\ndbus_start() {\n    DBUS_DAEMON_PID=\n    atexit+=(dbus_end)\n\n    local addr_file=/tmp/dbus-session-addr.txt\n    true > \"$addr_file\" || return $?\n\n    dbus-daemon --session --nofork --print-address=3 3>\"$addr_file\" &\n    DBUS_DAEMON_PID=$!\n\n    local x\n    while ! IFS= read -r x < \"$addr_file\"; do\n        echo >&2 \"Waiting for D-Bus daemon...\"\n        sleep 1 || return $?\n    done\n    rm -f \"$addr_file\" || return $?\n    export DBUS_SESSION_BUS_ADDRESS=\"$x\"\n}\n\ndbus_end() {\n    if [[ -n \"$DBUS_DAEMON_PID\" ]]; then\n        kill \"$DBUS_DAEMON_PID\" || true\n    fi\n}\n\ndbus_start\nsleep 1\npa_start\n\n#PT_TOOL=valgrind PT_MAX_LAG=250 ./tests/pt.sh .\nPT_MAX_LAG=300 ./tests/pt.sh .\n"
  },
  {
    "path": ".gitignore",
    "content": "*.o\n*.so\n/_gitignored/\n# CMake\nCMakeCache.txt\nCMakeFiles\nCMakeScripts\nMakefile\ncmake_install.cmake\ninstall_manifest.txt\nCTestTestfile.cmake\nbuild/\n# generated headers\n*.generated.[ch]\n# generated man pages\nluastatus*.[1-8]\n"
  },
  {
    "path": ".luacheckrc",
    "content": "new_globals = {'widget'}\nnew_read_globals = {'luastatus'}\nignore = {\n    '631', -- \"Line is too long\"\n}\n"
  },
  {
    "path": "CMakeLists.txt",
    "content": "cmake_minimum_required (VERSION 3.1.3...3.10)\nproject (luastatus C)\n\nif (NOT CMAKE_BUILD_TYPE)\n    set (CMAKE_BUILD_TYPE Release)\nendif ()\n\nset (CMAKE_C_STANDARD 99)\n\nset (CMAKE_C_FLAGS \"${CMAKE_C_FLAGS} -Wall -Wextra\")\n\n#------------------------------------------------------------------------------\n\noption (WITH_UBSAN \"build with UBSAN (undefined behavior sanitizer)\" OFF)\nif (${WITH_UBSAN})\n    set (CMAKE_C_FLAGS \"${CMAKE_C_FLAGS} -fsanitize=undefined -fsanitize-trap=all -fno-sanitize-recover=all\")\n    if (CMAKE_CXX_COMPILER_ID STREQUAL \"Clang\")\n        set (CMAKE_C_FLAGS \"${CMAKE_C_FLAGS} -fsanitize=local-bounds\")\n    endif ()\nendif ()\n\noption (WITH_ASAN \"build with ASAN (address sanitizer)\" OFF)\nif (${WITH_ASAN})\n    set (CMAKE_C_FLAGS \"${CMAKE_C_FLAGS} -fsanitize=address -fsanitize-address-use-after-scope -fsanitize-trap=all -fno-sanitize-recover=all\")\nendif ()\n\noption (WITH_TSAN \"build with TSAN (thread sanitizer)\" OFF)\nif (${WITH_TSAN})\n    set (CMAKE_C_FLAGS \"${CMAKE_C_FLAGS} -fsanitize=thread -fsanitize-trap=all -fno-sanitize-recover=all\")\nendif ()\n\noption (WITH_LSAN \"build with LSAN (memory leak sanitizer)\" OFF)\nif (${WITH_LSAN})\n    set (CMAKE_C_FLAGS \"${CMAKE_C_FLAGS} -fsanitize=leak -fsanitize-trap=all -fno-sanitize-recover=all\")\nendif ()\n\n#------------------------------------------------------------------------------\n\nfind_package (PkgConfig REQUIRED)\nset (WITH_LUA_LIBRARY \"-\" CACHE STRING \"Lua library name\")\nif (NOT \"${WITH_LUA_LIBRARY}\" STREQUAL \"-\")\n    pkg_search_module (LUA REQUIRED \"${WITH_LUA_LIBRARY}\")\nelse ()\n    pkg_search_module (LUA REQUIRED\n        lua54 lua-5.4 lua5.4\n        lua53 lua-5.3 lua5.3\n        lua52 lua-5.2 lua5.2\n        lua51 lua-5.1 lua5.1\n        luajit\n        lua)\nendif ()\n\nfunction (luastatus_target_compile_with target var)\n    target_include_directories (${target} SYSTEM PUBLIC\n        ${${var}_INCLUDE_DIRS})\n    target_compile_options (${target} PUBLIC\n        ${${var}_CFLAGS_OTHER})\nendfunction ()\n\nfunction (luastatus_target_build_with target var)\n    luastatus_target_compile_with (\"${target}\" \"${var}\")\n    target_link_libraries (${target} PUBLIC ${${var}_LIBRARIES})\nendfunction ()\n\n#------------------------------------------------------------------------------\n\ninclude (GNUInstallDirs)\n\nset (BARLIBS_DIR \"${CMAKE_INSTALL_FULL_LIBDIR}/luastatus/barlibs\")\nset (PLUGINS_DIR \"${CMAKE_INSTALL_FULL_LIBDIR}/luastatus/plugins\")\nset (LUA_PLUGINS_DIR \"${CMAKE_INSTALL_FULL_DATAROOTDIR}/luastatus/plugins\")\n\nfunction (luastatus_add_barlib_or_plugin destdir name)\n    set (sources ${ARGV})\n    list (REMOVE_AT sources 0 1)\n    add_library (\"${name}\" MODULE ${sources})\n    set_target_properties (\"${name}\" PROPERTIES PREFIX \"\")\n    if (NOT \"${destdir}\" STREQUAL \"-\")\n        install (TARGETS \"${name}\" DESTINATION \"${destdir}\")\n    endif ()\nendfunction ()\n\nfunction (luastatus_add_barlib)\n    luastatus_add_barlib_or_plugin (\"${BARLIBS_DIR}\" ${ARGV})\nendfunction ()\n\nfunction (luastatus_add_plugin)\n    luastatus_add_barlib_or_plugin (\"${PLUGINS_DIR}\" ${ARGV})\nendfunction ()\n\nfunction (luastatus_add_barlib_noinstall)\n    luastatus_add_barlib_or_plugin (\"-\" ${ARGV})\nendfunction ()\n\nfunction (luastatus_add_plugin_noinstall)\n    luastatus_add_barlib_or_plugin (\"-\" ${ARGV})\nendfunction ()\n\noption (BUILD_DOCS \"build man pages\" ON)\n\nfunction (luastatus_add_man_page src basename section)\n    if (NOT BUILD_DOCS)\n        return ()\n    endif ()\n    set (dest \"${basename}.${section}\")\n    add_custom_command (\n        OUTPUT \"${dest}\"\n        COMMAND \"${PROJECT_SOURCE_DIR}/generate-man.sh\" ARGS \"${CMAKE_CURRENT_SOURCE_DIR}/${src}\" \"${dest}\"\n        WORKING_DIRECTORY \"${CMAKE_CURRENT_BINARY_DIR}\"\n        MAIN_DEPENDENCY \"${src}\"\n        VERBATIM)\n    add_custom_target (\"man${section}-${basename}\" ALL DEPENDS \"${dest}\")\n    install (\n        FILES \"${CMAKE_CURRENT_BINARY_DIR}/${dest}\"\n        DESTINATION \"${CMAKE_INSTALL_MANDIR}/man${section}\")\nendfunction ()\n\n#------------------------------------------------------------------------------\n\nadd_subdirectory (libls)\nadd_subdirectory (libsafe)\nadd_subdirectory (libhackyfix)\nadd_subdirectory (librunshell)\nadd_subdirectory (libmoonvisit)\nadd_subdirectory (libwidechar)\nadd_subdirectory (libprocalive)\nadd_subdirectory (luastatus)\n\n#------------------------------------------------------------------------------\n\nmacro (DEF_OPT optname subdir defvalue)\n    option (${optname} \"build ${subdir}\" ${defvalue})\n    if (${optname})\n        add_subdirectory (${subdir})\n    endif ()\nendmacro ()\n\nDEF_OPT (BUILD_BARLIB_DWM                 \"barlibs/dwm\"                 ON)\nDEF_OPT (BUILD_BARLIB_I3                  \"barlibs/i3\"                  ON)\nDEF_OPT (BUILD_BARLIB_LEMONBAR            \"barlibs/lemonbar\"            ON)\nDEF_OPT (BUILD_BARLIB_STDOUT              \"barlibs/stdout\"              ON)\n\nDEF_OPT (BUILD_PLUGIN_ALSA                \"plugins/alsa\"                ON)\nDEF_OPT (BUILD_PLUGIN_BACKLIGHT_LINUX     \"plugins/backlight-linux\"     ON)\nDEF_OPT (BUILD_PLUGIN_BATTERY_LINUX       \"plugins/battery-linux\"       ON)\nDEF_OPT (BUILD_PLUGIN_CPU_FREQ_LINUX      \"plugins/cpu-freq-linux\"      ON)\nDEF_OPT (BUILD_PLUGIN_CPU_USAGE_LINUX     \"plugins/cpu-usage-linux\"     ON)\nDEF_OPT (BUILD_PLUGIN_DBUS                \"plugins/dbus\"                ON)\nDEF_OPT (BUILD_PLUGIN_DISK_IO_LINUX       \"plugins/disk-io-linux\"       ON)\nDEF_OPT (BUILD_PLUGIN_FILE_CONTENTS_LINUX \"plugins/file-contents-linux\" ON)\nDEF_OPT (BUILD_PLUGIN_FS                  \"plugins/fs\"                  ON)\nDEF_OPT (BUILD_PLUGIN_IMAP                \"plugins/imap\"                ON)\nDEF_OPT (BUILD_PLUGIN_INOTIFY             \"plugins/inotify\"             ON)\nDEF_OPT (BUILD_PLUGIN_IS_PROGRAM_RUNNING  \"plugins/is-program-running\"  ON)\nDEF_OPT (BUILD_PLUGIN_MEM_USAGE_LINUX     \"plugins/mem-usage-linux\"     ON)\nDEF_OPT (BUILD_PLUGIN_MPD                 \"plugins/mpd\"                 ON)\nDEF_OPT (BUILD_PLUGIN_MPRIS               \"plugins/mpris\"               ON)\nDEF_OPT (BUILD_PLUGIN_MULTIPLEX           \"plugins/multiplex\"           ON)\nDEF_OPT (BUILD_PLUGIN_NETWORK_LINUX       \"plugins/network-linux\"       ON)\nDEF_OPT (BUILD_PLUGIN_NETWORK_RATE_LINUX  \"plugins/network-rate-linux\"  ON)\nDEF_OPT (BUILD_PLUGIN_PIPE                \"plugins/pipe\"                OFF)\nDEF_OPT (BUILD_PLUGIN_PIPEV2              \"plugins/pipev2\"              ON)\nDEF_OPT (BUILD_PLUGIN_PULSE               \"plugins/pulse\"               OFF)\nDEF_OPT (BUILD_PLUGIN_SYSTEMD_UNIT        \"plugins/systemd-unit\"        OFF)\nDEF_OPT (BUILD_PLUGIN_TEMPERATURE_LINUX   \"plugins/temperature-linux\"   ON)\nDEF_OPT (BUILD_PLUGIN_TIMER               \"plugins/timer\"               ON)\nDEF_OPT (BUILD_PLUGIN_UDEV                \"plugins/udev\"                ON)\nDEF_OPT (BUILD_PLUGIN_UNIXSOCK            \"plugins/unixsock\"            OFF)\nDEF_OPT (BUILD_PLUGIN_WEB                 \"plugins/web\"                 OFF)\nDEF_OPT (BUILD_PLUGIN_XKB                 \"plugins/xkb\"                 ON)\nDEF_OPT (BUILD_PLUGIN_XTITLE              \"plugins/xtitle\"              ON)\n\nDEF_OPT (BUILD_TESTS                      \"tests\"                       OFF)\n"
  },
  {
    "path": "COPYING.LESSER.txt",
    "content": "                   GNU LESSER GENERAL PUBLIC LICENSE\n                       Version 3, 29 June 2007\n\n Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>\n Everyone is permitted to copy and distribute verbatim copies\n of this license document, but changing it is not allowed.\n\n\n  This version of the GNU Lesser General Public License incorporates\nthe terms and conditions of version 3 of the GNU General Public\nLicense, supplemented by the additional permissions listed below.\n\n  0. Additional Definitions.\n\n  As used herein, \"this License\" refers to version 3 of the GNU Lesser\nGeneral Public License, and the \"GNU GPL\" refers to version 3 of the GNU\nGeneral Public License.\n\n  \"The Library\" refers to a covered work governed by this License,\nother than an Application or a Combined Work as defined below.\n\n  An \"Application\" is any work that makes use of an interface provided\nby the Library, but which is not otherwise based on the Library.\nDefining a subclass of a class defined by the Library is deemed a mode\nof using an interface provided by the Library.\n\n  A \"Combined Work\" is a work produced by combining or linking an\nApplication with the Library.  The particular version of the Library\nwith which the Combined Work was made is also called the \"Linked\nVersion\".\n\n  The \"Minimal Corresponding Source\" for a Combined Work means the\nCorresponding Source for the Combined Work, excluding any source code\nfor portions of the Combined Work that, considered in isolation, are\nbased on the Application, and not on the Linked Version.\n\n  The \"Corresponding Application Code\" for a Combined Work means the\nobject code and/or source code for the Application, including any data\nand utility programs needed for reproducing the Combined Work from the\nApplication, but excluding the System Libraries of the Combined Work.\n\n  1. Exception to Section 3 of the GNU GPL.\n\n  You may convey a covered work under sections 3 and 4 of this License\nwithout being bound by section 3 of the GNU GPL.\n\n  2. Conveying Modified Versions.\n\n  If you modify a copy of the Library, and, in your modifications, a\nfacility refers to a function or data to be supplied by an Application\nthat uses the facility (other than as an argument passed when the\nfacility is invoked), then you may convey a copy of the modified\nversion:\n\n   a) under this License, provided that you make a good faith effort to\n   ensure that, in the event an Application does not supply the\n   function or data, the facility still operates, and performs\n   whatever part of its purpose remains meaningful, or\n\n   b) under the GNU GPL, with none of the additional permissions of\n   this License applicable to that copy.\n\n  3. Object Code Incorporating Material from Library Header Files.\n\n  The object code form of an Application may incorporate material from\na header file that is part of the Library.  You may convey such object\ncode under terms of your choice, provided that, if the incorporated\nmaterial is not limited to numerical parameters, data structure\nlayouts and accessors, or small macros, inline functions and templates\n(ten or fewer lines in length), you do both of the following:\n\n   a) Give prominent notice with each copy of the object code that the\n   Library is used in it and that the Library and its use are\n   covered by this License.\n\n   b) Accompany the object code with a copy of the GNU GPL and this license\n   document.\n\n  4. Combined Works.\n\n  You may convey a Combined Work under terms of your choice that,\ntaken together, effectively do not restrict modification of the\nportions of the Library contained in the Combined Work and reverse\nengineering for debugging such modifications, if you also do each of\nthe following:\n\n   a) Give prominent notice with each copy of the Combined Work that\n   the Library is used in it and that the Library and its use are\n   covered by this License.\n\n   b) Accompany the Combined Work with a copy of the GNU GPL and this license\n   document.\n\n   c) For a Combined Work that displays copyright notices during\n   execution, include the copyright notice for the Library among\n   these notices, as well as a reference directing the user to the\n   copies of the GNU GPL and this license document.\n\n   d) Do one of the following:\n\n       0) Convey the Minimal Corresponding Source under the terms of this\n       License, and the Corresponding Application Code in a form\n       suitable for, and under terms that permit, the user to\n       recombine or relink the Application with a modified version of\n       the Linked Version to produce a modified Combined Work, in the\n       manner specified by section 6 of the GNU GPL for conveying\n       Corresponding Source.\n\n       1) Use a suitable shared library mechanism for linking with the\n       Library.  A suitable mechanism is one that (a) uses at run time\n       a copy of the Library already present on the user's computer\n       system, and (b) will operate properly with a modified version\n       of the Library that is interface-compatible with the Linked\n       Version.\n\n   e) Provide Installation Information, but only if you would otherwise\n   be required to provide such information under section 6 of the\n   GNU GPL, and only to the extent that such information is\n   necessary to install and execute a modified version of the\n   Combined Work produced by recombining or relinking the\n   Application with a modified version of the Linked Version. (If\n   you use option 4d0, the Installation Information must accompany\n   the Minimal Corresponding Source and Corresponding Application\n   Code. If you use option 4d1, you must provide the Installation\n   Information in the manner specified by section 6 of the GNU GPL\n   for conveying Corresponding Source.)\n\n  5. Combined Libraries.\n\n  You may place library facilities that are a work based on the\nLibrary side by side in a single library together with other library\nfacilities that are not Applications and are not covered by this\nLicense, and convey such a combined library under terms of your\nchoice, if you do both of the following:\n\n   a) Accompany the combined library with a copy of the same work based\n   on the Library, uncombined with any other library facilities,\n   conveyed under the terms of this License.\n\n   b) Give prominent notice with the combined library that part of it\n   is a work based on the Library, and explaining where to find the\n   accompanying uncombined form of the same work.\n\n  6. Revised Versions of the GNU Lesser General Public License.\n\n  The Free Software Foundation may publish revised and/or new versions\nof the GNU Lesser General Public License from time to time. Such new\nversions will be similar in spirit to the present version, but may\ndiffer in detail to address new problems or concerns.\n\n  Each version is given a distinguishing version number. If the\nLibrary as you received it specifies that a certain numbered version\nof the GNU Lesser General Public License \"or any later version\"\napplies to it, you have the option of following the terms and\nconditions either of that published version or of any later version\npublished by the Free Software Foundation. If the Library as you\nreceived it does not specify a version number of the GNU Lesser\nGeneral Public License, you may choose any version of the GNU Lesser\nGeneral Public License ever published by the Free Software Foundation.\n\n  If the Library as you received it specifies that a proxy can decide\nwhether future versions of the GNU Lesser General Public License shall\napply, that proxy's public statement of acceptance of any version is\npermanent authorization for you to choose that version for the\nLibrary.\n"
  },
  {
    "path": "COPYING.txt",
    "content": "                    GNU GENERAL PUBLIC LICENSE\n                       Version 3, 29 June 2007\n\n Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>\n Everyone is permitted to copy and distribute verbatim copies\n of this license document, but changing it is not allowed.\n\n                            Preamble\n\n  The GNU General Public License is a free, copyleft license for\nsoftware and other kinds of works.\n\n  The licenses for most software and other practical works are designed\nto take away your freedom to share and change the works.  By contrast,\nthe GNU General Public License is intended to guarantee your freedom to\nshare and change all versions of a program--to make sure it remains free\nsoftware for all its users.  We, the Free Software Foundation, use the\nGNU General Public License for most of our software; it applies also to\nany other work released this way by its authors.  You can apply it to\nyour programs, too.\n\n  When we speak of free software, we are referring to freedom, not\nprice.  Our General Public Licenses are designed to make sure that you\nhave the freedom to distribute copies of free software (and charge for\nthem if you wish), that you receive source code or can get it if you\nwant it, that you can change the software or use pieces of it in new\nfree programs, and that you know you can do these things.\n\n  To protect your rights, we need to prevent others from denying you\nthese rights or asking you to surrender the rights.  Therefore, you have\ncertain responsibilities if you distribute copies of the software, or if\nyou modify it: responsibilities to respect the freedom of others.\n\n  For example, if you distribute copies of such a program, whether\ngratis or for a fee, you must pass on to the recipients the same\nfreedoms that you received.  You must make sure that they, too, receive\nor can get the source code.  And you must show them these terms so they\nknow their rights.\n\n  Developers that use the GNU GPL protect your rights with two steps:\n(1) assert copyright on the software, and (2) offer you this License\ngiving you legal permission to copy, distribute and/or modify it.\n\n  For the developers' and authors' protection, the GPL clearly explains\nthat there is no warranty for this free software.  For both users' and\nauthors' sake, the GPL requires that modified versions be marked as\nchanged, so that their problems will not be attributed erroneously to\nauthors of previous versions.\n\n  Some devices are designed to deny users access to install or run\nmodified versions of the software inside them, although the manufacturer\ncan do so.  This is fundamentally incompatible with the aim of\nprotecting users' freedom to change the software.  The systematic\npattern of such abuse occurs in the area of products for individuals to\nuse, which is precisely where it is most unacceptable.  Therefore, we\nhave designed this version of the GPL to prohibit the practice for those\nproducts.  If such problems arise substantially in other domains, we\nstand ready to extend this provision to those domains in future versions\nof the GPL, as needed to protect the freedom of users.\n\n  Finally, every program is threatened constantly by software patents.\nStates should not allow patents to restrict development and use of\nsoftware on general-purpose computers, but in those that do, we wish to\navoid the special danger that patents applied to a free program could\nmake it effectively proprietary.  To prevent this, the GPL assures that\npatents cannot be used to render the program non-free.\n\n  The precise terms and conditions for copying, distribution and\nmodification follow.\n\n                       TERMS AND CONDITIONS\n\n  0. Definitions.\n\n  \"This License\" refers to version 3 of the GNU General Public License.\n\n  \"Copyright\" also means copyright-like laws that apply to other kinds of\nworks, such as semiconductor masks.\n\n  \"The Program\" refers to any copyrightable work licensed under this\nLicense.  Each licensee is addressed as \"you\".  \"Licensees\" and\n\"recipients\" may be individuals or organizations.\n\n  To \"modify\" a work means to copy from or adapt all or part of the work\nin a fashion requiring copyright permission, other than the making of an\nexact copy.  The resulting work is called a \"modified version\" of the\nearlier work or a work \"based on\" the earlier work.\n\n  A \"covered work\" means either the unmodified Program or a work based\non the Program.\n\n  To \"propagate\" a work means to do anything with it that, without\npermission, would make you directly or secondarily liable for\ninfringement under applicable copyright law, except executing it on a\ncomputer or modifying a private copy.  Propagation includes copying,\ndistribution (with or without modification), making available to the\npublic, and in some countries other activities as well.\n\n  To \"convey\" a work means any kind of propagation that enables other\nparties to make or receive copies.  Mere interaction with a user through\na computer network, with no transfer of a copy, is not conveying.\n\n  An interactive user interface displays \"Appropriate Legal Notices\"\nto the extent that it includes a convenient and prominently visible\nfeature that (1) displays an appropriate copyright notice, and (2)\ntells the user that there is no warranty for the work (except to the\nextent that warranties are provided), that licensees may convey the\nwork under this License, and how to view a copy of this License.  If\nthe interface presents a list of user commands or options, such as a\nmenu, a prominent item in the list meets this criterion.\n\n  1. Source Code.\n\n  The \"source code\" for a work means the preferred form of the work\nfor making modifications to it.  \"Object code\" means any non-source\nform of a work.\n\n  A \"Standard Interface\" means an interface that either is an official\nstandard defined by a recognized standards body, or, in the case of\ninterfaces specified for a particular programming language, one that\nis widely used among developers working in that language.\n\n  The \"System Libraries\" of an executable work include anything, other\nthan the work as a whole, that (a) is included in the normal form of\npackaging a Major Component, but which is not part of that Major\nComponent, and (b) serves only to enable use of the work with that\nMajor Component, or to implement a Standard Interface for which an\nimplementation is available to the public in source code form.  A\n\"Major Component\", in this context, means a major essential component\n(kernel, window system, and so on) of the specific operating system\n(if any) on which the executable work runs, or a compiler used to\nproduce the work, or an object code interpreter used to run it.\n\n  The \"Corresponding Source\" for a work in object code form means all\nthe source code needed to generate, install, and (for an executable\nwork) run the object code and to modify the work, including scripts to\ncontrol those activities.  However, it does not include the work's\nSystem Libraries, or general-purpose tools or generally available free\nprograms which are used unmodified in performing those activities but\nwhich are not part of the work.  For example, Corresponding Source\nincludes interface definition files associated with source files for\nthe work, and the source code for shared libraries and dynamically\nlinked subprograms that the work is specifically designed to require,\nsuch as by intimate data communication or control flow between those\nsubprograms and other parts of the work.\n\n  The Corresponding Source need not include anything that users\ncan regenerate automatically from other parts of the Corresponding\nSource.\n\n  The Corresponding Source for a work in source code form is that\nsame work.\n\n  2. Basic Permissions.\n\n  All rights granted under this License are granted for the term of\ncopyright on the Program, and are irrevocable provided the stated\nconditions are met.  This License explicitly affirms your unlimited\npermission to run the unmodified Program.  The output from running a\ncovered work is covered by this License only if the output, given its\ncontent, constitutes a covered work.  This License acknowledges your\nrights of fair use or other equivalent, as provided by copyright law.\n\n  You may make, run and propagate covered works that you do not\nconvey, without conditions so long as your license otherwise remains\nin force.  You may convey covered works to others for the sole purpose\nof having them make modifications exclusively for you, or provide you\nwith facilities for running those works, provided that you comply with\nthe terms of this License in conveying all material for which you do\nnot control copyright.  Those thus making or running the covered works\nfor you must do so exclusively on your behalf, under your direction\nand control, on terms that prohibit them from making any copies of\nyour copyrighted material outside their relationship with you.\n\n  Conveying under any other circumstances is permitted solely under\nthe conditions stated below.  Sublicensing is not allowed; section 10\nmakes it unnecessary.\n\n  3. Protecting Users' Legal Rights From Anti-Circumvention Law.\n\n  No covered work shall be deemed part of an effective technological\nmeasure under any applicable law fulfilling obligations under article\n11 of the WIPO copyright treaty adopted on 20 December 1996, or\nsimilar laws prohibiting or restricting circumvention of such\nmeasures.\n\n  When you convey a covered work, you waive any legal power to forbid\ncircumvention of technological measures to the extent such circumvention\nis effected by exercising rights under this License with respect to\nthe covered work, and you disclaim any intention to limit operation or\nmodification of the work as a means of enforcing, against the work's\nusers, your or third parties' legal rights to forbid circumvention of\ntechnological measures.\n\n  4. Conveying Verbatim Copies.\n\n  You may convey verbatim copies of the Program's source code as you\nreceive it, in any medium, provided that you conspicuously and\nappropriately publish on each copy an appropriate copyright notice;\nkeep intact all notices stating that this License and any\nnon-permissive terms added in accord with section 7 apply to the code;\nkeep intact all notices of the absence of any warranty; and give all\nrecipients a copy of this License along with the Program.\n\n  You may charge any price or no price for each copy that you convey,\nand you may offer support or warranty protection for a fee.\n\n  5. Conveying Modified Source Versions.\n\n  You may convey a work based on the Program, or the modifications to\nproduce it from the Program, in the form of source code under the\nterms of section 4, provided that you also meet all of these conditions:\n\n    a) The work must carry prominent notices stating that you modified\n    it, and giving a relevant date.\n\n    b) The work must carry prominent notices stating that it is\n    released under this License and any conditions added under section\n    7.  This requirement modifies the requirement in section 4 to\n    \"keep intact all notices\".\n\n    c) You must license the entire work, as a whole, under this\n    License to anyone who comes into possession of a copy.  This\n    License will therefore apply, along with any applicable section 7\n    additional terms, to the whole of the work, and all its parts,\n    regardless of how they are packaged.  This License gives no\n    permission to license the work in any other way, but it does not\n    invalidate such permission if you have separately received it.\n\n    d) If the work has interactive user interfaces, each must display\n    Appropriate Legal Notices; however, if the Program has interactive\n    interfaces that do not display Appropriate Legal Notices, your\n    work need not make them do so.\n\n  A compilation of a covered work with other separate and independent\nworks, which are not by their nature extensions of the covered work,\nand which are not combined with it such as to form a larger program,\nin or on a volume of a storage or distribution medium, is called an\n\"aggregate\" if the compilation and its resulting copyright are not\nused to limit the access or legal rights of the compilation's users\nbeyond what the individual works permit.  Inclusion of a covered work\nin an aggregate does not cause this License to apply to the other\nparts of the aggregate.\n\n  6. Conveying Non-Source Forms.\n\n  You may convey a covered work in object code form under the terms\nof sections 4 and 5, provided that you also convey the\nmachine-readable Corresponding Source under the terms of this License,\nin one of these ways:\n\n    a) Convey the object code in, or embodied in, a physical product\n    (including a physical distribution medium), accompanied by the\n    Corresponding Source fixed on a durable physical medium\n    customarily used for software interchange.\n\n    b) Convey the object code in, or embodied in, a physical product\n    (including a physical distribution medium), accompanied by a\n    written offer, valid for at least three years and valid for as\n    long as you offer spare parts or customer support for that product\n    model, to give anyone who possesses the object code either (1) a\n    copy of the Corresponding Source for all the software in the\n    product that is covered by this License, on a durable physical\n    medium customarily used for software interchange, for a price no\n    more than your reasonable cost of physically performing this\n    conveying of source, or (2) access to copy the\n    Corresponding Source from a network server at no charge.\n\n    c) Convey individual copies of the object code with a copy of the\n    written offer to provide the Corresponding Source.  This\n    alternative is allowed only occasionally and noncommercially, and\n    only if you received the object code with such an offer, in accord\n    with subsection 6b.\n\n    d) Convey the object code by offering access from a designated\n    place (gratis or for a charge), and offer equivalent access to the\n    Corresponding Source in the same way through the same place at no\n    further charge.  You need not require recipients to copy the\n    Corresponding Source along with the object code.  If the place to\n    copy the object code is a network server, the Corresponding Source\n    may be on a different server (operated by you or a third party)\n    that supports equivalent copying facilities, provided you maintain\n    clear directions next to the object code saying where to find the\n    Corresponding Source.  Regardless of what server hosts the\n    Corresponding Source, you remain obligated to ensure that it is\n    available for as long as needed to satisfy these requirements.\n\n    e) Convey the object code using peer-to-peer transmission, provided\n    you inform other peers where the object code and Corresponding\n    Source of the work are being offered to the general public at no\n    charge under subsection 6d.\n\n  A separable portion of the object code, whose source code is excluded\nfrom the Corresponding Source as a System Library, need not be\nincluded in conveying the object code work.\n\n  A \"User Product\" is either (1) a \"consumer product\", which means any\ntangible personal property which is normally used for personal, family,\nor household purposes, or (2) anything designed or sold for incorporation\ninto a dwelling.  In determining whether a product is a consumer product,\ndoubtful cases shall be resolved in favor of coverage.  For a particular\nproduct received by a particular user, \"normally used\" refers to a\ntypical or common use of that class of product, regardless of the status\nof the particular user or of the way in which the particular user\nactually uses, or expects or is expected to use, the product.  A product\nis a consumer product regardless of whether the product has substantial\ncommercial, industrial or non-consumer uses, unless such uses represent\nthe only significant mode of use of the product.\n\n  \"Installation Information\" for a User Product means any methods,\nprocedures, authorization keys, or other information required to install\nand execute modified versions of a covered work in that User Product from\na modified version of its Corresponding Source.  The information must\nsuffice to ensure that the continued functioning of the modified object\ncode is in no case prevented or interfered with solely because\nmodification has been made.\n\n  If you convey an object code work under this section in, or with, or\nspecifically for use in, a User Product, and the conveying occurs as\npart of a transaction in which the right of possession and use of the\nUser Product is transferred to the recipient in perpetuity or for a\nfixed term (regardless of how the transaction is characterized), the\nCorresponding Source conveyed under this section must be accompanied\nby the Installation Information.  But this requirement does not apply\nif neither you nor any third party retains the ability to install\nmodified object code on the User Product (for example, the work has\nbeen installed in ROM).\n\n  The requirement to provide Installation Information does not include a\nrequirement to continue to provide support service, warranty, or updates\nfor a work that has been modified or installed by the recipient, or for\nthe User Product in which it has been modified or installed.  Access to a\nnetwork may be denied when the modification itself materially and\nadversely affects the operation of the network or violates the rules and\nprotocols for communication across the network.\n\n  Corresponding Source conveyed, and Installation Information provided,\nin accord with this section must be in a format that is publicly\ndocumented (and with an implementation available to the public in\nsource code form), and must require no special password or key for\nunpacking, reading or copying.\n\n  7. Additional Terms.\n\n  \"Additional permissions\" are terms that supplement the terms of this\nLicense by making exceptions from one or more of its conditions.\nAdditional permissions that are applicable to the entire Program shall\nbe treated as though they were included in this License, to the extent\nthat they are valid under applicable law.  If additional permissions\napply only to part of the Program, that part may be used separately\nunder those permissions, but the entire Program remains governed by\nthis License without regard to the additional permissions.\n\n  When you convey a copy of a covered work, you may at your option\nremove any additional permissions from that copy, or from any part of\nit.  (Additional permissions may be written to require their own\nremoval in certain cases when you modify the work.)  You may place\nadditional permissions on material, added by you to a covered work,\nfor which you have or can give appropriate copyright permission.\n\n  Notwithstanding any other provision of this License, for material you\nadd to a covered work, you may (if authorized by the copyright holders of\nthat material) supplement the terms of this License with terms:\n\n    a) Disclaiming warranty or limiting liability differently from the\n    terms of sections 15 and 16 of this License; or\n\n    b) Requiring preservation of specified reasonable legal notices or\n    author attributions in that material or in the Appropriate Legal\n    Notices displayed by works containing it; or\n\n    c) Prohibiting misrepresentation of the origin of that material, or\n    requiring that modified versions of such material be marked in\n    reasonable ways as different from the original version; or\n\n    d) Limiting the use for publicity purposes of names of licensors or\n    authors of the material; or\n\n    e) Declining to grant rights under trademark law for use of some\n    trade names, trademarks, or service marks; or\n\n    f) Requiring indemnification of licensors and authors of that\n    material by anyone who conveys the material (or modified versions of\n    it) with contractual assumptions of liability to the recipient, for\n    any liability that these contractual assumptions directly impose on\n    those licensors and authors.\n\n  All other non-permissive additional terms are considered \"further\nrestrictions\" within the meaning of section 10.  If the Program as you\nreceived it, or any part of it, contains a notice stating that it is\ngoverned by this License along with a term that is a further\nrestriction, you may remove that term.  If a license document contains\na further restriction but permits relicensing or conveying under this\nLicense, you may add to a covered work material governed by the terms\nof that license document, provided that the further restriction does\nnot survive such relicensing or conveying.\n\n  If you add terms to a covered work in accord with this section, you\nmust place, in the relevant source files, a statement of the\nadditional terms that apply to those files, or a notice indicating\nwhere to find the applicable terms.\n\n  Additional terms, permissive or non-permissive, may be stated in the\nform of a separately written license, or stated as exceptions;\nthe above requirements apply either way.\n\n  8. Termination.\n\n  You may not propagate or modify a covered work except as expressly\nprovided under this License.  Any attempt otherwise to propagate or\nmodify it is void, and will automatically terminate your rights under\nthis License (including any patent licenses granted under the third\nparagraph of section 11).\n\n  However, if you cease all violation of this License, then your\nlicense from a particular copyright holder is reinstated (a)\nprovisionally, unless and until the copyright holder explicitly and\nfinally terminates your license, and (b) permanently, if the copyright\nholder fails to notify you of the violation by some reasonable means\nprior to 60 days after the cessation.\n\n  Moreover, your license from a particular copyright holder is\nreinstated permanently if the copyright holder notifies you of the\nviolation by some reasonable means, this is the first time you have\nreceived notice of violation of this License (for any work) from that\ncopyright holder, and you cure the violation prior to 30 days after\nyour receipt of the notice.\n\n  Termination of your rights under this section does not terminate the\nlicenses of parties who have received copies or rights from you under\nthis License.  If your rights have been terminated and not permanently\nreinstated, you do not qualify to receive new licenses for the same\nmaterial under section 10.\n\n  9. Acceptance Not Required for Having Copies.\n\n  You are not required to accept this License in order to receive or\nrun a copy of the Program.  Ancillary propagation of a covered work\noccurring solely as a consequence of using peer-to-peer transmission\nto receive a copy likewise does not require acceptance.  However,\nnothing other than this License grants you permission to propagate or\nmodify any covered work.  These actions infringe copyright if you do\nnot accept this License.  Therefore, by modifying or propagating a\ncovered work, you indicate your acceptance of this License to do so.\n\n  10. Automatic Licensing of Downstream Recipients.\n\n  Each time you convey a covered work, the recipient automatically\nreceives a license from the original licensors, to run, modify and\npropagate that work, subject to this License.  You are not responsible\nfor enforcing compliance by third parties with this License.\n\n  An \"entity transaction\" is a transaction transferring control of an\norganization, or substantially all assets of one, or subdividing an\norganization, or merging organizations.  If propagation of a covered\nwork results from an entity transaction, each party to that\ntransaction who receives a copy of the work also receives whatever\nlicenses to the work the party's predecessor in interest had or could\ngive under the previous paragraph, plus a right to possession of the\nCorresponding Source of the work from the predecessor in interest, if\nthe predecessor has it or can get it with reasonable efforts.\n\n  You may not impose any further restrictions on the exercise of the\nrights granted or affirmed under this License.  For example, you may\nnot impose a license fee, royalty, or other charge for exercise of\nrights granted under this License, and you may not initiate litigation\n(including a cross-claim or counterclaim in a lawsuit) alleging that\nany patent claim is infringed by making, using, selling, offering for\nsale, or importing the Program or any portion of it.\n\n  11. Patents.\n\n  A \"contributor\" is a copyright holder who authorizes use under this\nLicense of the Program or a work on which the Program is based.  The\nwork thus licensed is called the contributor's \"contributor version\".\n\n  A contributor's \"essential patent claims\" are all patent claims\nowned or controlled by the contributor, whether already acquired or\nhereafter acquired, that would be infringed by some manner, permitted\nby this License, of making, using, or selling its contributor version,\nbut do not include claims that would be infringed only as a\nconsequence of further modification of the contributor version.  For\npurposes of this definition, \"control\" includes the right to grant\npatent sublicenses in a manner consistent with the requirements of\nthis License.\n\n  Each contributor grants you a non-exclusive, worldwide, royalty-free\npatent license under the contributor's essential patent claims, to\nmake, use, sell, offer for sale, import and otherwise run, modify and\npropagate the contents of its contributor version.\n\n  In the following three paragraphs, a \"patent license\" is any express\nagreement or commitment, however denominated, not to enforce a patent\n(such as an express permission to practice a patent or covenant not to\nsue for patent infringement).  To \"grant\" such a patent license to a\nparty means to make such an agreement or commitment not to enforce a\npatent against the party.\n\n  If you convey a covered work, knowingly relying on a patent license,\nand the Corresponding Source of the work is not available for anyone\nto copy, free of charge and under the terms of this License, through a\npublicly available network server or other readily accessible means,\nthen you must either (1) cause the Corresponding Source to be so\navailable, or (2) arrange to deprive yourself of the benefit of the\npatent license for this particular work, or (3) arrange, in a manner\nconsistent with the requirements of this License, to extend the patent\nlicense to downstream recipients.  \"Knowingly relying\" means you have\nactual knowledge that, but for the patent license, your conveying the\ncovered work in a country, or your recipient's use of the covered work\nin a country, would infringe one or more identifiable patents in that\ncountry that you have reason to believe are valid.\n\n  If, pursuant to or in connection with a single transaction or\narrangement, you convey, or propagate by procuring conveyance of, a\ncovered work, and grant a patent license to some of the parties\nreceiving the covered work authorizing them to use, propagate, modify\nor convey a specific copy of the covered work, then the patent license\nyou grant is automatically extended to all recipients of the covered\nwork and works based on it.\n\n  A patent license is \"discriminatory\" if it does not include within\nthe scope of its coverage, prohibits the exercise of, or is\nconditioned on the non-exercise of one or more of the rights that are\nspecifically granted under this License.  You may not convey a covered\nwork if you are a party to an arrangement with a third party that is\nin the business of distributing software, under which you make payment\nto the third party based on the extent of your activity of conveying\nthe work, and under which the third party grants, to any of the\nparties who would receive the covered work from you, a discriminatory\npatent license (a) in connection with copies of the covered work\nconveyed by you (or copies made from those copies), or (b) primarily\nfor and in connection with specific products or compilations that\ncontain the covered work, unless you entered into that arrangement,\nor that patent license was granted, prior to 28 March 2007.\n\n  Nothing in this License shall be construed as excluding or limiting\nany implied license or other defenses to infringement that may\notherwise be available to you under applicable patent law.\n\n  12. No Surrender of Others' Freedom.\n\n  If conditions are imposed on you (whether by court order, agreement or\notherwise) that contradict the conditions of this License, they do not\nexcuse you from the conditions of this License.  If you cannot convey a\ncovered work so as to satisfy simultaneously your obligations under this\nLicense and any other pertinent obligations, then as a consequence you may\nnot convey it at all.  For example, if you agree to terms that obligate you\nto collect a royalty for further conveying from those to whom you convey\nthe Program, the only way you could satisfy both those terms and this\nLicense would be to refrain entirely from conveying the Program.\n\n  13. Use with the GNU Affero General Public License.\n\n  Notwithstanding any other provision of this License, you have\npermission to link or combine any covered work with a work licensed\nunder version 3 of the GNU Affero General Public License into a single\ncombined work, and to convey the resulting work.  The terms of this\nLicense will continue to apply to the part which is the covered work,\nbut the special requirements of the GNU Affero General Public License,\nsection 13, concerning interaction through a network will apply to the\ncombination as such.\n\n  14. Revised Versions of this License.\n\n  The Free Software Foundation may publish revised and/or new versions of\nthe GNU General Public License from time to time.  Such new versions will\nbe similar in spirit to the present version, but may differ in detail to\naddress new problems or concerns.\n\n  Each version is given a distinguishing version number.  If the\nProgram specifies that a certain numbered version of the GNU General\nPublic License \"or any later version\" applies to it, you have the\noption of following the terms and conditions either of that numbered\nversion or of any later version published by the Free Software\nFoundation.  If the Program does not specify a version number of the\nGNU General Public License, you may choose any version ever published\nby the Free Software Foundation.\n\n  If the Program specifies that a proxy can decide which future\nversions of the GNU General Public License can be used, that proxy's\npublic statement of acceptance of a version permanently authorizes you\nto choose that version for the Program.\n\n  Later license versions may give you additional or different\npermissions.  However, no additional obligations are imposed on any\nauthor or copyright holder as a result of your choosing to follow a\nlater version.\n\n  15. Disclaimer of Warranty.\n\n  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY\nAPPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT\nHOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM \"AS IS\" WITHOUT WARRANTY\nOF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,\nTHE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\nPURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM\nIS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF\nALL NECESSARY SERVICING, REPAIR OR CORRECTION.\n\n  16. Limitation of Liability.\n\n  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING\nWILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS\nTHE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY\nGENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE\nUSE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF\nDATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD\nPARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),\nEVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF\nSUCH DAMAGES.\n\n  17. Interpretation of Sections 15 and 16.\n\n  If the disclaimer of warranty and limitation of liability provided\nabove cannot be given local legal effect according to their terms,\nreviewing courts shall apply local law that most closely approximates\nan absolute waiver of all civil liability in connection with the\nProgram, unless a warranty or assumption of liability accompanies a\ncopy of the Program in return for a fee.\n\n                     END OF TERMS AND CONDITIONS\n\n            How to Apply These Terms to Your New Programs\n\n  If you develop a new program, and you want it to be of the greatest\npossible use to the public, the best way to achieve this is to make it\nfree software which everyone can redistribute and change under these terms.\n\n  To do so, attach the following notices to the program.  It is safest\nto attach them to the start of each source file to most effectively\nstate the exclusion of warranty; and each file should have at least\nthe \"copyright\" line and a pointer to where the full notice is found.\n\n    <one line to give the program's name and a brief idea of what it does.>\n    Copyright (C) <year>  <name of author>\n\n    This program is free software: you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation, either version 3 of the License, or\n    (at your option) any later version.\n\n    This program is distributed in the hope that it will be useful,\n    but WITHOUT ANY WARRANTY; without even the implied warranty of\n    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n    GNU General Public License for more details.\n\n    You should have received a copy of the GNU General Public License\n    along with this program.  If not, see <https://www.gnu.org/licenses/>.\n\nAlso add information on how to contact you by electronic and paper mail.\n\n  If the program does terminal interaction, make it output a short\nnotice like this when it starts in an interactive mode:\n\n    <program>  Copyright (C) <year>  <name of author>\n    This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.\n    This is free software, and you are welcome to redistribute it\n    under certain conditions; type `show c' for details.\n\nThe hypothetical commands `show w' and `show c' should show the appropriate\nparts of the General Public License.  Of course, your program's commands\nmight be different; for a GUI interface, you would use an \"about box\".\n\n  You should also get your employer (if you work as a programmer) or school,\nif any, to sign a \"copyright disclaimer\" for the program, if necessary.\nFor more information on this, and how to apply and follow the GNU GPL, see\n<https://www.gnu.org/licenses/>.\n\n  The GNU General Public License does not permit incorporating your program\ninto proprietary programs.  If your program is a subroutine library, you\nmay consider it more useful to permit linking proprietary applications with\nthe library.  If this is what you want to do, use the GNU Lesser General\nPublic License instead of this License.  But first, please read\n<https://www.gnu.org/licenses/why-not-lgpl.html>.\n"
  },
  {
    "path": "DEPENDS.txt",
    "content": "luastatus itself, without any barlibs and/or plugins, has the following dependencies:\n* docutils >=0.11 (rst2man program)\n* CMake >=3.1.3\n* pkg-config >=0.25\n* either Lua 5.1, Lua 5.2, Lua 5.3, Lua 5.4, or LuaJIT >=2.0.0\n\nFor running tests, you will also need:\n* valgrind (some older versions don't like LuaJIT; >=3.11.0 are known to be OK)\n* For 'i3' barlib tests: 'jq' binary\n* For 'dbus' plugin tests: a D-Bus daemon running in session mode; 'dbus-send' binary\n* For 'pulse' plugin tests: a running PulseAudio daemon; 'pactl' and 'pacmd' binaries\n* For 'web' plugin tests: 'libwebsockets' library (to build 'httpserv' program)\n\nBarlib 'dwm' has the following dependencies:\n* xcb >=1.10\n\nBarlib 'i3' has the following dependencies:\n* yajl >=2.0.4\n\nPlugin 'alsa' has the following dependencies:\n* alsa >=1.0.27.2\n\nPlugin 'backlight-linux' has the following dependencies:\n* plugin 'udev'\n\nPlugin 'battery-linux' has the following dependencies:\n* plugin 'udev'\n\nPlugin 'cpu-freq-linux' has the following dependencies:\n* plugin 'timer'\n\nPlugin 'cpu-usage-linux' has the following dependencies:\n* plugin 'timer'\n\nPlugin 'dbus' has the following dependencies:\n* glib-2.0 >=2.40.2\n* gio-2.0 >=2.40.2\n\nPlugin 'disk-io-linux' has the following dependencies:\n* plugin 'timer'\n\nPlugin 'file-contents-linux' has the following dependencies:\n* plugin 'inotify'\n\nPlugin 'imap' has the following dependencies:\n* LuaSocket library for the Lua version that luastatus was built with\n* LuaSec library for the Lua version that luastatus was built with\n\nPlugin 'is-program-running' has the following dependencies:\n* plugin 'timer'\n\nPlugin 'inotify' has the following dependencies:\n* a Linux system with a libc that provides <sys/inotify.h> (preferably glibc)\n\nPlugin 'mem-usage-linux' has the following dependencies:\n* plugin 'timer'\n\nPlugin 'mpris' has the following dependencies:\n* plugin 'dbus'\n\nPlugin 'network-linux' has the following dependencies:\n* a Linux system with Linux kernel headers\n* libnl >=3.0\n* libnl-genl >=3.0\n\nPlugin 'network-rate-linux' has the following dependencies:\n* plugin 'timer'\n\nPlugin 'pipe' has the following dependencies:\n* plugin 'timer'\n\nPlugin 'pulse' has the following dependencies:\n* libpulse >=4.0\n\nPlugin 'systemd-unit' has the following dependencies:\n* plugin 'dbus'\n\nPlugin 'temperature-linux' has the following dependencies:\n* plugin 'timer'\n\nPlugin 'udev' has the following dependencies:\n* libudev >=204\n\nPlugin 'web' has the following dependencies:\n* cJSON >=1.7.10\n* libcurl >=7.8\n\nPlugin 'xkb' has the following dependencies:\n* x11 >=1.6.2\n\nPlugin 'xtitle' has the following dependencies:\n* xcb >=1.10\n* xcb-ewmh >=0.4.1\n* xcb-icccm >=0.4.1\n* xcb-event >=0.3.8\n"
  },
  {
    "path": "DOCS/CUSTOM_DATA_SRC_WIDGET.md",
    "content": "Writing a widget that uses custom data source\n===\n\nA “custom data source” widget is a fancy way to refer to a widget that wants to wait for events\nand/or receive data using some blocking Lua operation (most commonly using some external Lua\nmodule), rather than using some luastatus plugin.\n\nThe luastatus project explicitly supports this flow; in fact, the “separate state” thing is there\nexactly to help such widgets react to events timely.\n\nThe basic mechanism is (details may vary for different widgets):\n\n  * Specify `timer` as a plugin, with `period = 0` in plugin options (`opts`).\n\n  * Do the following in the callback function (`cb`):\n\n    + wait for next event (unless this is the first time `cb` is called);\n\n    + generate data for barlib and return it.\n\n  * If need to handle events, use separate-state event handler.\n\nTricks to know about\n---\n\n* Use `luastatus.plugin.push_period(seconds)` function of the `timer` plugin to force it to sleep\nfor a specified number of seconds after barlib receives your data. Note that this is a\n“*push* timeout” operation, not “*set* timeout”, meaning that it will only be respected for a single\niteration, and then “popped” (forgotten).\n\n* Use the argument to `cb` provided by the `timer` plugin to tell if this is the first time `cb`\nis called: the argument is a string, and it is `\"hello\"` if and only if this is the first time.\n\n* The `timer` plugin supports wakeup FIFO. This might be useful if `push_period` is used in case of\nerror, as a way to force an earlier re-try. The FIFO may be touched from inside the event handler,\nor from anywhere else.\n\nList of derived plugins using custom data sources\n---\n\n* imap;\n\n* pipe.\n\nList of widget examples using custom data sources\n---\n\n* btc-price;\n\n* gmail;\n\n* update-on-click;\n\n* weather.\n"
  },
  {
    "path": "DOCS/MIGRATION_GUIDE.md",
    "content": "0.2.0 to 0.3.0\n===\n* Semantics of the `greet` option of `inotify` plugin has changed;\n  see `plugins/inotify/README.md` for details.\n\n0.1.0 to 0.2.0\n===\n* `luastatus.spawn`, `luastatus.rc` and `luastatus.dollar` functions have been removed.\n   Use `os.execute` and/or `io.popen` instead — and please don’t forget to properly escape the arguments:\n    ````lua\n    function shell_escape(s)\n        return \"'\" .. s:gsub(\"'\", \"'\\\\''\") .. \"'\"\n    end\n    ````\n\n* `pipe` plugin has been removed. Use the `timer` plugin and `io.open` instead:\n    ````lua\n    f = io.popen('your command', 'r')\n    wdiget = {\n        plugin = 'timer',\n        cb = function()\n            local line = f:read('*line')\n            -- ...\n        end,\n    }\n    ````\n"
  },
  {
    "path": "DOCS/WRITING_BARLIB_OR_PLUGIN.md",
    "content": "Thread-safety\n===\nEach non-thread-safe thing must be synchronized with other entities by means of the `map_get`\nfunction (see `DOCS/design/map_get.md`).\n\nEnvironment variables\n===\nYour plugin or barlib must not modify environment variables (this includes `unsetenv()`, `setenv()`,\n`putenv()`, and modifying `environ`).\n\nSignals\n===\nYour plugin or barlib can install signal handlers, but:\n\n  1. this must be done with `sigaction()`, and `sa_mask` field of `struct sigaction` must include\n     `SA_RESTART`;\n\n  2. use the `map_get` facility to ensure there are no conflicting handlers installed; use, for\n     example, the key of `\"flag:signal_handled:SIGUSR1\"` for signal `SIGUSR1`; the value behind the\n     key should be checked to be null and then set to some non-null pointer. For POSIX real-time\n     signals, use `\"SIGRTMIN+%d\"` nomenclature, where `%d` is a non-negative offset in decimal.\n\nYour plugin or barlib can call `pthread_sigmask()` (and, consequently, `pselect()`).\n\nWriting a plugin\n===\nCopy `include/plugin_data.h`, `include/plugin_data_v1.h`, `include/plugin_v1.h` and `include/common.h`;\ninclude `include/plugin_v1.h` and start reading `include/plugin_data.h`.\n\nThen, declare a global `const LuastatusIfacePlugin luastatus_iface_plugin_v1` variable.\n\nWriting a barlib\n===\nCopy `include/barlib_data.h`, `include/barlib_data_v1.h`, `include/barlib_v1.h` and `include/common.h`;\ninclude `include/barlib_v1.h` and start reading `include/barlib_data.h`.\n\nThen, declare a global `const LuastatusIfaceBarlib luastatus_iface_barlib_v1` variable.\n"
  },
  {
    "path": "DOCS/c_notes/eintr-policy.md",
    "content": "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`.\n\nOur solution is to ensure that `SA_RESTART` is set for all the signals that could be raised (without terminating the program).\n\nSee also: http://man7.org/linux/man-pages/man7/signal.7.html, section \"Interruption of system calls and library functions by signal handlers\".\n"
  },
  {
    "path": "DOCS/c_notes/empty-ranges-and-c-stdlib.md",
    "content": "http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1256.pdf\n\nSubclause 7.1.4 Use of library functions, point 1:\n\n> Each of the following statements applies unless explicitly stated otherwise in the\n> detailed descriptions that follow: If an argument to a function has an invalid value (such\n> as a value outside the domain of the function, or a pointer outside the address space of\n> the program, or a null pointer, or a pointer to non-modifiable storage when the\n> corresponding parameter is not const-qualified) or a type (after promotion) not expected\n> by a function with variable number of arguments, the behavior is undefined. If a function\n> argument is described as being an array, the pointer actually passed to the function shall\n> have a value such that all address computations and accesses to objects (that would be\n> valid if the pointer did point to the first element of such an array) are in fact valid.\n\nIt’s not OK to call any function defined in `<string.h>` with a pointer that can’t be dereferenced:\n\nSubclause 7.21.1 String handling conventions, point 2:\n\n> Where an argument declared as `size_t n` specifies the length of the array for a function, `n`\n> can have the value zero on a call to that function. Unless explicitly stated otherwise in\n> the description of a particular function in this subclause, pointer arguments on such a\n> call shall still have valid values, as described in 7.1.4. On such a call, a function\n> that locates a character finds no occurrence, a function that compares two character\n> sequences returns zero, and a function that copies characters copies zero characters.\n\nIt’s not OK to call `qsort`/`bsearch` with a pointer that can’t be dereferenced:\n\nSubclause 7.20.5 Searching and sorting utilites, point 1:\n\n> These utilities make use of a comparison function to search or sort arrays of unspecified\n> type. Where an argument declared as `size_t nmemb` specifies the length of the array for a\n> function, `nmemb` can have the value zero on a call to that function; the comparison function\n> is not called, a search finds no matching element, and sorting performs no rearrangement.\n> Pointer arguments on such a call shall still have valid values, as described in 7.1.4.\n\nHowever, it’s OK to call `snprintf`/`vsnprintf` with a pointer that can’t be dereferenced, as long as `n` is zero:\n\nSubclause 7.19.6.5 The `snprintf` function, point 2:\n\n> The `snprintf` function is equivalent to `fprintf`, except that the output is written into an\n> array (specified by argument `s`) rather than to a stream. If `n` is zero, nothing is written, and\n> `s` may be a null pointer. Otherwise, output characters beyond the `n-1`st are discarded rather\n> than being written to the array, and a null character is written at the end of the characters\n> actually written into the array. If copying takes place between objects that overlap, the behavior\n> is undefined.\n"
  },
  {
    "path": "DOCS/design/locking-patterns.md",
    "content": "The list of locking patterns of the main luastatus binary; run\n\n    grep -Ew '(UN)?LOCK_[A-Z]' luastatus/luastatus.c\n\nto verify it is up-to-date.\n\nIt can be said that our order is: E < L < B.\nWe also don't lock the same mutex twice in any of the “procedures”.\nThis suffices to say there are no deadlocks.\n\n(We also have the `tests/torture.sh` test!)\n\n    cb-gets-called() {\n        lock L\n        lock B\n        unlock B\n        unlock L\n    }\n\n    plugin-begins-call-and-cancels() {\n        lock L\n        unlock L\n    }\n\n    #-----------------------------------------------\n\n    event-gets-called-and-raises-error-E() {\n        lock E\n        lock B\n        unlock B\n        unlock E\n    }\n\n    events-gets-called-and-succeeds-E() {\n        lock E\n        unlock E\n    }\n\n    barlib-ew-begins-call-and-cancels-E() {\n        lock E\n        unlock E\n    }\n\n    #-----------------------------------------------\n\n    event-gets-called-and-raises-error-L() {\n        lock L\n        lock B\n        unlock B\n        unlock L\n    }\n\n    events-gets-called-and-succeeds-L() {\n        lock L\n        unlock L\n    }\n\n    barlib-ew-begins-call-and-cancels-L() {\n        lock L\n        unlock L\n    }\n\n    #-----------------------------------------------\n\n    set-error-when-plugin-run-returned() {\n        lock B\n        unlock B\n    }\n\n    set-error-when-widget-init-failed() {\n        lock B\n        unlock B\n    }\n"
  },
  {
    "path": "DOCS/design/map_get.md",
    "content": "Overview\n===\nluastatus provides the following function to barlibs and plugins:\n\n```c\nvoid ** (*map_get)(void *userdata, const char *key);\n```\n\nThis function is **not** thread-safe and should only be used in the `init` function.\n\nluastatus maintains a global mapping from zero-terminated strings to pointers (`void *`).\n`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.\n\nYou 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.\n\nIts intended use is for synchronization.\n"
  },
  {
    "path": "README.md",
    "content": "[![CircleCI build status](https://circleci.com/gh/shdown/luastatus.svg?style=shield)](https://circleci.com/gh/shdown/luastatus)\n![Since 2016](https://img.shields.io/badge/Since-2016-lightblue)\n\n**luastatus** is a universal status bar content generator. It allows you to configure the way the\ndata from event sources is processed and shown, with Lua.\n\nIts main feature is that the content can be updated immediately as some event occurs, be it a change\nof keyboard layout, active window title, volume or a song in your favorite music player (provided\nthat there is a plugin for it) — a thing rather uncommon for tiling window managers.\n\nIts motto is:\n\n> No more heavy-forking, second-lagging shell-script status bar generators!\n\nScreenshot\n===\n\n![Screenshot](https://user-images.githubusercontent.com/5462697/39099519-092459aa-4685-11e8-94fe-0ac1cf706d82.gif)\n\nAbove is i3bar with luastatus with Bitcoin price, time, volume, and keyboard layout widgets.\n\nKey concepts\n===\n\n![Explanation](https://user-images.githubusercontent.com/5462697/42400208-5b54f5f2-8179-11e8-9836-70d4e46d5c13.png)\n\nIn short:\n  * plugin is a thing that decides when to call the callback function `widget.cb` and what to pass to it;\n  * barlib (**bar** **lib**rary) is a thing that decides what to with values that `widget.cb` function returns;\n  * there are also *derived plugins*, which are plugins written in Lua that use regular plugins.\n\nExamples\n===\nALSA volume widget:\n\n```lua\nwidget = {\n    plugin = 'alsa',\n    opts = {\n        channel = 'PCM'\n    },\n    cb = function(t)\n        if t.mute then\n            return {full_text = '[mute]', color = '#e03838'}\n        else\n            local percent = (t.vol.cur - t.vol.min)\n                          / (t.vol.max - t.vol.min)\n                          * 100\n            return {full_text = string.format('[%3d%%]', math.floor(0.5 + percent)),\n                    color = '#718ba6'}\n        end\n    end,\n    event = function(t)\n        if t.button == 1 then     -- left mouse button\n            os.execute('urxvt -e alsamixer &')\n        end\n    end\n}\n```\n\nGMail widget (uses the derived plugin `imap`):\n\n```lua\n--[[\n-- Expects 'credentials.lua' to be present in the current directory; it may contain, e.g.,\n--     return {\n--         gmail = {\n--             login = 'john.smith',\n--             password = 'qwerty'\n--         }\n--     }\n--]]\ncredentials = require 'credentials'\nwidget = luastatus.require_plugin('imap').widget{\n    host = 'imap.gmail.com',\n    port = 993,\n    mailbox = 'Inbox',\n    use_ssl = true,\n    timeout = 2 * 60,\n    handshake_timeout = 10,\n    login = credentials.gmail.login,\n    password = credentials.gmail.password,\n    error_sleep_period = 60,\n    cb = function(unseen)\n        if unseen == nil then\n            return nil\n        elseif unseen == 0 then\n            return {full_text = '[-]', color = '#595959'}\n        else\n            return {full_text = string.format('[%d unseen]', unseen)}\n        end\n    end,\n    event = [[                    -- separate-state event function\n        local t = ...             -- obtain argument of this implicit function\n        if t.button == 1 then     -- left mouse button\n            os.execute('xdg-open https://gmail.com &')\n        end\n    ]]\n}\n```\n\nSee more examples [here](https://github.com/shdown/luastatus/tree/master/examples).\n\nInstallation\n===\n`cmake . && make && sudo make install`\n\nYou can specify a Lua library to build with: `cmake -DWITH_LUA_LIBRARY=luajit .`\n\nYou can disable building certain barlibs and plugins, e.g. `cmake -DBUILD_PLUGIN_XTITLE=OFF .`\n\nYou can disable building man pages: `cmake -DBUILD_DOCS=OFF .`\n\nArchLinux\n---\nArchLinux users can use one of the following AUR packages:\n\n* [luastatus](https://aur.archlinux.org/packages/luastatus)\n* [luastatus-luajit](https://aur.archlinux.org/packages/luastatus-luajit)\n* [luastatus-git](https://aur.archlinux.org/packages/luastatus-git)\n* [luastatus-luajit-git](https://aur.archlinux.org/packages/luastatus-luajit-git)\n\nThere is also the [luastatus-meta](https://aur.archlinux.org/packages/luastatus-meta)\nmeta package which installs the dependencies of all of luastatus's plugins.\n\nGetting started\n===\nIt is recommended to first have a look at the\n[luastatus' man page](https://github.com/shdown/luastatus/blob/master/luastatus/README.rst).\n\nThen, read the barlib's and plugins' documentation, either via directly viewing\n`barlibs/<name>/README.rst` and `plugins/<name>/README.rst` files, or via installing the man pages\nand reading `luastatus-barlib-<name>(7)` and `luastatus-plugin-<name>(7)`.\n\nBarlib-specific notes on usage follow.\n\ni3 or sway\n----------\n`luastatus-i3-wrapper` should be specified as the i3bar's or sway-bar's status command in the config, e.g.:\n```\nbar {\n    status_command cd ~/.config/luastatus && exec luastatus-i3-wrapper -B no_separators time-battery-combined.lua alsa.lua xkb.lua\n```\n\nSince sway-bar is format-compatible with i3wm, the exact same configuration works for both.\n\nSee also [README for i3](https://github.com/shdown/luastatus/blob/master/barlibs/i3/README.rst) and\n[examples for i3](https://github.com/shdown/luastatus/tree/master/examples/i3).\n\ndwm\n---\nluastatus should simply be launched with `-b dwm`, e.g.:\n```\nluastatus -b dwm -B separator=' • ' alsa.lua time-battery-combined.lua\n```\n\nSee also [README for dwm](https://github.com/shdown/luastatus/blob/master/barlibs/dwm/README.rst)\nand [examples for dwm](https://github.com/shdown/luastatus/tree/master/examples/dwm).\n\nlemonbar\n--------\n`lemonbar` should be launched with `luastatus-lemonbar-launcher`, e.g.:\n```\nluastatus-lemonbar-launcher -p -B#111111 -p -f'Droid Sans Mono for Powerline:pixelsize=12:weight=Bold' -- -Bseparator=' ' alsa.lua time-date.lua\n```\n\nSee also\n[README for lemonbar](https://github.com/shdown/luastatus/blob/master/barlibs/lemonbar/README.rst)\nand [examples for lemonbar](https://github.com/shdown/luastatus/tree/master/examples/lemonbar).\n\nstdout\n------\nluastatus should be launched with `luastatus-stdout-wrapper`; or write your own wrapper, see e.g.\nthe [wrapper for launching dvtm with luastatus](https://github.com/shdown/luastatus/blob/master/barlibs/stdout/luastatus-dvtm).\n\nSee also\n[README for stdout](https://github.com/shdown/luastatus/blob/master/barlibs/stdout/README.rst) and\nand [examples for stdout](https://github.com/shdown/luastatus/tree/master/examples/stdout).\n\nSupported Lua versions\n===\n* 5.1\n* LuaJIT, which is currently 5.1-compatible with \"some language and library extensions from Lua 5.2\"\n* 5.2\n* 5.3\n* 5.4\n* 5.5\n\nReporting bugs, requesting features, suggesting patches\n===\nFeel free to open an issue or a pull request.\n\nMigrating from older versions\n===\nSee the [Migration Guide](https://github.com/shdown/luastatus/blob/master/DOCS/MIGRATION_GUIDE.md).\n\nTesting\n===\n\nHere, at luastatus, we take code correctness and safety very seriously.\nWe do the following things:\n  * We use best practices for C programming:\n    - All non-trivial string processing goes through libsafe\n    - We mark printf-like functions with appropriate attributes in order to get warnings on illegal format string or wrong argument types\n    - We handle every error possible (except for cases where we can't, and don't want to, do anything about an error)\n    - We pay attention to integer overflows/underflows, conversion of integers to/from floating-point types, and other possible cases of undefined behavior\n    - 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\n    - We use macros for (re)allocations that make it impossible to get types wrong\n  * We compile with `-Wall -Wextra`\n  * 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)\nwhich bombard luastatus with a lot of events from a plugin and a barlib simultaneously:\n    * It passes under valgrind [memcheck tool]\n    * It passes under valgrind [helgrind tool]\n    * It passes under UBSAN (Undefined Behavior Sanitizer)\n    * It passes under ASAN (Address Sanitizer)\n    * It passes under LSAN (Leak Sanitizer)\n    * It passes under TSAN (Thread Sanitizer)\n  * 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/`)\n\nWe tried to use additional compiler warnings, `-fanalyzer`, and external linters/static analyzers, but\nthese tools only gave false positives, except for PVS-Studio, which found one actual bug, but with a lot of false positives.\nSo we don't use any of these on a regular basis.\n"
  },
  {
    "path": "barlibs/dwm/CMakeLists.txt",
    "content": "file (GLOB sources \"*.c\")\nluastatus_add_barlib (barlib-dwm $<TARGET_OBJECTS:ls> ${sources})\n\ntarget_compile_definitions (barlib-dwm PUBLIC -D_POSIX_C_SOURCE=200809L)\nluastatus_target_compile_with (barlib-dwm LUA)\ntarget_include_directories (barlib-dwm PUBLIC \"${PROJECT_SOURCE_DIR}\")\n\nfind_package (PkgConfig REQUIRED)\npkg_check_modules (XCB REQUIRED xcb)\nluastatus_target_build_with (barlib-dwm XCB)\n\nluastatus_add_man_page (README.rst luastatus-barlib-dwm 7)\n"
  },
  {
    "path": "barlibs/dwm/README.rst",
    "content": ".. :X-man-page-only: luastatus-barlib-dwm\n.. :X-man-page-only: #####################\n.. :X-man-page-only:\n.. :X-man-page-only: ##########################\n.. :X-man-page-only: dwm barlib for luastatus\n.. :X-man-page-only: ##########################\n.. :X-man-page-only:\n.. :X-man-page-only: :Copyright: LGPLv3\n.. :X-man-page-only: :Manual section: 7\n\nOverview\n========\nThis barlib updates the name of the root window.\n\nIt joins all non-empty strings returned by widgets by a separator, which defaults to ``\" | \"``.\n\nIt does not provide functions and does not support events.\n\n``cb`` return value\n===================\nEither of:\n\n* a string\n\n  An empty string hides the widget.\n\n* an array of strings\n\n  Equivalent to returning a string with all non-empty elements of the array joined by the separator.\n\n* ``nil``\n\n  Hides the widget.\n\nOptions\n=======\nThe following options are supported:\n\n* ``display=<name>``\n\n  Set the name of a display to connect to. Default is to use ``DISPLAY`` environment variable.\n\n* ``separator=<string>``\n\n  Set the separator.\n"
  },
  {
    "path": "barlibs/dwm/dwm.c",
    "content": "/*\n * Copyright (C) 2015-2025  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#include \"include/barlib_v1.h\"\n#include \"include/sayf_macros.h\"\n\n#include <xcb/xcb.h>\n#include <xcb/xproto.h>\n#include <stdlib.h>\n#include <stdbool.h>\n#include <lua.h>\n#include <lauxlib.h>\n\n#include \"libls/ls_alloc_utils.h\"\n#include \"libls/ls_cstring_utils.h\"\n#include \"libls/ls_string.h\"\n#include \"libls/ls_lua_compat.h\"\n\ntypedef struct {\n    size_t nwidgets;\n\n    LS_String *bufs;\n\n    // Temporary buffer for secondary buffering, to avoid unneeded redraws.\n    LS_String tmpbuf;\n\n    // Buffer for the content of the widgets joined by /sep/.\n    LS_String joined;\n\n    char *sep;\n\n    xcb_connection_t *conn;\n\n    xcb_window_t root;\n} Priv;\n\nstatic void destroy(LuastatusBarlibData *bd)\n{\n    Priv *p = bd->priv;\n    for (size_t i = 0; i < p->nwidgets; ++i)\n        ls_string_free(p->bufs[i]);\n    free(p->bufs);\n    ls_string_free(p->tmpbuf);\n    ls_string_free(p->joined);\n    free(p->sep);\n    if (p->conn)\n        xcb_disconnect(p->conn);\n    free(p);\n}\n\n// Returns zero on success, non-zero XCB error code on failure. In either case, /*out_conn/ is\n// written to, and should be closed with /xcb_disconnect()/.\nstatic int do_connect(\n        const char *dpyname,\n        xcb_connection_t **out_conn,\n        xcb_window_t *out_root)\n{\n    int screenp;\n    *out_conn = xcb_connect(dpyname, &screenp);\n    int r = xcb_connection_has_error(*out_conn);\n    if (r != 0)\n        return r;\n\n    const xcb_setup_t *setup = xcb_get_setup(*out_conn);\n    xcb_screen_iterator_t iter = xcb_setup_roots_iterator(setup);\n\n    for (int i = 0; i < screenp; ++i)\n        xcb_screen_next(&iter);\n\n    *out_root = iter.data->root;\n    return 0;\n}\n\nstatic bool redraw(LuastatusBarlibData *bd)\n{\n    Priv *p = bd->priv;\n\n    LS_String *joined = &p->joined;\n    size_t n = p->nwidgets;\n    LS_String *bufs = p->bufs;\n    const char *sep = p->sep;\n\n    ls_string_clear(joined);\n    for (size_t i = 0; i < n; ++i) {\n        if (bufs[i].size) {\n            if (joined->size) {\n                ls_string_append_s(joined, sep);\n            }\n            ls_string_append_b(joined, bufs[i].data, bufs[i].size);\n        }\n    }\n\n    xcb_generic_error_t *err = xcb_request_check(\n        p->conn,\n        xcb_change_property_checked(\n            p->conn,\n            XCB_PROP_MODE_REPLACE,\n            p->root,\n            XCB_ATOM_WM_NAME,\n            XCB_ATOM_STRING,\n            8,\n            joined->size,\n            joined->data\n        )\n    );\n    if (err) {\n        LS_FATALF(bd, \"XCB error %d occurred\", err->error_code);\n        free(err);\n        return false;\n    }\n    return true;\n}\n\nstatic int init(LuastatusBarlibData *bd, const char *const *opts, size_t nwidgets)\n{\n    Priv *p = bd->priv = LS_XNEW(Priv, 1);\n    *p = (Priv) {\n        .nwidgets = nwidgets,\n        .bufs = LS_XNEW(LS_String, nwidgets),\n        .tmpbuf = ls_string_new_reserve(512),\n        .joined = ls_string_new_reserve(1024),\n        .sep = NULL,\n        .conn = NULL,\n    };\n    for (size_t i = 0; i < nwidgets; ++i)\n        p->bufs[i] = ls_string_new_reserve(512);\n\n    // All the options may be passed multiple times!\n    const char *dpyname = NULL;\n    const char *sep = NULL;\n    for (const char *const *s = opts; *s; ++s) {\n        const char *v;\n        if ((v = ls_strfollow(*s, \"display=\"))) {\n            dpyname = v;\n        } else if ((v = ls_strfollow(*s, \"separator=\"))) {\n            sep = v;\n        } else {\n            LS_FATALF(bd, \"unknown option '%s'\", *s);\n            goto error;\n        }\n    }\n    p->sep = ls_xstrdup(sep ? sep : \" | \");\n\n    int r = do_connect(dpyname, &p->conn, &p->root);\n    if (r != 0) {\n        LS_FATALF(bd, \"can't connect to display: XCB error %d\", r);\n        goto error;\n    }\n\n    // Clear the current name.\n    if (!redraw(bd))\n        goto error;\n\n    return LUASTATUS_OK;\n\nerror:\n    destroy(bd);\n    return LUASTATUS_ERR;\n}\n\nstatic int set(LuastatusBarlibData *bd, lua_State *L, size_t widget_idx)\n{\n    Priv *p = bd->priv;\n    LS_String *buf = &p->tmpbuf;\n    ls_string_clear(buf);\n\n    // L: ? data\n\n    switch (lua_type(L, -1)) {\n    case LUA_TSTRING:\n        {\n            size_t ns;\n            const char *s = lua_tolstring(L, -1, &ns);\n            ls_string_assign_b(buf, s, ns);\n        }\n        break;\n    case LUA_TNIL:\n        break;\n    case LUA_TTABLE:\n        {\n            const char *sep = p->sep;\n\n            size_t len = ls_lua_array_len(L, -1);\n            for (size_t i = 1; i <= len; ++i) {\n                lua_rawgeti(L, -1, i); // L: ? data value\n                if (lua_isnil(L, -1)) {\n                    goto next;\n                }\n                if (!lua_isstring(L, -1)) {\n                    LS_ERRF(bd, \"table value: expected string, found %s\", luaL_typename(L, -1));\n                    goto invalid_data;\n                }\n                size_t ns;\n                const char *s = lua_tolstring(L, -1, &ns);\n                if (buf->size && ns) {\n                    ls_string_append_s(buf, sep);\n                }\n                ls_string_append_b(buf, s, ns);\nnext:\n                lua_pop(L, 1); // L: ? data\n            }\n            // L: ? data\n        }\n        break;\n    default:\n        LS_ERRF(bd, \"expected table, string or nil, found %s\", luaL_typename(L, -1));\n        goto invalid_data;\n    }\n\n    if (!ls_string_eq(*buf, p->bufs[widget_idx])) {\n        ls_string_swap(buf, &p->bufs[widget_idx]);\n        if (!redraw(bd)) {\n            return LUASTATUS_ERR;\n        }\n    }\n    return LUASTATUS_OK;\n\ninvalid_data:\n    ls_string_clear(&p->bufs[widget_idx]);\n    return LUASTATUS_NONFATAL_ERR;\n}\n\nstatic int set_error(LuastatusBarlibData *bd, size_t widget_idx)\n{\n    Priv *p = bd->priv;\n    ls_string_assign_s(&p->bufs[widget_idx], \"(Error)\");\n    if (!redraw(bd)) {\n        return LUASTATUS_ERR;\n    }\n    return LUASTATUS_OK;\n}\n\nLuastatusBarlibIface luastatus_barlib_iface_v1 = {\n    .init = init,\n    .set = set,\n    .set_error = set_error,\n    .destroy = destroy,\n};\n"
  },
  {
    "path": "barlibs/i3/CMakeLists.txt",
    "content": "file (GLOB sources \"*.c\")\nluastatus_add_barlib (\n    barlib-i3\n    $<TARGET_OBJECTS:ls>\n    $<TARGET_OBJECTS:safe>\n    ${sources}\n)\n\ntarget_compile_definitions (barlib-i3 PUBLIC -D_POSIX_C_SOURCE=200809L)\nluastatus_target_compile_with (barlib-i3 LUA)\ntarget_include_directories (barlib-i3 PUBLIC \"${PROJECT_SOURCE_DIR}\")\n\nfind_package (PkgConfig REQUIRED)\npkg_check_modules (YAJL REQUIRED yajl>=2.0.4)\nluastatus_target_build_with (barlib-i3 YAJL)\n\nfind_library (MATH_LIBRARY m)\nif (MATH_LIBRARY)\n    target_link_libraries (barlib-i3 PUBLIC ${MATH_LIBRARY})\nendif ()\n\ninclude (GNUInstallDirs)\n\ninstall (PROGRAMS luastatus-i3-wrapper DESTINATION ${CMAKE_INSTALL_BINDIR})\n\nluastatus_add_man_page (README.rst luastatus-barlib-i3 7)\n"
  },
  {
    "path": "barlibs/i3/README.rst",
    "content": ".. :X-man-page-only: luastatus-barlib-i3\n.. :X-man-page-only: ###################\n.. :X-man-page-only:\n.. :X-man-page-only: ############################\n.. :X-man-page-only: i3/sway barlib for luastatus\n.. :X-man-page-only: ############################\n.. :X-man-page-only:\n.. :X-man-page-only: :Copyright: LGPLv3\n.. :X-man-page-only: :Manual section: 7\n\nOverview\n========\nThis barlib talks with i3bar (or sway-bar).\n\nTo use this barlib, you need to specify ``luastatus-i3-wrapper`` with appropriate arguments as the\n``status_command`` parameter of a bar in the i3 (or sway) configuration file. For example::\n\n    bar {\n        status_command cd ~/.config/luastatus && exec luastatus-i3-wrapper -B no_separators time-battery-combined.lua alsa.lua xkb.lua\n\nRedirections and ``luastatus-i3-wrapper``\n=========================================\ni3bar and sway-bar require all the data to be written to stdout and read from stdin.\n\nThis makes it very easy\nto mess things up: Lua's ``print()`` prints to stdout, processes spawned by widgets/plugins inherit\nour stdin and stdout, etc.\n\nThat's why this barlib requires that stdin and stdout file descriptors are manually redirected.\n\nA shell wrapper, ``luastatus-i3-wrapper``, is shipped with it; it does all the redirections and\nexecutes ``luastatus`` with ``-b i3``, all the required ``-B`` options, and additional arguments\npassed by you.\n\n``cb`` return value\n===================\nEither of:\n\n* an empty table or ``nil``\n\n  Hide the widget.\n\n* a table with strings keys\n\n  Is interpreted as a single segment (or a \"block\"). The keys *should not* include ``name``, as it\n  is set automatically to be able to tell which widget was clicked.\n\n  For more information, see http://i3wm.org/docs/i3bar-protocol.html#_blocks_in_detail.\n\n* an array (table with numeric keys)\n\n  Is interpreted as an array of segments. To be able to tell which one was clicked, set the\n  ``instance`` field of a segment.\n\n  ``nil`` elements are ignored.\n\n``event`` argument\n==================\nA table with all click properties i3bar (or sway-bar) provides.\n\nFor more information, see http://i3wm.org/docs/i3bar-protocol.html#_click_events.\n\nFunctions\n=========\nThe following functions are provided:\n\n* ``escaped_str = luastatus.barlib.pango_escape(str)``\n\n  Escapes text for the Pango markup.\n\nExample\n=======\nAn example that uses all possible features::\n\n    function get_user_name()\n        local f = io.popen('whoami', 'r')\n        local r = f:read('*line')\n        f:close()\n        return r\n    end\n\n    i = 0\n    widget = {\n        plugin = 'timer',\n        opts = {period = 2},\n        cb = function(t)\n            i = i + 1\n            if i == 1 then\n                return {} -- hides widget; alternatively, you can return nil.\n            elseif i == 2 then\n                -- no markup unless you specify markup='pango'\n                return {full_text = '<Hello!>', color = '#aaaa00'}\n            elseif i == 3 then\n                -- see https://developer.gnome.org/pygtk/stable/pango-markup-language.html\n                return {full_text = 'Hello, <span color=\"#aaaa00\">'\n                                    .. luastatus.barlib.pango_escape(get_user_name())\n                                    .. '</span>!',\n                        markup = 'pango'}\n            elseif i == 4 then\n                i = 0\n                return {\n                    {full_text = 'Now,', instance = 'now-segment'},\n                    nil, -- nils are ignored so that you can do\n                         --     return {get_time(), get_battery(), get_smth_else()}\n                         -- where, for example, get_battery() returns nil if the battery is full.\n                    {full_text = 'click me!', instance = 'click-me-segment'},\n                }\n            end\n        end,\n        event = function(t)\n            local r = {'properties:'}\n            for k, v in pairs(t) do\n                table.insert(r, k .. '=' .. tostring(v))\n            end\n            os.execute(string.format(\n               \"notify-send 'Click event' '%s'\", table.concat(r, ' ')))\n        end,\n    }\n\nOptions\n=======\nThe following options are supported:\n\n* ``in_fd=<fd>``\n\n  File descriptor to read i3bar or sway-bar input from. Usually set by the wrapper.\n\n* ``out_fd=<fd>``\n\n  File descriptor to write to. Usually set by the wrapper.\n\n* ``no_click_events``\n\n  Tell i3bar (or sway-bar) we don't want to receive click events. This changes its behavior in that\n  it will interpret \"clicks\" on segments as if an empty space on the bar was clicked,\n  particularly, will switch workspaces if you scroll on a segment.\n\n* ``no_separators``\n\n  Append ``\"separator\": false`` to a segment, unless it has a ``separator`` key. Also appends it\n  to an ``(Error)`` segment.\n\n* ``allow_stopping``\n\n  Allow i3bar (or sway-bar) to send luastatus ``SIGSTOP`` when it thinks it becomes invisible, and ``SIGCONT``\n  when it thinks it becomes visible. Quite a questionable feature.\n\n* ``extra_init_json=<string>``\n\n  Extra JSON to output in header, e.g. ``\"key1\":10,\"key2\":true``.\n  This could be of some use because Sway WM seems to be experimenting with additional header\n  fields (but not yet documenting them), e.g. ``float_event_coords``.\n"
  },
  {
    "path": "barlibs/i3/escape_json_str.c",
    "content": "/*\n * Copyright (C) 2025  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#include \"escape_json_str.h\"\n#include <stddef.h>\n#include \"libsafe/safev.h\"\n#include \"libsafe/mut_safev.h\"\n\nstatic const SAFEV HEX_CHARS = SAFEV_STATIC_INIT_FROM_LITERAL(\"0123456789ABCDEF\");\n\nstatic inline void append_sv(LS_String *dst, SAFEV v)\n{\n    ls_string_append_b(dst, SAFEV_ptr_UNSAFE(v), SAFEV_len(v));\n}\n\nvoid append_json_escaped_str(LS_String *dst, SAFEV v)\n{\n    ls_string_append_c(dst, '\"');\n\n    char esc_arr[] = {'\\\\', 'u', '0', '0', '#', '#'};\n    MUT_SAFEV esc = MUT_SAFEV_new_UNSAFE(esc_arr, sizeof(esc_arr));\n\n    size_t n = SAFEV_len(v);\n    size_t prev = 0;\n    for (size_t i = 0; i < n; ++i) {\n        unsigned char c = SAFEV_at(v, i);\n        if (c < 32 || c == '\\\\' || c == '\"' || c == '/') {\n            append_sv(dst, SAFEV_subspan(v, prev, i));\n            MUT_SAFEV_set_at(esc, 4, SAFEV_at(HEX_CHARS, c / 16));\n            MUT_SAFEV_set_at(esc, 5, SAFEV_at(HEX_CHARS, c % 16));\n            append_sv(dst, MUT_SAFEV_TO_SAFEV(esc));\n            prev = i + 1;\n        }\n    }\n    append_sv(dst, SAFEV_subspan(v, prev, n));\n\n    ls_string_append_c(dst, '\"');\n}\n"
  },
  {
    "path": "barlibs/i3/escape_json_str.h",
    "content": "/*\n * Copyright (C) 2025  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifndef escape_json_str_h_\n#define escape_json_str_h_\n\n#include \"libls/ls_string.h\"\n#include \"libsafe/safev.h\"\n\n// Append to /dst/ JSON-escaped C string /v/.\nvoid append_json_escaped_str(LS_String *dst, SAFEV v);\n\n#endif\n"
  },
  {
    "path": "barlibs/i3/event_watcher.c",
    "content": "/*\n * Copyright (C) 2015-2025  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#include \"event_watcher.h\"\n\n#include <stdlib.h>\n#include <sys/types.h>\n#include <stdbool.h>\n#include <lua.h>\n#include <yajl/yajl_parse.h>\n#include <unistd.h>\n#include <errno.h>\n#include <string.h>\n\n#include \"include/sayf_macros.h\"\n\n#include \"libls/ls_tls_ebuf.h\"\n#include \"libls/ls_parse_int.h\"\n#include \"libls/ls_strarr.h\"\n#include \"libls/ls_panic.h\"\n#include \"libls/ls_lua_compat.h\"\n#include \"libls/ls_alloc_utils.h\"\n#include \"libls/ls_freemem.h\"\n\n#include \"priv.h\"\n\n// If this is to be incremented, /lua_checkstack()/ must be called at appropriate times, and the\n// depth of the recursion in /push_object()/ be potentially limited somehow.\nenum { DEPTH_LIMIT = 10 };\n\ntypedef struct {\n    enum {\n        TYPE_ARRAY_START,\n        TYPE_ARRAY_END,\n        TYPE_MAP_START,\n        TYPE_MAP_END,\n\n        TYPE_STRING_KEY,\n\n        TYPE_STRING,\n        TYPE_NUMBER,\n        TYPE_BOOL,\n        TYPE_NULL,\n    } type;\n\n    union {\n        size_t str_idx;\n        double num;\n        bool flag;\n    } as;\n} Token;\n\ntypedef struct {\n    Token *data;\n    size_t size;\n    size_t capacity;\n} TokenList;\n\nstatic inline TokenList token_list_new(void)\n{\n    return (TokenList) {NULL, 0, 0};\n}\n\nstatic inline void token_list_push(TokenList *x, Token token)\n{\n    if (x->size == x->capacity) {\n        x->data = LS_M_X2REALLOC(x->data, &x->capacity);\n    }\n    x->data[x->size++] = token;\n}\n\nstatic inline void token_list_clear(TokenList *x)\n{\n    x->data = LS_M_FREEMEM(x->data, &x->size, &x->capacity);\n}\n\nstatic inline void token_list_free(TokenList *x)\n{\n    free(x->data);\n}\n\ntypedef struct {\n    // Current JSON nesting depth. Before the initial '[', depth == -1.\n    int depth;\n\n    // Whether the last key (at depth == 1) was \"name\".\n    bool last_key_is_name;\n\n    // An array in which all the JSON strings, including keys, are stored.\n    LS_StringArray strarr;\n\n    // A flat list of current event's tokens.\n    TokenList tokens;\n\n    // Current event's widget index, or a negative value if is not known yet or invalid.\n    int widget;\n\n    LuastatusBarlibData *bd;\n    LuastatusBarlibEWFuncs funcs;\n} Context;\n\n// Converts a JSON object that starts at the token with index /*idx/ in /ctx->tokens/, to a Lua\n// object, and pushes it onto /L/'s stack.\n// Advances /*idx/ so that it points to one token past the last token of the object.\nstatic void push_object(lua_State *L, Context *ctx, size_t *idx)\n{\n    Token t = ctx->tokens.data[*idx];\n    switch (t.type) {\n    case TYPE_ARRAY_START:\n        lua_newtable(L); // L: table\n        ++*idx;\n        for (size_t n = 1; ctx->tokens.data[*idx].type != TYPE_ARRAY_END; ++n) {\n            push_object(L, ctx, idx); // L: table elem\n            LS_ASSERT(n <= (size_t) LS_LUA_MAXI);\n            lua_rawseti(L, -2, n); // L: table\n        }\n        break;\n    case TYPE_MAP_START:\n        lua_newtable(L); // L: table\n        ++*idx;\n        while (ctx->tokens.data[*idx].type != TYPE_MAP_END) {\n            Token key = ctx->tokens.data[*idx];\n            LS_ASSERT(key.type == TYPE_STRING_KEY);\n\n            // To limit the maximum number of slots pushed onto /L/'s stack to /N + O(1)/, where /N/\n            // is the maximum /ctx->depth/ encountered, we have to push the value first.\n            // Unfortunately, /lua_settable()/ expects the key to be pushed first. So we simply swap\n            // them with /lua_insert()/.\n\n            ++*idx;\n            push_object(L, ctx, idx); // L: table value\n\n            size_t ns;\n            const char *s = ls_strarr_at(ctx->strarr, key.as.str_idx, &ns);\n            lua_pushlstring(L, s, ns); // L: table value key\n\n            lua_insert(L, -2); // L: table key value\n            lua_settable(L, -3); // L: table\n        }\n        break;\n    case TYPE_STRING:\n        {\n            size_t ns;\n            const char *s = ls_strarr_at(ctx->strarr, t.as.str_idx, &ns);\n            lua_pushlstring(L, s, ns);\n        }\n        break;\n    case TYPE_NUMBER:\n        lua_pushnumber(L, t.as.num);\n        break;\n    case TYPE_BOOL:\n        lua_pushboolean(L, t.as.flag);\n        break;\n    case TYPE_NULL:\n        lua_pushnil(L);\n        break;\n    default:\n        LS_MUST_BE_UNREACHABLE();\n    }\n    // Now, /*idx/ points to the last token of the object; increment it by one.\n    ++*idx;\n}\n\nstatic void flush(Context *ctx)\n{\n    Priv *p = ctx->bd->priv;\n\n    if (ctx->widget >= 0 && (size_t) ctx->widget < p->nwidgets) {\n        lua_State *L = ctx->funcs.call_begin(ctx->bd->userdata, ctx->widget);\n        size_t idx = 0;\n        push_object(L, ctx, &idx);\n        LS_ASSERT(idx == ctx->tokens.size);\n        ctx->funcs.call_end(ctx->bd->userdata, ctx->widget);\n    }\n\n    // reset the context\n    ctx->last_key_is_name = false;\n    ls_strarr_clear(&ctx->strarr);\n    token_list_clear(&ctx->tokens);\n    ctx->widget = -1;\n}\n\nstatic int token_helper(Context *ctx, Token token)\n{\n    if (ctx->depth == -1) {\n        if (token.type != TYPE_ARRAY_START) {\n            LS_ERRF(ctx->bd, \"(event watcher) expected '['\");\n            return 0;\n        }\n        ++ctx->depth;\n    } else {\n        if (ctx->depth == 0 && token.type != TYPE_MAP_START) {\n            LS_ERRF(ctx->bd, \"(event watcher) expected '{'\");\n            return 0;\n        }\n        token_list_push(&ctx->tokens, token);\n        switch (token.type) {\n        case TYPE_ARRAY_START:\n        case TYPE_MAP_START:\n            if (++ctx->depth >= DEPTH_LIMIT) {\n                LS_ERRF(ctx->bd, \"(event watcher) nesting depth limit exceeded\");\n                return 0;\n            }\n            break;\n\n        case TYPE_ARRAY_END:\n        case TYPE_MAP_END:\n            if (--ctx->depth == 0) {\n                flush(ctx);\n            }\n            break;\n\n        default:\n            break;\n        }\n    }\n    return 1;\n}\n\nstatic inline size_t append_to_strarr(Context *ctx, const char *buf, size_t nbuf)\n{\n    ls_strarr_append(&ctx->strarr, buf, nbuf);\n    return ls_strarr_size(ctx->strarr) - 1;\n}\n\nstatic int callback_null(void *vctx)\n{\n    return token_helper(vctx, (Token) {TYPE_NULL, {0}});\n}\n\nstatic int callback_boolean(void *vctx, int value)\n{\n    return token_helper(vctx, (Token) {TYPE_BOOL, {.flag = value}});\n}\n\nstatic int callback_integer(void *vctx, long long value)\n{\n    return token_helper(vctx, (Token) {TYPE_NUMBER, {.num = value}});\n}\n\nstatic int callback_double(void *vctx, double value)\n{\n    return token_helper(vctx, (Token) {TYPE_NUMBER, {.num = value}});\n}\n\nstatic int callback_string(void *vctx, const unsigned char *buf, size_t nbuf)\n{\n    Context *ctx = vctx;\n    if (ctx->depth == 1 && ctx->last_key_is_name) {\n        // parse error is OK here, /ctx->widget/ is checked in /flush()/.\n        ctx->widget = ls_full_strtou_b((const char *) buf, nbuf);\n    }\n\n    return token_helper(ctx, (Token) {\n        TYPE_STRING,\n        {.str_idx = append_to_strarr(ctx, (const char *) buf, nbuf)}\n    });\n}\n\nstatic int callback_start_map(void *vctx)\n{\n    return token_helper(vctx, (Token) {TYPE_MAP_START, {0}});\n}\n\nstatic int callback_map_key(void *vctx, const unsigned char *buf, size_t nbuf)\n{\n    Context *ctx = vctx;\n    if (ctx->depth == 1) {\n        ctx->last_key_is_name = (nbuf == 4 && memcmp(buf, \"name\", 4) == 0);\n    }\n    return token_helper(ctx, (Token) {\n        TYPE_STRING_KEY,\n        {.str_idx = append_to_strarr(ctx, (const char *) buf, nbuf)}\n    });\n}\n\nstatic int callback_end_map(void *vctx)\n{\n    return token_helper(vctx, (Token) {TYPE_MAP_END, {0}});\n}\n\nstatic int callback_start_array(void *vctx)\n{\n    return token_helper(vctx, (Token) {TYPE_ARRAY_START, {0}});\n}\n\nstatic int callback_end_array(void *vctx)\n{\n    return token_helper(vctx, (Token) {TYPE_ARRAY_END, {0}});\n}\n\nint event_watcher(LuastatusBarlibData *bd, LuastatusBarlibEWFuncs funcs)\n{\n    Priv *p = bd->priv;\n    if (p->noclickev)\n        return LUASTATUS_NONFATAL_ERR;\n\n    Context ctx = {\n        .depth = -1,\n        .last_key_is_name = false,\n        .strarr = ls_strarr_new(),\n        .tokens = token_list_new(),\n        .widget = -1,\n        .bd = bd,\n        .funcs = funcs,\n    };\n    yajl_callbacks callbacks = {\n        .yajl_null        = callback_null,\n        .yajl_boolean     = callback_boolean,\n        .yajl_integer     = callback_integer,\n        .yajl_double      = callback_double,\n        .yajl_string      = callback_string,\n        .yajl_start_map   = callback_start_map,\n        .yajl_map_key     = callback_map_key,\n        .yajl_end_map     = callback_end_map,\n        .yajl_start_array = callback_start_array,\n        .yajl_end_array   = callback_end_array,\n    };\n    yajl_handle hand = yajl_alloc(&callbacks, NULL, &ctx);\n\n    unsigned char buf[1024];\n    while (1) {\n        ssize_t nread = read(p->in_fd, buf, sizeof(buf));\n        if (nread < 0) {\n            LS_ERRF(bd, \"(event watcher) read error: %s\", ls_tls_strerror(errno));\n            goto error;\n        } else if (nread == 0) {\n            LS_ERRF(bd, \"(event watcher) i3bar closed its end of the pipe\");\n            goto error;\n        }\n        switch (yajl_parse(hand, buf, nread)) {\n        case yajl_status_ok:\n            break;\n        case yajl_status_client_canceled:\n            goto error;\n        case yajl_status_error:\n            {\n                unsigned char *descr = yajl_get_error(hand, /*verbose*/ 1, buf, nread);\n                LS_ERRF(bd, \"(event watcher) yajl parse error: %s\", (char *) descr);\n                yajl_free_error(hand, descr);\n            }\n            goto error;\n        }\n    }\n\nerror:\n    ls_strarr_destroy(ctx.strarr);\n    token_list_free(&ctx.tokens);\n    yajl_free(hand);\n    return LUASTATUS_ERR;\n}\n"
  },
  {
    "path": "barlibs/i3/event_watcher.h",
    "content": "/*\n * Copyright (C) 2015-2025  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifndef event_watcher_h_\n#define event_watcher_h_\n\n#include \"include/barlib_data_v1.h\"\n\nint event_watcher(LuastatusBarlibData *bd, LuastatusBarlibEWFuncs funcs);\n\n#endif\n"
  },
  {
    "path": "barlibs/i3/fuzz_esc_json/.gitignore",
    "content": "harness\nfindings\n"
  },
  {
    "path": "barlibs/i3/fuzz_esc_json/build.sh",
    "content": "#!/bin/sh\n\nif [ -z \"$CC\" ]; then\n    echo >&2 \"You must set the 'CC' environment variable.\"\n    echo >&2 \"Hint: you probably want to set 'CC' to 'some-directory/afl-gcc'.\"\n    exit 1\nfi\n\ncd -- \"$(dirname \"$(readlink \"$0\" || printf '%s\\n' \"$0\")\")\"\n\nluastatus_root=../../..\n\n$CC -Wall -Wextra -O3 -fsanitize=undefined -std=c99 -D_POSIX_C_SOURCE=200809L \\\n    -I\"$luastatus_root\" \\\n    ./harness.c \\\n    ../escape_json_str.c \\\n    \"$luastatus_root\"/libls/ls_string.c \\\n    \"$luastatus_root\"/libls/ls_alloc_utils.c \\\n    \"$luastatus_root\"/libls/ls_panic.c \\\n    \"$luastatus_root\"/libls/ls_cstring_utils.c \\\n    \"$luastatus_root\"/libsafe/*.c \\\n    -o harness\n"
  },
  {
    "path": "barlibs/i3/fuzz_esc_json/clear.sh",
    "content": "#!/bin/sh\n\nset -e\n\ncd -- \"$(dirname \"$(readlink \"$0\" || printf '%s\\n' \"$0\")\")\"\n\nrm -rf ./findings\n"
  },
  {
    "path": "barlibs/i3/fuzz_esc_json/fuzz.sh",
    "content": "#!/bin/sh\n\nset -e\n\nif [ -z \"$XXX_AFL_DIR\" ]; then\n    echo >&2 \"You must set the 'XXX_AFL_DIR' environment variable.\"\n    exit 1\nfi\n\ncd -- \"$(dirname \"$(readlink \"$0\" || printf '%s\\n' \"$0\")\")\"\n\nmkdir -p ./findings\n\nexport UBSAN_OPTIONS=halt_on_error=1\n\nexport AFL_EXIT_WHEN_DONE=1\n\n\"$XXX_AFL_DIR\"/afl-fuzz -i testcases -o findings -t 5 ./harness @@\n"
  },
  {
    "path": "barlibs/i3/fuzz_esc_json/gen_testcases.sh",
    "content": "#!/bin/sh\n\nset -e\n\ncd -- \"$(dirname \"$(readlink \"$0\" || printf '%s\\n' \"$0\")\")\"\n\nluastatus_root=../../..\n\n\"$luastatus_root\"/fuzz_utils/gen_testcases/gen_testcases.py \\\n    ./testcases \\\n    --a=1:'\\\"/' \\\n    --a-range=h:1:0-31 \\\n    --b=1:abc \\\n    --b-range=h:1:127-255 \\\n    --a-is-important \\\n    --length=5-20 \\\n    --num-files=10 \\\n    --random-seed=123\n"
  },
  {
    "path": "barlibs/i3/fuzz_esc_json/harness.c",
    "content": "/*\n * Copyright (C) 2025  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#include <stdio.h>\n#include <stdlib.h>\n#include <unistd.h>\n#include <fcntl.h>\n\n#include \"libls/ls_string.h\"\n#include \"libsafe/safev.h\"\n#include \"fuzz_utils/fuzz_utils.h\"\n\n#include \"../escape_json_str.h\"\n\nint main(int argc, char **argv)\n{\n    if (argc != 2) {\n        fprintf(stderr, \"USAGE: harness INPUT_FILE\\n\");\n        return 2;\n    }\n\n    int fd_in = open(argv[1], O_RDONLY | O_CLOEXEC);\n    if (fd_in < 0) {\n        perror(argv[1]);\n        abort();\n    }\n\n    FuzzInput input = fuzz_input_new_prealloc(1024);\n    if (fuzz_input_read(fd_in, &input) < 0) {\n        perror(\"read\");\n        abort();\n    }\n\n    LS_String res = ls_string_new_from_s(\"escape result = \");\n    append_json_escaped_str(&res, SAFEV_new_UNSAFE(input.data, input.size));\n\n    fuzz_utils_used(res.data, res.size);\n\n    fuzz_input_free(input);\n    ls_string_free(res);\n    close(fd_in);\n\n    return 0;\n}\n"
  },
  {
    "path": "barlibs/i3/fuzz_esc_json/testcases/testcase_000",
    "content": "\\\"/\u0002/\\"
  },
  {
    "path": "barlibs/i3/fuzz_esc_json/testcases/testcase_001",
    "content": "\"\u0006\u0005\b\u0001\"\u0010\\\u0015"
  },
  {
    "path": "barlibs/i3/fuzz_esc_json/testcases/testcase_003",
    "content": "/\u0002\u0015\\\n"
  },
  {
    "path": "barlibs/i3/fuzz_esc_json/testcases/testcase_004",
    "content": "\"\u0018/cb\\\"\ba\u0017c\"b\\\u0005"
  },
  {
    "path": "barlibs/i3/fuzz_esc_json/testcases/testcase_006",
    "content": "ba\u000ba߫\u0018\""
  },
  {
    "path": "barlibs/i3/fuzz_esc_json/testcases/testcase_007",
    "content": "c\u0014\"bcc"
  },
  {
    "path": "barlibs/i3/fuzz_esc_json/testcases/testcase_008",
    "content": "cbcȂbab\\"
  },
  {
    "path": "barlibs/i3/fuzz_esc_json/testcases/testcase_009",
    "content": "cbƐbccb"
  },
  {
    "path": "barlibs/i3/fuzz_esc_pango/.gitignore",
    "content": "harness\nfindings\n"
  },
  {
    "path": "barlibs/i3/fuzz_esc_pango/build.sh",
    "content": "#!/bin/sh\n\nif [ -z \"$CC\" ]; then\n    echo >&2 \"You must set the 'CC' environment variable.\"\n    echo >&2 \"Hint: you probably want to set 'CC' to 'some-directory/afl-gcc'.\"\n    exit 1\nfi\n\ncd -- \"$(dirname \"$(readlink \"$0\" || printf '%s\\n' \"$0\")\")\"\n\nluastatus_root=../../..\n\n$CC -Wall -Wextra -O3 -fsanitize=undefined -std=c99 -D_POSIX_C_SOURCE=200809L \\\n    -I\"$luastatus_root\" \\\n    ./harness.c \\\n    ../pango_escape.c \\\n    \"$luastatus_root\"/libls/ls_string.c \\\n    \"$luastatus_root\"/libls/ls_alloc_utils.c \\\n    \"$luastatus_root\"/libls/ls_panic.c \\\n    \"$luastatus_root\"/libls/ls_cstring_utils.c \\\n    \"$luastatus_root\"/libsafe/*.c \\\n    -o harness\n"
  },
  {
    "path": "barlibs/i3/fuzz_esc_pango/clear.sh",
    "content": "#!/bin/sh\n\nset -e\n\ncd -- \"$(dirname \"$(readlink \"$0\" || printf '%s\\n' \"$0\")\")\"\n\nrm -rf ./findings\n"
  },
  {
    "path": "barlibs/i3/fuzz_esc_pango/fuzz.sh",
    "content": "#!/bin/sh\n\nset -e\n\nif [ -z \"$XXX_AFL_DIR\" ]; then\n    echo >&2 \"You must set the 'XXX_AFL_DIR' environment variable.\"\n    exit 1\nfi\n\ncd -- \"$(dirname \"$(readlink \"$0\" || printf '%s\\n' \"$0\")\")\"\n\nmkdir -p ./findings\n\nexport UBSAN_OPTIONS=halt_on_error=1\n\nexport AFL_EXIT_WHEN_DONE=1\n\n\"$XXX_AFL_DIR\"/afl-fuzz -i testcases -o findings -t 5 ./harness @@\n"
  },
  {
    "path": "barlibs/i3/fuzz_esc_pango/gen_testcases.sh",
    "content": "#!/bin/sh\n\nset -e\n\ncd -- \"$(dirname \"$(readlink \"$0\" || printf '%s\\n' \"$0\")\")\"\n\nluastatus_root=../../..\n\n\"$luastatus_root\"/fuzz_utils/gen_testcases/gen_testcases.py \\\n    ./testcases \\\n    --a=1:'&<>\\\"' \\\n    --b=1:abc \\\n    --a-is-important \\\n    --length=5-20 \\\n    --num-files=10 \\\n    --random-seed=123\n"
  },
  {
    "path": "barlibs/i3/fuzz_esc_pango/harness.c",
    "content": "/*\n * Copyright (C) 2025  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#include <stdio.h>\n#include <stdlib.h>\n#include <unistd.h>\n#include <fcntl.h>\n\n#include \"libls/ls_string.h\"\n#include \"libsafe/safev.h\"\n#include \"fuzz_utils/fuzz_utils.h\"\n\n#include \"../pango_escape.h\"\n\nstatic void append_to_ls_string(void *ud, SAFEV segment)\n{\n    LS_String *dst = ud;\n    ls_string_append_b(dst, SAFEV_ptr_UNSAFE(segment), SAFEV_len(segment));\n}\n\nint main(int argc, char **argv)\n{\n    if (argc != 2) {\n        fprintf(stderr, \"USAGE: harness INPUT_FILE\\n\");\n        return 2;\n    }\n\n    int fd_in = open(argv[1], O_RDONLY | O_CLOEXEC);\n    if (fd_in < 0) {\n        perror(argv[1]);\n        abort();\n    }\n\n    FuzzInput input = fuzz_input_new_prealloc(1024);\n    if (fuzz_input_read(fd_in, &input) < 0) {\n        perror(\"read\");\n        abort();\n    }\n\n    LS_String res = ls_string_new_from_s(\"pango escape result = \");\n    pango_escape(\n        SAFEV_new_UNSAFE(input.data, input.size),\n        append_to_ls_string,\n        &res);\n\n    fuzz_utils_used(res.data, res.size);\n\n    fuzz_input_free(input);\n    ls_string_free(res);\n    close(fd_in);\n\n    return 0;\n}\n"
  },
  {
    "path": "barlibs/i3/fuzz_esc_pango/testcases/testcase_000",
    "content": "&<>\\\"&"
  },
  {
    "path": "barlibs/i3/fuzz_esc_pango/testcases/testcase_001",
    "content": ">&&<&\\>&b\\\"\">\"\"c\\"
  },
  {
    "path": "barlibs/i3/fuzz_esc_pango/testcases/testcase_002",
    "content": ">c>>\\&\\c\"<c\\<&>\\a&"
  },
  {
    "path": "barlibs/i3/fuzz_esc_pango/testcases/testcase_003",
    "content": "&<\\<<cc>cbb\"\"<\\<"
  },
  {
    "path": "barlibs/i3/fuzz_esc_pango/testcases/testcase_004",
    "content": "\">&aca\"\"><\"bb>\"baa&"
  },
  {
    "path": "barlibs/i3/fuzz_esc_pango/testcases/testcase_005",
    "content": "&\\<b>bcbbb>b\\>cc>&ab"
  },
  {
    "path": "barlibs/i3/fuzz_esc_pango/testcases/testcase_006",
    "content": "acb&\\ab\\a"
  },
  {
    "path": "barlibs/i3/fuzz_esc_pango/testcases/testcase_007",
    "content": "ac>bc"
  },
  {
    "path": "barlibs/i3/fuzz_esc_pango/testcases/testcase_008",
    "content": "bbccba<"
  },
  {
    "path": "barlibs/i3/fuzz_esc_pango/testcases/testcase_009",
    "content": "ababbaabcbbb"
  },
  {
    "path": "barlibs/i3/i3.c",
    "content": "/*\n * Copyright (C) 2015-2025  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#include <string.h>\n#include <stdbool.h>\n#include <stdlib.h>\n#include <stdio.h>\n#include <errno.h>\n#include <math.h>\n#include <lua.h>\n#include <lauxlib.h>\n\n#include \"include/barlib_v1.h\"\n#include \"include/sayf_macros.h\"\n\n#include \"libls/ls_string.h\"\n#include \"libls/ls_alloc_utils.h\"\n#include \"libls/ls_parse_int.h\"\n#include \"libls/ls_cstring_utils.h\"\n#include \"libls/ls_tls_ebuf.h\"\n#include \"libls/ls_io_utils.h\"\n#include \"libls/ls_lua_compat.h\"\n#include \"libsafe/safev.h\"\n\n#include \"priv.h\"\n#include \"event_watcher.h\"\n#include \"escape_json_str.h\"\n#include \"pango_escape.h\"\n\nstatic void destroy(LuastatusBarlibData *bd)\n{\n    Priv *p = bd->priv;\n    for (size_t i = 0; i < p->nwidgets; ++i) {\n        ls_string_free(p->bufs[i]);\n    }\n    free(p->bufs);\n    ls_string_free(p->tmpbuf);\n    ls_close(p->in_fd);\n    if (p->out) {\n        fclose(p->out);\n    }\n    free(p);\n}\n\nstatic int init(LuastatusBarlibData *bd, const char *const *opts, size_t nwidgets)\n{\n    Priv *p = bd->priv = LS_XNEW(Priv, 1);\n    *p = (Priv) {\n        .nwidgets = nwidgets,\n        .bufs = LS_XNEW(LS_String, nwidgets),\n        .tmpbuf = ls_string_new_reserve(1024),\n        .in_fd = -1,\n        .out = NULL,\n        .noclickev = false,\n        .noseps = false,\n    };\n    for (size_t i = 0; i < nwidgets; ++i)\n        p->bufs[i] = ls_string_new_reserve(1024);\n\n    // All the options may be passed multiple times!\n    int in_fd = -1;\n    int out_fd = -1;\n    const char *extra_init_json = NULL;\n    bool allow_stopping = false;\n    for (const char *const *s = opts; *s; ++s) {\n        const char *v;\n        if ((v = ls_strfollow(*s, \"in_fd=\"))) {\n            if ((in_fd = ls_full_strtou(v)) < 0) {\n                LS_FATALF(bd, \"in_fd value is not a valid unsigned integer\");\n                goto error;\n            }\n        } else if ((v = ls_strfollow(*s, \"out_fd=\"))) {\n            if ((out_fd = ls_full_strtou(v)) < 0) {\n                LS_FATALF(bd, \"out_fd value is not a valid unsigned integer\");\n                goto error;\n            }\n        } else if (strcmp(*s, \"no_click_events\") == 0) {\n            p->noclickev = true;\n        } else if (strcmp(*s, \"no_separators\") == 0) {\n            p->noseps = true;\n        } else if (strcmp(*s, \"allow_stopping\") == 0) {\n            allow_stopping = true;\n        } else if ((v = ls_strfollow(*s, \"extra_init_json=\"))) {\n            extra_init_json = v;\n        } else {\n            LS_FATALF(bd, \"unknown option '%s'\", *s);\n            goto error;\n        }\n    }\n\n    // we require /in_fd/ and /out_fd/ to >=3 because making stdin/stdout/stderr CLOEXEC has very\n    // bad consequences, and we just don't want to complicate the logic.\n    if (in_fd < 3) {\n        LS_FATALF(bd, \"in_fd is not specified or less than 3\");\n        goto error;\n    }\n    if (out_fd < 3) {\n        LS_FATALF(bd, \"out_fd is not specified or less than 3\");\n        goto error;\n    }\n\n    // assign\n    p->in_fd = in_fd;\n    if (!(p->out = fdopen(out_fd, \"w\"))) {\n        LS_FATALF(bd, \"can't fdopen %d: %s\", out_fd, ls_tls_strerror(errno));\n        goto error;\n    }\n\n    // make CLOEXEC\n    if (ls_make_cloexec(in_fd) < 0) {\n        LS_FATALF(bd, \"can't make fd %d CLOEXEC: %s\", in_fd, ls_tls_strerror(errno));\n        goto error;\n    }\n    if (ls_make_cloexec(out_fd) < 0) {\n        LS_FATALF(bd, \"can't make fd %d CLOEXEC: %s\", out_fd, ls_tls_strerror(errno));\n        goto error;\n    }\n\n    // print header\n    fprintf(p->out, \"{\\\"version\\\":1,\\\"click_events\\\":%s\", p->noclickev ? \"false\" : \"true\");\n    if (extra_init_json && extra_init_json[0]) {\n        fprintf(p->out, \",%s\", extra_init_json);\n    }\n    if (!allow_stopping) {\n        fprintf(p->out, \",\\\"stop_signal\\\":0,\\\"cont_signal\\\":0\");\n    }\n    fprintf(p->out, \"}\\n[\\n\");\n    fflush(p->out);\n    if (ferror(p->out)) {\n        LS_FATALF(bd, \"write error: %s\", ls_tls_strerror(errno));\n        goto error;\n    }\n\n    return LUASTATUS_OK;\n\nerror:\n    destroy(bd);\n    return LUASTATUS_ERR;\n}\n\nstatic bool redraw(LuastatusBarlibData *bd)\n{\n    Priv *p = bd->priv;\n\n    FILE *out = p->out;\n    size_t n = p->nwidgets;\n    LS_String *bufs = p->bufs;\n\n    putc('[', out);\n    bool first = true;\n    for (size_t i = 0; i < n; ++i) {\n        if (bufs[i].size) {\n            if (!first) {\n                putc(',', out);\n            }\n            fwrite(bufs[i].data, 1, bufs[i].size, out);\n            first = false;\n        }\n    }\n    fputs(\"],\\n\", out);\n    fflush(out);\n    if (ferror(out)) {\n        LS_FATALF(bd, \"write error: %s\", ls_tls_strerror(errno));\n        return false;\n    }\n    return true;\n}\n\nstatic void append_to_lua_buf(void *ud, SAFEV segment)\n{\n    luaL_Buffer *b = ud;\n    luaL_addlstring(b, SAFEV_ptr_UNSAFE(segment), SAFEV_len(segment));\n}\n\nstatic int l_pango_escape(lua_State *L)\n{\n    size_t ns;\n    // WARNING: luaL_check*() functions do a long jump on error!\n    const char *s = luaL_checklstring(L, 1, &ns);\n\n    luaL_Buffer b;\n    luaL_buffinit(L, &b);\n\n    SAFEV v = SAFEV_new_UNSAFE(s, ns);\n    pango_escape(v, append_to_lua_buf, &b);\n\n    luaL_pushresult(&b);\n    return 1;\n}\n\nstatic void register_funcs(LuastatusBarlibData *bd, lua_State *L)\n{\n    (void) bd;\n    // L: table\n    lua_pushcfunction(L, l_pango_escape); // L: table l_pango_escape\n    lua_setfield(L, -2, \"pango_escape\"); // L: table\n}\n\nstatic inline bool append_json_number(LS_String *s, double value)\n{\n    if (!isfinite(value)) {\n        return false;\n    }\n    ls_string_append_f(s, \"%.20g\", value);\n    return true;\n}\n\n// Appends a JSON segment generated from table at the top of /L/'s stack, to\n// /((Priv *) bd->priv)->tmpbuf/.\nstatic bool append_segment(LuastatusBarlibData *bd, lua_State *L, size_t widget_idx)\n{\n    Priv *p = bd->priv;\n    LS_String *dst = &p->tmpbuf;\n\n    // add a \"prologue\"\n    if (dst->size) {\n        ls_string_append_c(dst, ',');\n    }\n    ls_string_append_f(dst, \"{\\\"name\\\":\\\"%zu\\\"\", widget_idx);\n\n    bool has_separator_key = false;\n    // L: ? table\n    lua_pushnil(L); // L: ? table nil\n    while (lua_next(L, -2)) {\n        // L: ? table key value\n        if (!lua_isstring(L, -2)) {\n            LS_ERRF(bd, \"segment key: expected string, found %s\", luaL_typename(L, -2));\n            return false;\n        }\n        const char *key = lua_tostring(L, -2);\n\n        if (strcmp(key, \"name\") == 0) {\n            LS_WARNF(bd, \"segment: ignoring 'name', it is set automatically; use 'instance' \"\n                         \"instead\");\n            goto next_entry;\n        }\n\n        if (strcmp(key, \"separator\") == 0) {\n            has_separator_key = true;\n        }\n\n        ls_string_append_c(dst, ',');\n        append_json_escaped_str(dst, SAFEV_new_from_cstr_UNSAFE(key));\n        ls_string_append_c(dst, ':');\n\n        switch (lua_type(L, -1)) {\n        case LUA_TNUMBER:\n            {\n                double val = lua_tonumber(L, -1);\n                if (!append_json_number(dst, val)) {\n                    LS_ERRF(bd, \"segment entry '%s': invalid number (NaN/Inf)\", key);\n                    return false;\n                }\n            }\n            break;\n        case LUA_TSTRING:\n            {\n                size_t ns;\n                const char *s = lua_tolstring(L, -1, &ns);\n                append_json_escaped_str(dst, SAFEV_new_UNSAFE(s, ns));\n            }\n            break;\n        case LUA_TBOOLEAN:\n            {\n                bool val = lua_toboolean(L, -1);\n                ls_string_append_s(dst, val ? \"true\" : \"false\");\n            }\n            break;\n        case LUA_TNIL:\n            ls_string_append_s(dst, \"null\");\n            break;\n        default:\n            LS_ERRF(bd, \"segment entry '%s': expected string, number, boolean or nil, found %s\",\n                    key, luaL_typename(L, -1));\n            return false;\n        }\n\nnext_entry:\n        lua_pop(L, 1); // L: ? table key\n    }\n    // L: ? table\n\n    // add an \"epilogue\"\n    if (p->noseps && !has_separator_key) {\n        ls_string_append_s(dst, \",\\\"separator\\\":false\");\n    }\n    ls_string_append_c(dst, '}');\n\n    return true;\n}\n\ntypedef enum {\n    TC_EMPTY,\n    TC_ARRAY,\n    TC_DICT,\n} TableClass;\n\nstatic inline TableClass classify_table(lua_State *L)\n{\n    // L: ? table\n    lua_pushnil(L); // L: ? table nil\n    if (!lua_next(L, -2)) {\n        // L: ? table\n        return TC_EMPTY;\n    }\n    // L: ? table key value\n    bool is_array = lua_isnumber(L, -2);\n    lua_pop(L, 2); // L: ? table\n    return is_array ? TC_ARRAY : TC_DICT;\n}\n\nstatic int set(LuastatusBarlibData *bd, lua_State *L, size_t widget_idx)\n{\n    Priv *p = bd->priv;\n    ls_string_clear(&p->tmpbuf);\n\n    // L: ? data\n\n    switch (lua_type(L, -1)) {\n    case LUA_TNIL:\n        break;\n\n    case LUA_TTABLE:\n        switch (classify_table(L)) {\n        case TC_EMPTY:\n            break;\n        case TC_DICT:\n            if (!append_segment(bd, L, widget_idx)) {\n                goto invalid_data;\n            }\n            break;\n        case TC_ARRAY:\n            {\n                size_t len = ls_lua_array_len(L, -1);\n                for (size_t i = 1; i <= len; ++i) {\n                    lua_rawgeti(L, -1, i); // L: ? data value\n                    switch (lua_type(L, -1)) {\n                    case LUA_TTABLE:\n                        if (!append_segment(bd, L, widget_idx)) {\n                            goto invalid_data;\n                        }\n                        break;\n                    case LUA_TNIL:\n                        break;\n                    default:\n                        LS_ERRF(bd, \"array value: expected table or nil, found %s\",\n                                luaL_typename(L, -1));\n                        goto invalid_data;\n                    }\n                    lua_pop(L, 1); // L: ? data\n                }\n            }\n        }\n        // L: ? data\n        break;\n\n    default:\n        LS_ERRF(bd, \"expected table or nil, found %s\", luaL_typename(L, -1));\n        goto invalid_data;\n    }\n\n    if (!ls_string_eq(p->tmpbuf, p->bufs[widget_idx])) {\n        ls_string_swap(&p->tmpbuf, &p->bufs[widget_idx]);\n        if (!redraw(bd)) {\n            return LUASTATUS_ERR;\n        }\n    }\n    return LUASTATUS_OK;\n\ninvalid_data:\n    ls_string_clear(&p->bufs[widget_idx]);\n    return LUASTATUS_NONFATAL_ERR;\n}\n\nstatic int set_error(LuastatusBarlibData *bd, size_t widget_idx)\n{\n    Priv *p = bd->priv;\n    LS_String *s = &p->bufs[widget_idx];\n\n    ls_string_assign_s(\n        s, \"{\\\"full_text\\\":\\\"(Error)\\\",\\\"color\\\":\\\"#ff0000\\\",\\\"background\\\":\\\"#000000\\\"\");\n    if (p->noseps) {\n        ls_string_append_s(s, \",\\\"separator\\\":false\");\n    }\n    ls_string_append_c(s, '}');\n\n    if (!redraw(bd)) {\n        return LUASTATUS_ERR;\n    }\n    return LUASTATUS_OK;\n}\n\nLuastatusBarlibIface luastatus_barlib_iface_v1 = {\n    .init = init,\n    .register_funcs = register_funcs,\n    .set = set,\n    .set_error = set_error,\n    .event_watcher = event_watcher,\n    .destroy = destroy,\n};\n"
  },
  {
    "path": "barlibs/i3/luastatus-i3-wrapper",
    "content": "#!/bin/sh\nexec ${LUASTATUS:-luastatus} -b i3 -B in_fd=3 -B out_fd=4 \"$@\" 3<&0 0</dev/null 4>&1 1>&2\n"
  },
  {
    "path": "barlibs/i3/pango_escape.c",
    "content": "/*\n * Copyright (C) 2025  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#include \"pango_escape.h\"\n#include <stddef.h>\n#include \"libsafe/safev.h\"\n\n#define LIT(StrLit_) SAFEV_new_from_literal(StrLit_)\n\nvoid pango_escape(\n    SAFEV v,\n    void (*append)(void *ud, SAFEV segment),\n    void *ud)\n{\n    size_t n = SAFEV_len(v);\n    size_t prev = 0;\n    for (size_t i = 0; i < n; ++i) {\n        SAFEV esc = {0};\n        switch (SAFEV_at(v, i)) {\n        case '&':  esc = LIT(\"&amp;\");   break;\n        case '<':  esc = LIT(\"&lt;\");    break;\n        case '>':  esc = LIT(\"&gt;\");    break;\n        case '\\'': esc = LIT(\"&apos;\");  break;\n        case '\"':  esc = LIT(\"&quot;\");  break;\n        default: continue;\n        }\n        append(ud, SAFEV_subspan(v, prev, i));\n        append(ud, esc);\n        prev = i + 1;\n    }\n    append(ud, SAFEV_suffix(v, prev));\n}\n"
  },
  {
    "path": "barlibs/i3/pango_escape.h",
    "content": "/*\n * Copyright (C) 2025  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifndef pango_escape_h_\n#define pango_escape_h_\n\n#include \"libsafe/safev.h\"\n\nvoid pango_escape(\n    SAFEV v,\n    void (*append)(void *ud, SAFEV segment),\n    void *ud);\n\n#endif\n"
  },
  {
    "path": "barlibs/i3/priv.h",
    "content": "/*\n * Copyright (C) 2015-2025  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifndef priv_h_\n#define priv_h_\n\n#include <stdio.h>\n#include <stdbool.h>\n#include <stddef.h>\n\n#include \"libls/ls_string.h\"\n\ntypedef struct {\n    size_t nwidgets;\n\n    LS_String *bufs;\n\n    // Temporary buffer for secondary buffering, to avoid unneeded redraws.\n    LS_String tmpbuf;\n\n    // Input file descriptor.\n    int in_fd;\n\n    // /fdopen/'ed output file descritor.\n    FILE *out;\n\n    bool noclickev;\n\n    bool noseps;\n} Priv;\n\n#endif\n"
  },
  {
    "path": "barlibs/lemonbar/CMakeLists.txt",
    "content": "file (GLOB sources \"*.c\")\nluastatus_add_barlib (\n    barlib-lemonbar\n    $<TARGET_OBJECTS:ls>\n    $<TARGET_OBJECTS:safe>\n    ${sources}\n)\n\ntarget_compile_definitions (barlib-lemonbar PUBLIC -D_POSIX_C_SOURCE=200809L)\nluastatus_target_compile_with (barlib-lemonbar LUA)\ntarget_include_directories (barlib-lemonbar PUBLIC \"${PROJECT_SOURCE_DIR}\")\n\ninclude (GNUInstallDirs)\n\ninstall (PROGRAMS luastatus-lemonbar-launcher DESTINATION ${CMAKE_INSTALL_BINDIR})\n\nluastatus_add_man_page (README.rst luastatus-barlib-lemonbar 7)\n"
  },
  {
    "path": "barlibs/lemonbar/README.rst",
    "content": ".. :X-man-page-only: luastatus-barlib-lemonbar\n.. :X-man-page-only: #########################\n.. :X-man-page-only:\n.. :X-man-page-only: #############################\n.. :X-man-page-only: lemonbar barlib for luastatus\n.. :X-man-page-only: #############################\n.. :X-man-page-only:\n.. :X-man-page-only: :Copyright: LGPLv3\n.. :X-man-page-only: :Manual section: 7\n\nOverview\n========\nThis barlib talks with ``lemonbar``.\n\nIt joins all non-empty strings returned by widgets by a separator, which defaults to ``\" | \"``.\n\nRedirections and ``luastatus-lemonbar-launcher``\n================================================\n``lemonbar`` is not capable of creating a bidirectional pipe itself; instead, it requires all the\ndata to be written to its stdin and read from its stdout.\n\nThat's why the input/output file descriptors of this barlib must be manually redirected.\n\nA launcher, ``luastatus-lemonbar-launcher``, is shipped with it; it spawns ``lemonbar`` connected to\na bidirectional pipe and executes ``luastatus`` with ``-b lemonbar``, all the required ``-B``\noptions, and additional arguments passed by you.\n\nPass each ``lemonbar`` argument with ``-p``, then pass ``--``, then pass luastatus arguments, e.g.::\n\n   luastatus-lemonbar-launcher -p -B#111111 -p -f'Droid Sans Mono for Powerline:pixelsize=12:weight=Bold' -- -Bseparator=' ' widget1.lua widget2.lua\n\n``cb`` return value\n===================\nEither of:\n\n* a string with lemonbar markup\n\n  An empty string hides the widget.\n\n* an array of such strings\n\n  Equivalent to returning a string with all non-empty elements of the array joined by the\n  separator.\n\n* ``nil``\n\n  Hides the widget.\n\nFunctions\n=========\nThe following functions are provided:\n\n* ``escaped_str = luastatus.barlib.escape(str)``\n\n  Escapes text for lemonbar markup.\n\n``event`` argument\n==================\nA string with the name of the command.\n\nOptions\n=======\nThe following options are supported:\n\n* ``in_fd=<fd>``\n\n  File descriptor to read ``lemonbar`` input from. Usually set by the launcher.\n\n* ``out_fd=<fd>``\n\n  File descriptor to write to. Usually set by the launcher.\n\n* ``separator=<string>``\n\n  Set the separator.\n"
  },
  {
    "path": "barlibs/lemonbar/fuzz_esc/.gitignore",
    "content": "harness\nfindings\n"
  },
  {
    "path": "barlibs/lemonbar/fuzz_esc/build.sh",
    "content": "#!/bin/sh\n\nif [ -z \"$CC\" ]; then\n    echo >&2 \"You must set the 'CC' environment variable.\"\n    echo >&2 \"Hint: you probably want to set 'CC' to 'some-directory/afl-gcc'.\"\n    exit 1\nfi\n\ncd -- \"$(dirname \"$(readlink \"$0\" || printf '%s\\n' \"$0\")\")\"\n\nluastatus_root=../../..\n\n$CC -Wall -Wextra -O3 -fsanitize=undefined -std=c99 -D_POSIX_C_SOURCE=200809L \\\n    -I\"$luastatus_root\" \\\n    ./harness.c \\\n    ../markup_utils.c \\\n    \"$luastatus_root\"/libls/ls_string.c \\\n    \"$luastatus_root\"/libls/ls_alloc_utils.c \\\n    \"$luastatus_root\"/libls/ls_panic.c \\\n    \"$luastatus_root\"/libls/ls_cstring_utils.c \\\n    \"$luastatus_root\"/libls/ls_parse_int.c \\\n    \"$luastatus_root\"/libsafe/*.c \\\n    -o harness\n"
  },
  {
    "path": "barlibs/lemonbar/fuzz_esc/clear.sh",
    "content": "#!/bin/sh\n\nset -e\n\ncd -- \"$(dirname \"$(readlink \"$0\" || printf '%s\\n' \"$0\")\")\"\n\nrm -rf ./findings\n"
  },
  {
    "path": "barlibs/lemonbar/fuzz_esc/fuzz.sh",
    "content": "#!/bin/sh\n\nset -e\n\nif [ -z \"$XXX_AFL_DIR\" ]; then\n    echo >&2 \"You must set the 'XXX_AFL_DIR' environment variable.\"\n    exit 1\nfi\n\ncd -- \"$(dirname \"$(readlink \"$0\" || printf '%s\\n' \"$0\")\")\"\n\nmkdir -p ./findings\n\nexport UBSAN_OPTIONS=halt_on_error=1\n\nexport AFL_EXIT_WHEN_DONE=1\n\n\"$XXX_AFL_DIR\"/afl-fuzz -i testcases -o findings -t 5 ./harness @@\n"
  },
  {
    "path": "barlibs/lemonbar/fuzz_esc/gen_testcases.sh",
    "content": "#!/bin/sh\n\nset -e\n\ncd -- \"$(dirname \"$(readlink \"$0\" || printf '%s\\n' \"$0\")\")\"\n\nluastatus_root=../../..\n\n\"$luastatus_root\"/fuzz_utils/gen_testcases/gen_testcases.py \\\n    ./testcases \\\n    --a=1:abc \\\n    --b=1:'%' \\\n    --length=5-20 \\\n    --num-files=10 \\\n    --random-seed=123\n"
  },
  {
    "path": "barlibs/lemonbar/fuzz_esc/harness.c",
    "content": "/*\n * Copyright (C) 2025  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#include <stdio.h>\n#include <stdlib.h>\n#include <unistd.h>\n#include <fcntl.h>\n\n#include \"libls/ls_string.h\"\n#include \"libsafe/safev.h\"\n#include \"fuzz_utils/fuzz_utils.h\"\n\n#include \"../markup_utils.h\"\n\nstatic void append_to_ls_string(void *ud, SAFEV segment)\n{\n    LS_String *dst = ud;\n    ls_string_append_b(dst, SAFEV_ptr_UNSAFE(segment), SAFEV_len(segment));\n}\n\nint main(int argc, char **argv)\n{\n    if (argc != 2) {\n        fprintf(stderr, \"USAGE: harness INPUT_FILE\\n\");\n        return 2;\n    }\n\n    int fd_in = open(argv[1], O_RDONLY | O_CLOEXEC);\n    if (fd_in < 0) {\n        perror(argv[1]);\n        abort();\n    }\n\n    FuzzInput input = fuzz_input_new_prealloc(1024);\n    if (fuzz_input_read(fd_in, &input) < 0) {\n        perror(\"read\");\n        abort();\n    }\n\n    LS_String res = ls_string_new_from_s(\"escape result = \");\n    escape(\n        append_to_ls_string,\n        &res,\n        SAFEV_new_UNSAFE(input.data, input.size));\n\n    fuzz_utils_used(res.data, res.size);\n\n    fuzz_input_free(input);\n    ls_string_free(res);\n    close(fd_in);\n\n    return 0;\n}\n"
  },
  {
    "path": "barlibs/lemonbar/fuzz_esc/testcases/testcase_000",
    "content": "acabca"
  },
  {
    "path": "barlibs/lemonbar/fuzz_esc/testcases/testcase_001",
    "content": "abbc%cccbbacacb%cc"
  },
  {
    "path": "barlibs/lemonbar/fuzz_esc/testcases/testcase_002",
    "content": "ccacb%a%%aacbcc"
  },
  {
    "path": "barlibs/lemonbar/fuzz_esc/testcases/testcase_003",
    "content": "%c%abb%%%ccb%abcb"
  },
  {
    "path": "barlibs/lemonbar/fuzz_esc/testcases/testcase_004",
    "content": "%a%ca%b%"
  },
  {
    "path": "barlibs/lemonbar/fuzz_esc/testcases/testcase_005",
    "content": "%a%cbc%%%"
  },
  {
    "path": "barlibs/lemonbar/fuzz_esc/testcases/testcase_006",
    "content": "%b%b%b%%c%%%%b%"
  },
  {
    "path": "barlibs/lemonbar/fuzz_esc/testcases/testcase_007",
    "content": "%%%%%%%%bb"
  },
  {
    "path": "barlibs/lemonbar/fuzz_esc/testcases/testcase_008",
    "content": "%%%%b%%%%%%%%%c%"
  },
  {
    "path": "barlibs/lemonbar/fuzz_esc/testcases/testcase_009",
    "content": "%%%%%%"
  },
  {
    "path": "barlibs/lemonbar/fuzz_sanitize/.gitignore",
    "content": "harness\nfindings\n"
  },
  {
    "path": "barlibs/lemonbar/fuzz_sanitize/build.sh",
    "content": "#!/bin/sh\n\nif [ -z \"$CC\" ]; then\n    echo >&2 \"You must set the 'CC' environment variable.\"\n    echo >&2 \"Hint: you probably want to set 'CC' to 'some-directory/afl-gcc'.\"\n    exit 1\nfi\n\ncd -- \"$(dirname \"$(readlink \"$0\" || printf '%s\\n' \"$0\")\")\"\n\nluastatus_root=../../..\n\n$CC -Wall -Wextra -O3 -fsanitize=undefined -std=c99 -D_POSIX_C_SOURCE=200809L \\\n    -I\"$luastatus_root\" \\\n    ./harness.c \\\n    ../markup_utils.c \\\n    \"$luastatus_root\"/libls/ls_string.c \\\n    \"$luastatus_root\"/libls/ls_alloc_utils.c \\\n    \"$luastatus_root\"/libls/ls_panic.c \\\n    \"$luastatus_root\"/libls/ls_cstring_utils.c \\\n    \"$luastatus_root\"/libls/ls_parse_int.c \\\n    \"$luastatus_root\"/libsafe/*.c \\\n    -o harness\n"
  },
  {
    "path": "barlibs/lemonbar/fuzz_sanitize/clear.sh",
    "content": "#!/bin/sh\n\nset -e\n\ncd -- \"$(dirname \"$(readlink \"$0\" || printf '%s\\n' \"$0\")\")\"\n\nrm -rf ./findings\n"
  },
  {
    "path": "barlibs/lemonbar/fuzz_sanitize/fuzz.sh",
    "content": "#!/bin/sh\n\nset -e\n\nif [ -z \"$XXX_AFL_DIR\" ]; then\n    echo >&2 \"You must set the 'XXX_AFL_DIR' environment variable.\"\n    exit 1\nfi\n\n# We add '-d' argument for original AFL because otherwise it\n# does not terminate within 24 hours.\n#\n# '-d' means \"quick & dirty mode (skips deterministic steps)\".\n\ncase \"$XXX_AFL_IS_PP\" in\n0)\n    extra_opts='-d'\n    ;;\n1)\n    extra_opts=\n    ;;\n*)\n    echo >&2 \"You must set 'XXX_AFL_IS_PP' environment variable to either 0 or 1.\"\n    echo >&2 \"Set it to 0 if AFL is the original Google's version (not AFL++).\"\n    echo >&2 \"Set it to 1 if AFL is actually AFL++.\"\n    exit 1\nesac\n\n# We also set AFL_NO_ARITH=1 because it's a text-based format.\n# This potentially speeds up fuzzing.\nexport AFL_NO_ARITH=1\n\ncd -- \"$(dirname \"$(readlink \"$0\" || printf '%s\\n' \"$0\")\")\"\n\nmkdir -p ./findings\n\nexport UBSAN_OPTIONS=halt_on_error=1\n\nexport AFL_EXIT_WHEN_DONE=1\n\n\"$XXX_AFL_DIR\"/afl-fuzz $extra_opts -i testcases -o findings -t 5 ./harness @@\n"
  },
  {
    "path": "barlibs/lemonbar/fuzz_sanitize/gen_testcases.sh",
    "content": "#!/bin/sh\n\nset -e\n\ncd -- \"$(dirname \"$(readlink \"$0\" || printf '%s\\n' \"$0\")\")\"\n\nluastatus_root=../../..\n\nnl=$(printf '\\nx')\nnl=${nl%x}\n\n# 'fps' means 'final percent sign'\n\"$luastatus_root\"/fuzz_utils/gen_testcases/gen_testcases.py \\\n    ./testcases \\\n    --file-prefix='X_' \\\n    --a=1:x \\\n    --b=1:x \\\n    --mut-substrings=\"|${nl}|}|%{A|%{x|%x|%%\" \\\n    --length=10 \\\n    --num-files=6 \\\n    --random-seed=123 \\\n    --extra-testcase='fps:x%'\n\n\"$luastatus_root\"/fuzz_utils/gen_testcases/gen_testcases.py \\\n    ./testcases \\\n    --file-prefix='Y_' \\\n    --a=1:x \\\n    --b=1:x \\\n    --mut-prefix='%{A' \\\n    --mut-substrings='|:' \\\n    --length=10 \\\n    --num-files=4 \\\n    --random-seed=123\n\n\"$luastatus_root\"/fuzz_utils/gen_testcases/gen_testcases.py \\\n    ./testcases \\\n    --file-prefix='Z_' \\\n    --a=1:x \\\n    --b=1:x \\\n    --mut-prefix='x%{A' \\\n    --mut-substrings='|:' \\\n    --length=10 \\\n    --num-files=5 \\\n    --random-seed=123\n"
  },
  {
    "path": "barlibs/lemonbar/fuzz_sanitize/harness.c",
    "content": "/*\n * Copyright (C) 2025  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#include <stdio.h>\n#include <stdlib.h>\n#include <unistd.h>\n#include <fcntl.h>\n\n#include \"libls/ls_string.h\"\n#include \"libsafe/safev.h\"\n#include \"fuzz_utils/fuzz_utils.h\"\n\n#include \"../markup_utils.h\"\n\nint main(int argc, char **argv)\n{\n    if (argc != 2) {\n        fprintf(stderr, \"USAGE: harness INPUT_FILE\\n\");\n        return 2;\n    }\n\n    int fd_in = open(argv[1], O_RDONLY | O_CLOEXEC);\n    if (fd_in < 0) {\n        perror(argv[1]);\n        abort();\n    }\n\n    FuzzInput input = fuzz_input_new_prealloc(1024);\n    if (fuzz_input_read(fd_in, &input) < 0) {\n        perror(\"read\");\n        abort();\n    }\n\n    LS_String res = ls_string_new_reserve(2048);\n    ls_string_append_s(&res, \"sanitize result = \");\n\n    append_sanitized(\n        &res,\n        123,\n        SAFEV_new_UNSAFE(input.data, input.size));\n\n    fuzz_utils_used(res.data, res.size);\n\n    fuzz_input_free(input);\n    ls_string_free(res);\n    close(fd_in);\n\n    return 0;\n}\n"
  },
  {
    "path": "barlibs/lemonbar/fuzz_sanitize/testcases/X_testcase_000",
    "content": "xxxx\nxxxxxx"
  },
  {
    "path": "barlibs/lemonbar/fuzz_sanitize/testcases/X_testcase_001",
    "content": "xxxxxxx}xxx"
  },
  {
    "path": "barlibs/lemonbar/fuzz_sanitize/testcases/X_testcase_002",
    "content": "xxxxxxxxxx%{A"
  },
  {
    "path": "barlibs/lemonbar/fuzz_sanitize/testcases/X_testcase_003",
    "content": "xxxxx%{xxxxxx"
  },
  {
    "path": "barlibs/lemonbar/fuzz_sanitize/testcases/X_testcase_004",
    "content": "xxxxxxxx%xxx"
  },
  {
    "path": "barlibs/lemonbar/fuzz_sanitize/testcases/X_testcase_005",
    "content": "xxxxxxxxx%%x"
  },
  {
    "path": "barlibs/lemonbar/fuzz_sanitize/testcases/X_testcase_fps",
    "content": "x%"
  },
  {
    "path": "barlibs/lemonbar/fuzz_sanitize/testcases/Y_testcase_000",
    "content": "xxxx:xxxxxx"
  },
  {
    "path": "barlibs/lemonbar/fuzz_sanitize/testcases/Y_testcase_001",
    "content": "%xxxxxxx:xxx"
  },
  {
    "path": "barlibs/lemonbar/fuzz_sanitize/testcases/Y_testcase_002",
    "content": "%{xxxxxxxxxx:"
  },
  {
    "path": "barlibs/lemonbar/fuzz_sanitize/testcases/Y_testcase_003",
    "content": "%{Axxxxx:xxxxx"
  },
  {
    "path": "barlibs/lemonbar/fuzz_sanitize/testcases/Z_testcase_000",
    "content": "xxxx:xxxxxx"
  },
  {
    "path": "barlibs/lemonbar/fuzz_sanitize/testcases/Z_testcase_001",
    "content": "xxxxxxxx:xxx"
  },
  {
    "path": "barlibs/lemonbar/fuzz_sanitize/testcases/Z_testcase_002",
    "content": "x%xxxxxxxxxx:"
  },
  {
    "path": "barlibs/lemonbar/fuzz_sanitize/testcases/Z_testcase_003",
    "content": "x%{xxxxx:xxxxx"
  },
  {
    "path": "barlibs/lemonbar/fuzz_sanitize/testcases/Z_testcase_004",
    "content": "x%{Axxxxxxxx:xx"
  },
  {
    "path": "barlibs/lemonbar/lemonbar.c",
    "content": "/*\n * Copyright (C) 2015-2025  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#include <stddef.h>\n#include <sys/types.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <lua.h>\n#include <lauxlib.h>\n#include <errno.h>\n#include <stdbool.h>\n\n#include \"include/barlib_v1.h\"\n#include \"include/sayf_macros.h\"\n\n#include \"libls/ls_string.h\"\n#include \"libls/ls_cstring_utils.h\"\n#include \"libls/ls_tls_ebuf.h\"\n#include \"libls/ls_parse_int.h\"\n#include \"libls/ls_io_utils.h\"\n#include \"libls/ls_alloc_utils.h\"\n#include \"libls/ls_lua_compat.h\"\n#include \"libsafe/safev.h\"\n\n#include \"markup_utils.h\"\n\ntypedef struct {\n    size_t nwidgets;\n\n    LS_String *bufs;\n\n    // Temporary buffer for secondary buffering, to avoid unneeded redraws.\n    LS_String tmpbuf;\n\n    char *sep;\n\n    // /fdopen/'ed input file descriptor.\n    FILE *in;\n\n    // /fdopen/'ed output file descriptor.\n    FILE *out;\n} Priv;\n\nstatic void destroy(LuastatusBarlibData *bd)\n{\n    Priv *p = bd->priv;\n    for (size_t i = 0; i < p->nwidgets; ++i)\n        ls_string_free(p->bufs[i]);\n    free(p->bufs);\n    ls_string_free(p->tmpbuf);\n    free(p->sep);\n    if (p->in)\n        fclose(p->in);\n    if (p->out)\n        fclose(p->out);\n    free(p);\n}\n\nstatic int init(LuastatusBarlibData *bd, const char *const *opts, size_t nwidgets)\n{\n    Priv *p = bd->priv = LS_XNEW(Priv, 1);\n    *p = (Priv) {\n        .nwidgets = nwidgets,\n        .bufs = LS_XNEW(LS_String, nwidgets),\n        .tmpbuf = ls_string_new_reserve(512),\n        .sep = NULL,\n        .in = NULL,\n        .out = NULL,\n    };\n    for (size_t i = 0; i < nwidgets; ++i)\n        p->bufs[i] = ls_string_new_reserve(512);\n\n    // All the options may be passed multiple times!\n    const char *sep = NULL;\n    int in_fd = -1;\n    int out_fd = -1;\n    for (const char *const *s = opts; *s; ++s) {\n        const char *v;\n        if ((v = ls_strfollow(*s, \"in_fd=\"))) {\n            if ((in_fd = ls_full_strtou(v)) < 0) {\n                LS_FATALF(bd, \"in_fd value is not a valid unsigned integer\");\n                goto error;\n            }\n        } else if ((v = ls_strfollow(*s, \"out_fd=\"))) {\n            if ((out_fd = ls_full_strtou(v)) < 0) {\n                LS_FATALF(bd, \"out_fd value is not a valid unsigned integer\");\n                goto error;\n            }\n        } else if ((v = ls_strfollow(*s, \"separator=\"))) {\n            sep = v;\n        } else {\n            LS_FATALF(bd, \"unknown option '%s'\", *s);\n            goto error;\n        }\n    }\n    p->sep = ls_xstrdup(sep ? sep : \" | \");\n\n    // we require /in_fd/ and /out_fd/ to be >=3 because making stdin/stdout/stderr CLOEXEC has very\n    // bad consequences, and we just don't want to complicate the logic.\n    if (in_fd < 3) {\n        LS_FATALF(bd, \"in_fd is not specified or less than 3\");\n        goto error;\n    }\n    if (out_fd < 3) {\n        LS_FATALF(bd, \"out_fd is not specified or less than 3\");\n        goto error;\n    }\n\n    // open\n    if (!(p->in = fdopen(in_fd, \"r\"))) {\n        LS_FATALF(bd, \"can't fdopen %d: %s\", in_fd, ls_tls_strerror(errno));\n        goto error;\n    }\n    if (!(p->out = fdopen(out_fd, \"w\"))) {\n        LS_FATALF(bd, \"can't fdopen %d: %s\", out_fd, ls_tls_strerror(errno));\n        goto error;\n    }\n\n    // make CLOEXEC\n    if (ls_make_cloexec(in_fd) < 0) {\n        LS_FATALF(bd, \"can't make fd %d CLOEXEC: %s\", in_fd, ls_tls_strerror(errno));\n        goto error;\n    }\n    if (ls_make_cloexec(out_fd) < 0) {\n        LS_FATALF(bd, \"can't make fd %d CLOEXEC: %s\", out_fd, ls_tls_strerror(errno));\n        goto error;\n    }\n\n    return LUASTATUS_OK;\n\nerror:\n    destroy(bd);\n    return LUASTATUS_ERR;\n}\n\nstatic void append_to_lua_buf_callback(void *ud, SAFEV v)\n{\n    luaL_Buffer *b = ud;\n    luaL_addlstring(b, SAFEV_ptr_UNSAFE(v), SAFEV_len(v));\n}\n\nstatic int l_escape(lua_State *L)\n{\n    size_t ns;\n    // WARNING: /luaL_check*()/ functions do a long jump on error!\n    const char *s = luaL_checklstring(L, 1, &ns);\n\n    luaL_Buffer b;\n    luaL_buffinit(L, &b);\n\n    escape(append_to_lua_buf_callback, &b, SAFEV_new_UNSAFE(s, ns));\n\n    luaL_pushresult(&b); // L: result\n\n    return 1;\n}\n\nstatic void register_funcs(LuastatusBarlibData *bd, lua_State *L)\n{\n    (void) bd;\n    // L: table\n    lua_pushcfunction(L, l_escape); // L: table l_escape\n    lua_setfield(L, -2, \"escape\"); // L: table\n}\n\nstatic bool redraw(LuastatusBarlibData *bd)\n{\n    Priv *p = bd->priv;\n    FILE *out = p->out;\n    size_t n = p->nwidgets;\n    LS_String *bufs = p->bufs;\n    const char *sep = p->sep;\n\n    bool first = true;\n    for (size_t i = 0; i < n; ++i) {\n        if (bufs[i].size) {\n            if (!first) {\n                fputs(sep, out);\n            }\n            fwrite(bufs[i].data, 1, bufs[i].size, out);\n            first = false;\n        }\n    }\n    putc('\\n', out);\n    fflush(out);\n    if (ferror(out)) {\n        LS_FATALF(bd, \"write error: %s\", ls_tls_strerror(errno));\n        return false;\n    }\n    return true;\n}\n\nstatic int set(LuastatusBarlibData *bd, lua_State *L, size_t widget_idx)\n{\n    Priv *p = bd->priv;\n    LS_String *buf = &p->tmpbuf;\n    ls_string_clear(buf);\n\n    // L: ? data\n\n    switch (lua_type(L, -1)) {\n    case LUA_TNIL:\n        break;\n\n    case LUA_TSTRING:\n        {\n            size_t ns;\n            const char *s = lua_tolstring(L, -1, &ns);\n            append_sanitized(buf, widget_idx, SAFEV_new_UNSAFE(s, ns));\n        }\n        break;\n\n    case LUA_TTABLE:\n        {\n            const char *sep = p->sep;\n\n            size_t len = ls_lua_array_len(L, -1);\n            for (size_t i = 1; i <= len; ++i) {\n                lua_rawgeti(L, -1, i); // L: ? data value\n                if (lua_isnil(L, -1)) {\n                    goto next;\n                }\n                if (!lua_isstring(L, -1)) {\n                    LS_ERRF(bd, \"table value: expected string, found %s\", luaL_typename(L, -1));\n                    goto invalid_data;\n                }\n                size_t ns;\n                const char *s = lua_tolstring(L, -1, &ns);\n                if (buf->size && ns) {\n                    ls_string_append_s(buf, sep);\n                }\n                append_sanitized(buf, widget_idx, SAFEV_new_UNSAFE(s, ns));\nnext:\n                lua_pop(L, 1); // L: ? data\n            }\n            // L: ? data\n        }\n        break;\n\n    default:\n        LS_ERRF(bd, \"expected string, table or nil, found %s\", luaL_typename(L, -1));\n        goto invalid_data;\n    }\n\n    if (!ls_string_eq(*buf, p->bufs[widget_idx])) {\n        ls_string_swap(buf, &p->bufs[widget_idx]);\n        if (!redraw(bd)) {\n            return LUASTATUS_ERR;\n        }\n    }\n    return LUASTATUS_OK;\n\ninvalid_data:\n    ls_string_clear(&p->bufs[widget_idx]);\n    return LUASTATUS_NONFATAL_ERR;\n}\n\nstatic int set_error(LuastatusBarlibData *bd, size_t widget_idx)\n{\n    Priv *p = bd->priv;\n    ls_string_assign_s(&p->bufs[widget_idx], \"%{B#f00}%{F#fff}(Error)%{B-}%{F-}\");\n    if (!redraw(bd)) {\n        return LUASTATUS_ERR;\n    }\n    return LUASTATUS_OK;\n}\n\nstatic int event_watcher(LuastatusBarlibData *bd, LuastatusBarlibEWFuncs funcs)\n{\n    Priv *p = bd->priv;\n\n    char *buf = NULL;\n    size_t nbuf = 256;\n\n    for (ssize_t nread; (nread = getline(&buf, &nbuf, p->in)) >= 0;) {\n        if (nread == 0 || buf[nread - 1] != '\\n')\n            continue;\n\n        size_t ncommand;\n        size_t widget_idx;\n        const char *command = parse_command(buf, nread - 1, &ncommand, &widget_idx);\n        if (!command)\n            continue;\n        if (widget_idx >= p->nwidgets) {\n            continue;\n        }\n        lua_State *L = funcs.call_begin(bd->userdata, widget_idx);\n        lua_pushlstring(L, command, ncommand);\n        funcs.call_end(bd->userdata, widget_idx);\n    }\n\n    if (feof(p->in)) {\n        LS_ERRF(bd, \"lemonbar closed its pipe end\");\n    } else {\n        LS_ERRF(bd, \"read error: %s\", ls_tls_strerror(errno));\n    }\n\n    free(buf);\n\n    return LUASTATUS_ERR;\n}\n\nLuastatusBarlibIface luastatus_barlib_iface_v1 = {\n    .init = init,\n    .register_funcs = register_funcs,\n    .set = set,\n    .set_error = set_error,\n    .event_watcher = event_watcher,\n    .destroy = destroy,\n};\n"
  },
  {
    "path": "barlibs/lemonbar/luastatus-lemonbar-launcher",
    "content": "#!/usr/bin/env bash\n\nset -e\n\nif (( BASH_VERSINFO[0] < 4 )); then\n    echo >&2 \"bash 4.0 or higher is required (this one is $BASH_VERSION).\"\n    exit 1\nfi\n\nusage() {\n    printf '%s\\n' >&2 \"$*\nUSAGE: ${0##*/} [-p lemonbar_argument [-p ...]] -- [luastatus_argument [...]]\nNote that '--' is mandatory.\"\n    exit 2\n}\n\nlemonbar_args=()\n# There is no easy way to require '--' with getopts, so we parse $@ manually.\nwhile true; do\n    if (( $# == 0 )); then\n        usage \"'--' argument not found.\"\n    fi\n    case \"$1\" in\n    --)\n        shift\n        break\n        ;;\n    -p)\n        if (( $# == 1 )); then\n            usage \"'-p' option requires an argument.\"\n        fi\n        shift\n        lemonbar_args+=(\"$1\")\n        ;;\n    -p*)\n        lemonbar_args+=(\"${1:2}\")\n        ;;\n    *)\n        usage \"Unexpected argument '$1' found before '--'.\"\n        ;;\n    esac\n    shift\ndone\n\ncoproc ${LEMONBAR:-lemonbar} \"${lemonbar_args[@]}\"\nexec <&${COPROC[0]} >&${COPROC[1]}\nexec ${LUASTATUS:-luastatus} -b lemonbar -B in_fd=3 -B out_fd=4 \"$@\" 3<&0 0</dev/null 4>&1 1>&2\n"
  },
  {
    "path": "barlibs/lemonbar/markup_utils.c",
    "content": "/*\n * Copyright (C) 2015-2025  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#include \"markup_utils.h\"\n\n#include \"libls/ls_string.h\"\n#include \"libls/ls_parse_int.h\"\n#include \"libsafe/safev.h\"\n\n#include <stddef.h>\n#include <stdbool.h>\n\nvoid escape(\n    void (*append)(void *ud, SAFEV segment),\n    void *ud,\n    SAFEV v)\n{\n    // just replace all \"%\"s with \"%%\"\n    for (;;) {\n        size_t i = SAFEV_index_of(v, '%');\n        if (i == (size_t) -1) {\n            break;\n        }\n        append(ud, SAFEV_subspan(v, 0, i));\n        append(ud, SAFEV_new_from_literal(\"%%\"));\n        v = SAFEV_suffix(v, i + 1);\n    }\n    append(ud, v);\n}\n\nstatic inline void append_sv(LS_String *dst, SAFEV v)\n{\n    ls_string_append_b(dst, SAFEV_ptr_UNSAFE(v), SAFEV_len(v));\n}\n\nvoid append_sanitized(LS_String *buf, size_t widget_idx, SAFEV v)\n{\n    size_t n = SAFEV_len(v);\n\n    size_t prev = 0;\n    bool a_tag = false;\n    for (size_t i = 0; i < n; ++i) {\n\n#define DO_PREV(WhetherToIncludeThis_) \\\n    do { \\\n        size_t j__ = i + ((WhetherToIncludeThis_) ? 1 : 0); \\\n        append_sv(buf, SAFEV_subspan(v, prev, j__)); \\\n        prev = i + 1; \\\n    } while (0)\n\n#define PEEK(Offset_) SAFEV_at_or(v, i + (Offset_), '\\0')\n\n        switch (SAFEV_at(v, i)) {\n        case '\\n':\n            DO_PREV(false);\n            break;\n\n        case '%':\n            if (PEEK(1) == '{' && PEEK(2) == 'A') {\n                a_tag = true;\n            } else if (PEEK(1) == '%') {\n                ++i;\n            }\n            break;\n\n        case ':':\n            if (a_tag) {\n                DO_PREV(true);\n                ls_string_append_f(buf, \"%zu_\", widget_idx);\n                a_tag = false;\n            }\n            break;\n\n        case '}':\n            a_tag = false;\n            break;\n        }\n\n#undef DO_PREV\n#undef PEEK\n\n    }\n\n    append_sv(buf, SAFEV_suffix(v, prev));\n}\n\nconst char *parse_command(const char *line, size_t nline, size_t *ncommand, size_t *widget_idx)\n{\n    const char *endptr;\n    int idx = ls_strtou_b(line, nline, &endptr);\n    if (idx < 0 ||\n        endptr == line ||\n        endptr == line + nline ||\n        *endptr != '_')\n    {\n        return NULL;\n    }\n    const char *command = endptr + 1;\n    *ncommand = nline - (command - line);\n    *widget_idx = idx;\n    return command;\n}\n"
  },
  {
    "path": "barlibs/lemonbar/markup_utils.h",
    "content": "/*\n * Copyright (C) 2015-2025  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifndef markup_utils_h_\n#define markup_utils_h_\n\n#include <stddef.h>\n\n#include \"libls/ls_string.h\"\n#include \"libsafe/safev.h\"\n\nvoid escape(\n    void (*append)(void *ud, SAFEV segment),\n    void *ud,\n    SAFEV v);\n\nvoid append_sanitized(LS_String *buf, size_t widget_idx, SAFEV v);\n\nconst char *parse_command(const char *line, size_t nline, size_t *ncommand, size_t *widget_idx);\n\n#endif\n"
  },
  {
    "path": "barlibs/stdout/CMakeLists.txt",
    "content": "file (GLOB sources \"*.c\")\nluastatus_add_barlib (\n    barlib-stdout\n    $<TARGET_OBJECTS:ls>\n    $<TARGET_OBJECTS:safe>\n    ${sources}\n)\n\ntarget_compile_definitions (barlib-stdout PUBLIC -D_POSIX_C_SOURCE=200809L)\nluastatus_target_compile_with (barlib-stdout LUA)\ntarget_include_directories (barlib-stdout PUBLIC \"${PROJECT_SOURCE_DIR}\")\n\ninclude (GNUInstallDirs)\n\ninstall (PROGRAMS luastatus-stdout-wrapper DESTINATION ${CMAKE_INSTALL_BINDIR})\n\nluastatus_add_man_page (README.rst luastatus-barlib-stdout 7)\n"
  },
  {
    "path": "barlibs/stdout/README.rst",
    "content": ".. :X-man-page-only: luastatus-barlib-stdout\n.. :X-man-page-only: #######################\n.. :X-man-page-only:\n.. :X-man-page-only: ###########################\n.. :X-man-page-only: stdout barlib for luastatus\n.. :X-man-page-only: ###########################\n.. :X-man-page-only:\n.. :X-man-page-only: :Copyright: LGPLv3\n.. :X-man-page-only: :Manual section: 7\n\nOverview\n========\nThis barlib simply writes lines to a file descriptor.\n\nIt can be used for status bars such as **dzen**/**dzen2**, **xmobar**, **yabar**, **dvtm**, and\nothers.\n\nIt joins all non-empty strings returned by widgets by a separator, which defaults to ``\" | \"``.\n\nIt does not provide functions.\n\nRedirections and ``luastatus-stdout-wrapper``\n=============================================\nSince we need to write to stdout, it is very easy to mess things up: Lua's ``print()`` prints to\nstdout, processes spawned by widgets/plugins inherit our stdin and stdout, etc.\n\nThat's why this barlib requires that stdout file descriptor is manually redirected. A shell wrapper,\n``luastatus-stdout-wrapper``, is shipped with it; it does all the redirections needed and executes\n``luastatus`` with ``-b stdout`` and additional arguments passed by you.\n\nIt does not redirect stdin and/or pass ``in_fd=`` option. Make your own wrapper if this is needed.\n\n``cb`` return value\n===================\nEither of:\n\n* a string\n\n  An empty string hides the widget.\n\n* an array of strings\n\n  Equivalent to returning a string with all non-empty elements of the array joined by the\n  separator.\n\n* ``nil``\n\n  Hides the widget.\n\nOptions\n=======\nThe following options are supported:\n\n* ``out_fd=<fd>``\n\n  File descriptor to write to. Usually set by the wrapper.\n\n* ``separator=<string>``\n\n  Set the separator.\n\n* ``error=<string>``\n\n  Set the content of an \"error\" segment. Defaults to ``\"(Error)\"``.\n\n* ``in_filename=<string>`` or ``in_fd=<fd>``\n\n  Enable event watcher.\n\n  If ``in_filename=<string>`` is specified, this barlib will open the specified\n  filename for reading and then read from. If ``in_fd=<fd>`` is specified, the\n  specified file descriptor will be read from. It is invalid to pass both options;\n  in this case, this barlib will fail to initialize.\n\n  This barilib will then read lines from the specified source. Each line will be\n  treated as an event.\n\n  This barlib doesn't try to interpret the content of the line; instead, the event\n  will be broadcast to all widgets. Thus, if this option is used, it is the\n  responsibility of each widget (that listens to the events) to check if the event\n  is somehow related to it.\n\n``event`` argument\n==================\nEvents are only reported if either ``in_filename=`` or ``in_fd=`` option was\nspecified (see above). In this case, the argument is the line that was read from\nthe specified file, without the trailing newline character.\n"
  },
  {
    "path": "barlibs/stdout/fuzz/.gitignore",
    "content": "harness\nfindings\n"
  },
  {
    "path": "barlibs/stdout/fuzz/build.sh",
    "content": "#!/bin/sh\n\nif [ -z \"$CC\" ]; then\n    echo >&2 \"You must set the 'CC' environment variable.\"\n    echo >&2 \"Hint: you probably want to set 'CC' to 'some-directory/afl-gcc'.\"\n    exit 1\nfi\n\ncd -- \"$(dirname \"$(readlink \"$0\" || printf '%s\\n' \"$0\")\")\"\n\nluastatus_root=../../..\n\n$CC -Wall -Wextra -O3 -fsanitize=undefined -std=c99 -D_POSIX_C_SOURCE=200809L \\\n    -I\"$luastatus_root\" \\\n    ./harness.c \\\n    ../sanitize.c \\\n    \"$luastatus_root\"/libls/ls_string.c \\\n    \"$luastatus_root\"/libls/ls_alloc_utils.c \\\n    \"$luastatus_root\"/libls/ls_panic.c \\\n    \"$luastatus_root\"/libls/ls_cstring_utils.c \\\n    \"$luastatus_root\"/libsafe/*.c \\\n    -o harness\n"
  },
  {
    "path": "barlibs/stdout/fuzz/clear.sh",
    "content": "#!/bin/sh\n\nset -e\n\ncd -- \"$(dirname \"$(readlink \"$0\" || printf '%s\\n' \"$0\")\")\"\n\nrm -rf ./findings\n"
  },
  {
    "path": "barlibs/stdout/fuzz/fuzz.sh",
    "content": "#!/bin/sh\n\nset -e\n\nif [ -z \"$XXX_AFL_DIR\" ]; then\n    echo >&2 \"You must set the 'XXX_AFL_DIR' environment variable.\"\n    exit 1\nfi\n\ncd -- \"$(dirname \"$(readlink \"$0\" || printf '%s\\n' \"$0\")\")\"\n\nmkdir -p ./findings\n\nexport UBSAN_OPTIONS=halt_on_error=1\n\nexport AFL_EXIT_WHEN_DONE=1\n\n\"$XXX_AFL_DIR\"/afl-fuzz -i testcases -o findings -t 5 ./harness @@\n"
  },
  {
    "path": "barlibs/stdout/fuzz/gen_testcases.sh",
    "content": "#!/bin/sh\n\nset -e\n\ncd -- \"$(dirname \"$(readlink \"$0\" || printf '%s\\n' \"$0\")\")\"\n\nluastatus_root=../../..\n\nnl=$(printf '\\nx')\nnl=${nl%x}\n\n\"$luastatus_root\"/fuzz_utils/gen_testcases/gen_testcases.py \\\n    ./testcases \\\n    --a=1:\"$nl\" \\\n    --b=1:xyz \\\n    --a-is-important \\\n    --length=5-20 \\\n    --num-files=10 \\\n    --random-seed=123\n"
  },
  {
    "path": "barlibs/stdout/fuzz/harness.c",
    "content": "/*\n * Copyright (C) 2025  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#include <stdio.h>\n#include <stdlib.h>\n#include <unistd.h>\n#include <fcntl.h>\n\n#include \"libls/ls_string.h\"\n#include \"libsafe/safev.h\"\n#include \"fuzz_utils/fuzz_utils.h\"\n\n#include \"../sanitize.h\"\n\nint main(int argc, char **argv)\n{\n    if (argc != 2) {\n        fprintf(stderr, \"USAGE: harness INPUT_FILE\\n\");\n        return 2;\n    }\n\n    int fd_in = open(argv[1], O_RDONLY | O_CLOEXEC);\n    if (fd_in < 0) {\n        perror(argv[1]);\n        abort();\n    }\n\n    FuzzInput input = fuzz_input_new_prealloc(1024);\n    if (fuzz_input_read(fd_in, &input) < 0) {\n        perror(\"read\");\n        abort();\n    }\n\n    LS_String res = ls_string_new_from_s(\"sanitize result = \");\n    append_sanitized(&res, SAFEV_new_UNSAFE(input.data, input.size));\n\n    fuzz_utils_used(res.data, res.size);\n\n    fuzz_input_free(input);\n    ls_string_free(res);\n    close(fd_in);\n\n    return 0;\n}\n"
  },
  {
    "path": "barlibs/stdout/fuzz/testcases/testcase_000",
    "content": "\n\n\n\n\n\n"
  },
  {
    "path": "barlibs/stdout/fuzz/testcases/testcase_001",
    "content": "\n\n\n\ny\n\n\n\n\n"
  },
  {
    "path": "barlibs/stdout/fuzz/testcases/testcase_002",
    "content": "\n\n\n\n\n\n\nz\n\n\n\n\n\ny\nx\n\nx"
  },
  {
    "path": "barlibs/stdout/fuzz/testcases/testcase_003",
    "content": "xz\n\n\n\nz\nz\ny\n\n\n\nz\n"
  },
  {
    "path": "barlibs/stdout/fuzz/testcases/testcase_004",
    "content": "y\nx\n\nz\nz"
  },
  {
    "path": "barlibs/stdout/fuzz/testcases/testcase_005",
    "content": "z\n\nxx\nz\ny"
  },
  {
    "path": "barlibs/stdout/fuzz/testcases/testcase_006",
    "content": "x\ny\nz\nyy\nyyy\nyxx"
  },
  {
    "path": "barlibs/stdout/fuzz/testcases/testcase_007",
    "content": "\nxyyxyx\nzy\nzy\nzxyy"
  },
  {
    "path": "barlibs/stdout/fuzz/testcases/testcase_008",
    "content": "xxxxzxx\n"
  },
  {
    "path": "barlibs/stdout/fuzz/testcases/testcase_009",
    "content": "zyyyyzxyyxyxxx"
  },
  {
    "path": "barlibs/stdout/luastatus-dvtm",
    "content": "#!/bin/sh\n\nFIFO=~/.luastatus-dvtm\n\nset -e\n\nrm -f \"$FIFO\"\nmkfifo -m600 \"$FIFO\"\n\nexec ${LUASTATUS:-luastatus} -b stdout -B out_fd=3 3>\"$FIFO\" \"$@\" &\nstatus_pid=$!\n\n${DVTM:-dvtm} -s \"$FIFO\" 2>/dev/null\n\nkill \"$status_pid\"\nrm -f \"$FIFO\"\n"
  },
  {
    "path": "barlibs/stdout/luastatus-stdout-wrapper",
    "content": "#!/bin/sh\nexec ${LUASTATUS:-luastatus} -b stdout -B out_fd=3 \"$@\" 3>&1 1>&2\n"
  },
  {
    "path": "barlibs/stdout/open_stdio_file.c",
    "content": "/*\n * Copyright (C) 2026  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#include \"open_stdio_file.h\"\n\n#include <stdio.h>\n#include <stdbool.h>\n#include <unistd.h>\n#include <fcntl.h>\n\n#include \"libls/ls_io_utils.h\"\n#include \"libls/ls_tls_ebuf.h\"\n\n#include \"include/barlib_data_v1.h\"\n#include \"include/sayf_macros.h\"\n\nstatic bool open_input_fd(\n        LuastatusBarlibData *bd,\n        FILE **dst,\n        int fd)\n{\n    if (ls_make_cloexec(fd) < 0) {\n        LS_FATALF(bd, \"can't make fd %d (in_fd) CLOEXEC: %s\", fd, ls_tls_strerror(errno));\n        return false;\n    }\n\n    *dst = fdopen(fd, \"r\");\n    if (!*dst) {\n        LS_FATALF(bd, \"can't fdopen %d (in_fd): %s\", fd, ls_tls_strerror(errno));\n        return false;\n    }\n\n    return true;\n}\n\nstatic bool open_input_filename(\n        LuastatusBarlibData *bd,\n        FILE **dst,\n        const char *filename)\n{\n    int fd = open(filename, O_RDONLY | O_CLOEXEC);\n    if (fd < 0) {\n        LS_FATALF(bd, \"can't open '%s' (in_filename): %s\", filename, ls_tls_strerror(errno));\n        return false;\n    }\n\n    *dst = fdopen(fd, \"r\");\n    if (!*dst) {\n        LS_FATALF(bd, \"can't fdopen %d (opened in_filename): %s\", fd, ls_tls_strerror(errno));\n        close(fd);\n        return false;\n    }\n\n    return true;\n}\n\nbool open_input(\n        LuastatusBarlibData *bd,\n        FILE **dst,\n        int fd,\n        const char *filename)\n{\n    if (fd >= 0 && filename) {\n        LS_FATALF(bd, \"both in_fd and in_filename were specified\");\n        return false;\n    }\n    if (fd >= 0) {\n        return open_input_fd(bd, dst, fd);\n    }\n    if (filename) {\n        return open_input_filename(bd, dst, filename);\n    }\n    return true;\n}\n\nbool open_output(\n        LuastatusBarlibData *bd,\n        FILE **dst,\n        int fd)\n{\n    if (ls_make_cloexec(fd) < 0) {\n        LS_FATALF(bd, \"can't make fd %d (out_fd) CLOEXEC: %s\", fd, ls_tls_strerror(errno));\n        return false;\n    }\n\n    *dst = fdopen(fd, \"w\");\n    if (!*dst) {\n        LS_FATALF(bd, \"can't fdopen %d (out_fd): %s\", fd, ls_tls_strerror(errno));\n        return false;\n    }\n\n    return true;\n}\n"
  },
  {
    "path": "barlibs/stdout/open_stdio_file.h",
    "content": "/*\n * Copyright (C) 2026  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifndef open_stdio_file_\n#define open_stdio_file_\n\n#include <stdio.h>\n#include <stdbool.h>\n#include \"include/barlib_data_v1.h\"\n\nbool open_input(\n        LuastatusBarlibData *bd,\n        FILE **dst,\n        int fd,\n        const char *filename);\n\nbool open_output(\n        LuastatusBarlibData *bd,\n        FILE **dst,\n        int fd);\n\n#endif\n"
  },
  {
    "path": "barlibs/stdout/sanitize.c",
    "content": "/*\n * Copyright (C) 2015-2025  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#include \"sanitize.h\"\n#include <stddef.h>\n#include \"libls/ls_string.h\"\n#include \"libsafe/safev.h\"\n\nstatic inline void append_sv(LS_String *dst, SAFEV v)\n{\n    ls_string_append_b(dst, SAFEV_ptr_UNSAFE(v), SAFEV_len(v));\n}\n\nvoid append_sanitized(LS_String *buf, SAFEV v)\n{\n    for (;;) {\n        size_t i = SAFEV_index_of(v, '\\n');\n        if (i == (size_t) -1) {\n            break;\n        }\n        append_sv(buf, SAFEV_subspan(v, 0, i));\n        v = SAFEV_suffix(v, i + 1);\n    }\n    append_sv(buf, v);\n}\n"
  },
  {
    "path": "barlibs/stdout/sanitize.h",
    "content": "/*\n * Copyright (C) 2015-2025  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifndef sanitize_h_\n#define sanitize_h_\n\n#include \"libls/ls_string.h\"\n#include \"libsafe/safev.h\"\n\nvoid append_sanitized(LS_String *buf, SAFEV v);\n\n#endif\n"
  },
  {
    "path": "barlibs/stdout/stdout.c",
    "content": "/*\n * Copyright (C) 2015-2025  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#include <stdio.h>\n#include <stdlib.h>\n#include <lua.h>\n#include <lauxlib.h>\n#include <errno.h>\n#include <stdbool.h>\n#include <sys/types.h>\n\n#include \"include/barlib_v1.h\"\n#include \"include/sayf_macros.h\"\n\n#include \"libls/ls_string.h\"\n#include \"libls/ls_cstring_utils.h\"\n#include \"libls/ls_tls_ebuf.h\"\n#include \"libls/ls_parse_int.h\"\n#include \"libls/ls_alloc_utils.h\"\n#include \"libls/ls_lua_compat.h\"\n#include \"libsafe/safev.h\"\n\n#include \"sanitize.h\"\n#include \"open_stdio_file.h\"\n\ntypedef struct {\n    size_t nwidgets;\n\n    LS_String *bufs;\n\n    // Temporary buffer for secondary buffering, to avoid unneeded redraws.\n    LS_String tmpbuf;\n\n    char *sep;\n\n    // Content of an \"error\" segment.\n    char *error;\n\n    // /fdopen/'ed output file descriptor.\n    FILE *out;\n\n    // Value of /in_filename/ option.\n    FILE *in;\n} Priv;\n\nstatic void destroy(LuastatusBarlibData *bd)\n{\n    Priv *p = bd->priv;\n    for (size_t i = 0; i < p->nwidgets; ++i) {\n        ls_string_free(p->bufs[i]);\n    }\n    free(p->bufs);\n    ls_string_free(p->tmpbuf);\n    free(p->sep);\n    free(p->error);\n    if (p->out) {\n        fclose(p->out);\n    }\n    if (p->in) {\n        fclose(p->in);\n    }\n    free(p);\n}\n\nstatic int init(LuastatusBarlibData *bd, const char *const *opts, size_t nwidgets)\n{\n    Priv *p = bd->priv = LS_XNEW(Priv, 1);\n    *p = (Priv) {\n        .nwidgets = nwidgets,\n        .bufs = LS_XNEW(LS_String, nwidgets),\n        .tmpbuf = ls_string_new_reserve(512),\n        .sep = NULL,\n        .error = NULL,\n        .out = NULL,\n        .in = NULL,\n    };\n    for (size_t i = 0; i < nwidgets; ++i)\n        p->bufs[i] = ls_string_new_reserve(512);\n\n    // All the options may be passed multiple times!\n    const char *sep = NULL;\n    const char *error = NULL;\n    const char *in_filename = NULL;\n    int out_fd = -1;\n    int in_fd = -1;\n    for (const char *const *s = opts; *s; ++s) {\n        const char *v;\n        if ((v = ls_strfollow(*s, \"out_fd=\"))) {\n            if ((out_fd = ls_full_strtou(v)) < 0) {\n                LS_FATALF(bd, \"out_fd value is not a valid unsigned integer\");\n                goto error;\n            }\n        } else if ((v = ls_strfollow(*s, \"in_fd=\"))) {\n            if ((in_fd = ls_full_strtou(v)) < 0) {\n                LS_FATALF(bd, \"in_fd value is not a valid unsigned integer\");\n                goto error;\n            }\n        } else if ((v = ls_strfollow(*s, \"separator=\"))) {\n            sep = v;\n        } else if ((v = ls_strfollow(*s, \"error=\"))) {\n            error = v;\n        } else if ((v = ls_strfollow(*s, \"in_filename=\"))) {\n            in_filename = v;\n        } else {\n            LS_FATALF(bd, \"unknown option '%s'\", *s);\n            goto error;\n        }\n    }\n    p->sep = ls_xstrdup(sep ? sep : \" | \");\n    p->error = ls_xstrdup(error ? error : \"(Error)\");\n\n    // we require /out_fd/ to be >=3 because making stdin/stdout/stderr CLOEXEC has very bad\n    // consequences, and we just don't want to complicate the logic.\n    if (out_fd < 3) {\n        LS_FATALF(bd, \"out_fd is not specified or less than 3\");\n        goto error;\n    }\n    // same goes for /in_fd/, if specified\n    if (in_fd >= 0 && in_fd < 3) {\n        LS_FATALF(bd, \"in_fd is less than 3\");\n        goto error;\n    }\n\n    // open output\n    if (!open_output(bd, &p->out, out_fd)) {\n        goto error;\n    }\n\n    // open input\n    if (!open_input(bd, &p->in, in_fd, in_filename)) {\n        goto error;\n    }\n\n    return LUASTATUS_OK;\n\nerror:\n    destroy(bd);\n    return LUASTATUS_ERR;\n}\n\nstatic bool redraw(LuastatusBarlibData *bd)\n{\n    Priv *p = bd->priv;\n    FILE *out = p->out;\n    size_t n = p->nwidgets;\n    LS_String *bufs = p->bufs;\n    const char *sep = p->sep;\n\n    bool first = true;\n    for (size_t i = 0; i < n; ++i) {\n        if (bufs[i].size) {\n            if (!first) {\n                fputs(sep, out);\n            }\n            fwrite(bufs[i].data, 1, bufs[i].size, out);\n            first = false;\n        }\n    }\n    putc('\\n', out);\n    fflush(out);\n    if (ferror(out)) {\n        LS_FATALF(bd, \"write error: %s\", ls_tls_strerror(errno));\n        return false;\n    }\n    return true;\n}\n\nstatic int set(LuastatusBarlibData *bd, lua_State *L, size_t widget_idx)\n{\n    Priv *p = bd->priv;\n    LS_String *buf = &p->tmpbuf;\n    ls_string_clear(buf);\n\n    // L: ? data\n\n    switch (lua_type(L, -1)) {\n    case LUA_TNIL:\n        break;\n    case LUA_TSTRING:\n        {\n            size_t ns;\n            const char *s = lua_tolstring(L, -1, &ns);\n            append_sanitized(buf, SAFEV_new_UNSAFE(s, ns));\n        }\n        break;\n    case LUA_TTABLE:\n        {\n            const char *sep = p->sep;\n\n            size_t len = ls_lua_array_len(L, -1);\n            for (size_t i = 1; i <= len; ++i) {\n                lua_rawgeti(L, -1, i); // L: ? data value\n                if (lua_isnil(L, -1)) {\n                    goto next;\n                }\n                if (!lua_isstring(L, -1)) {\n                    LS_ERRF(bd, \"table value: expected string, found %s\", luaL_typename(L, -1));\n                    goto invalid_data;\n                }\n                size_t ns;\n                const char *s = lua_tolstring(L, -1, &ns);\n                if (buf->size && ns) {\n                    ls_string_append_s(buf, sep);\n                }\n                append_sanitized(buf, SAFEV_new_UNSAFE(s, ns));\nnext:\n                lua_pop(L, 1); // L: ? data value\n            }\n            // L: ? data\n        }\n        break;\n    default:\n        LS_ERRF(bd, \"expected string, table or nil, found %s\", luaL_typename(L, -1));\n        goto invalid_data;\n    }\n\n    if (!ls_string_eq(*buf, p->bufs[widget_idx])) {\n        ls_string_swap(buf, &p->bufs[widget_idx]);\n        if (!redraw(bd)) {\n            return LUASTATUS_ERR;\n        }\n    }\n    return LUASTATUS_OK;\n\ninvalid_data:\n    ls_string_clear(&p->bufs[widget_idx]);\n    return LUASTATUS_NONFATAL_ERR;\n}\n\nstatic int set_error(LuastatusBarlibData *bd, size_t widget_idx)\n{\n    Priv *p = bd->priv;\n    ls_string_assign_s(&p->bufs[widget_idx], p->error);\n    if (!redraw(bd)) {\n        return LUASTATUS_ERR;\n    }\n    return LUASTATUS_OK;\n}\n\nstatic int event_watcher(LuastatusBarlibData *bd, LuastatusBarlibEWFuncs funcs)\n{\n    Priv *p = bd->priv;\n\n    if (!p->in) {\n        LS_DEBUGF(bd, \"event watcher: in_fd/in_filename not specified, returning\");\n        return LUASTATUS_NONFATAL_ERR;\n    }\n\n    char *line = NULL;\n    size_t line_buf_n = 1024;\n\n    ssize_t line_n;\n    while ((line_n = getline(&line, &line_buf_n, p->in)) >= 0) {\n\n        if (line_n && line[line_n - 1] == '\\n') {\n            --line_n;\n        }\n\n        for (size_t i = 0; i < p->nwidgets; ++i) {\n            lua_State *L = funcs.call_begin(bd->userdata, i);\n            lua_pushlstring(L, line, line_n);\n            funcs.call_end(bd->userdata, i);\n        }\n    }\n\n    if (feof(p->in)) {\n        LS_FATALF(bd, \"event watcher: the other end of pipe/FIFO/something has been closed\");\n    } else {\n        LS_FATALF(bd, \"event watcher: I/O error: %s\", ls_tls_strerror(errno));\n    }\n\n    free(line);\n    return LUASTATUS_ERR;\n}\n\nLuastatusBarlibIface luastatus_barlib_iface_v1 = {\n    .init = init,\n    .set = set,\n    .set_error = set_error,\n    .event_watcher = event_watcher,\n    .destroy = destroy,\n};\n"
  },
  {
    "path": "check_final_newline.py",
    "content": "#!/usr/bin/env python3\nimport os\nimport sys\n\n\ndef say(s: str) -> None:\n    print(s, file=sys.stderr)\n\n\ndef main() -> None:\n    args = sys.argv[1:]\n    if not args:\n        say('USAGE: check_final_newline.py FILE [FILE ...]')\n        sys.exit(2)\n\n    everything_ok = True\n    for arg in args:\n        with open(arg, 'rb') as f:\n            f.seek(0, os.SEEK_END)\n            file_size = f.tell()\n            if not file_size:\n                continue\n            f.seek(-1, os.SEEK_END)\n            final_byte = f.read(1)\n            if final_byte != b'\\n':\n                say(f'File \"{arg}\" has no trailing newline!')\n                everything_ok = False\n\n    if everything_ok:\n        sys.exit(0)\n    else:\n        sys.exit(1)\n\n\nif __name__ == '__main__':\n    main()\n"
  },
  {
    "path": "check_includes.sh",
    "content": "#!/usr/bin/env bash\n\n# USAGE: check_includes.sh DIRECTORY [EXTRA CFLAGS...]\n# Requires 'include-what-you-use' tool.\n\nset -e\nset -o pipefail\n\ncheck_entity=${1?}; shift\nif [[ -d $check_entity ]]; then\n    check_dir=$check_entity\n    check_file=\nelse\n    check_dir=$(dirname -- \"$check_entity\")\n    check_file=$check_entity\nfi\n\nextra_cflags=( -D_POSIX_C_SOURCE=200809L \"$@\" )\n\nluastatus_dir=\"$check_dir\"\nluastatus_dir_found=0\nfor (( i = 0; i < 10; ++i )); do\n    if [[ -e \"$luastatus_dir\"/generate-man.sh ]]; then\n        luastatus_dir_found=1\n        break\n    fi\n    luastatus_dir+='/..'\ndone\n\nif (( ! luastatus_dir_found )); then\n    echo >&2 \"Cannot find luastatus dir\"\n    exit 1\nfi\n\ncmakelists_dir=${CMAKELISTS_DIR:-$check_dir}\n\nmodules=()\nif [[ -e \"$cmakelists_dir\"/CMakeLists.txt ]]; then\n    modules_raw=$(sed -rn 's/^\\s*pkg_check_modules\\s*\\(.*\\s+REQUIRED\\s+(.*)\\)\\s*$/\\1/p' \"$cmakelists_dir\"/CMakeLists.txt)\n    # Replace all whitespace with newlines\n    modules_raw=$(sed -r 's/\\s+/\\n/g' <<< \"$modules_raw\")\n    # Remove version specifications (e.g. \"yajl>=2.0.4\" -> \"yajl\")\n    modules_raw=$(sed -r 's/^([-a-zA-Z0-9_.]+).*/\\1/' <<< \"$modules_raw\")\n    # Split by whitespace, assign to 'modules' array\n    modules=( $modules_raw )\nfi\n\nif (( ${#modules[@]} )); then\n    echo >&2 \"Modules: ${modules[*]}\"\nelse\n    echo >&2 \"Modules: (none)\"\nfi\n\nmy_filter() {\n    awk '\n/^The full include-list for / {\n    skip = 1\n}\n/^---$/ {\n    skip = 0\n}\n# print if and only if (!skip)\n!skip\n'\n}\n\ncflags=$(pkg-config --cflags ${LUA_LIB:-lua} \"${modules[@]}\")\n\ndo_check_specific_file() {\n    include-what-you-use -I\"$luastatus_dir\" $cflags \"${extra_cflags[@]}\" \"$1\" 2>&1 | my_filter\n}\n\nif [[ -n $check_file ]]; then\n    do_check_specific_file \"$check_file\"\nelse\n    find \"$check_dir\" -name '*.[ch]' | while IFS= read -r src_file; do\n        if [[ $src_file == *.in.h ]]; then\n            continue\n        fi\n        do_check_specific_file \"$src_file\"\n    done\nfi\n"
  },
  {
    "path": "check_style.sh",
    "content": "#!/usr/bin/env bash\n\nset -e\nset -o pipefail\n\ncd -- \"$(dirname \"$(readlink \"$0\" || printf '%s\\n' \"$0\")\")\"\n\nsay() {\n    printf '%s\\n' \"$*\" >&2\n}\n\nexts=(\n    c h\n    lua\n    py\n    sh bash\n    md rst txt\n)\n\nfind_name_clause=()\nfor ext in \"${exts[@]}\"; do\n    if (( ${#find_name_clause[@]} )); then\n        find_name_clause+=( -or )\n    fi\n    find_name_clause+=( -name \"*.$ext\" )\ndone\n\nignore_db=(\n    f:CMakeCache.txt\n    D:CMakeFiles\n    D:CMakeScripts\n    f:Makefile\n    f:cmake_install.cmake\n    f:install_manifest.txt\n    f:CTestTestfile.cmake\n    D:build\n)\ncheck_against_ignore_db() {\n    local entry\n    local entry_basename\n    for entry in \"${ignore_db[@]}\"; do\n        entry_basename=${entry#?:}\n        case \"$entry\" in\n        f:*)\n            if [[ $1 == */\"$entry_basename\" ]]; then\n                return 1\n            fi\n            ;;\n        D:*)\n            if [[ $1 == */\"$entry_basename\"/* ]]; then\n                return 1\n            fi\n            ;;\n        *)\n            say \"FATAL: ignore_db contains invalid entry '$entry'.\";\n            exit 1\n            ;;\n        esac\n    done\n    return 0\n}\n\npaths=()\nwhile IFS= read -r path; do\n    if ! check_against_ignore_db \"$path\"; then\n        continue\n    fi\n    paths+=( \"$path\" )\ndone < <(find \\( \"${find_name_clause[@]}\" \\) -and -type f)\n\nif (( ! ${#paths[@]} )); then\n    say 'No files found with extensions of interest.'\n    say 'This means something is wrong, so exiting with non-zero code.'\n    exit 1\nfi\n\nprintf '%s\\n' \"${paths[@]}\" | xargs -d $'\\n' ./check_final_newline.py\n\nok=1\nfor path in \"${paths[@]}\"; do\n    if grep -E -H -n '\\s$' -- \"$path\"; then\n        ok=0\n    fi\ndone\nif (( !ok )); then\n    say 'Some files have lines with trailing whitespace; see above.'\n    exit 1\nfi\n\nsay \"Checked ${#paths[@]} file(s), everything is OK.\"\n\nexit 0\n"
  },
  {
    "path": "contrib/luastatus-9999.ebuild",
    "content": "# Copyright 1999-2023 Gentoo Authors\n# Distributed under the terms of the GNU General Public License v2\n\nEAPI=8\nCMAKE_IN_SOURCE_BUILD=1\ninherit cmake\n\nDESCRIPTION=\"Universal status bar content generator\"\nHOMEPAGE=\"https://github.com/shdown/luastatus\"\n\nif [[ ${PV} == *9999* ]]; then\n\tinherit git-r3\n\tSRC_URI=\"\"\n\tEGIT_REPO_URI=\"https://github.com/shdown/${PN}.git\"\n\tKEYWORDS=\"~amd64 ~x86\"\nelse\n\tSRC_URI=\"https://github.com/shdown/${PN}/archive/v${PV}.tar.gz -> ${P}.tar.gz\"\n\tKEYWORDS=\"amd64 x86\"\nfi\n\nBARLIBS=\"\n\t${PN}_barlibs_dwm\n\t${PN}_barlibs_i3\n\t${PN}_barlibs_lemonbar\n\t${PN}_barlibs_stdout\n\"\n\nPROPER_PLUGINS=\"\n\t+${PN}_plugins_alsa\n\t+${PN}_plugins_dbus\n\t+${PN}_plugins_fs\n\t+${PN}_plugins_inotify\n\t+${PN}_plugins_mpd\n\t+${PN}_plugins_network-linux\n\t+${PN}_plugins_pulse\n\t+${PN}_plugins_timer\n\t+${PN}_plugins_udev\n\t+${PN}_plugins_unixsock\n\t+${PN}_plugins_xkb\n\t+${PN}_plugins_xtitle\n\"\n\nDERIVED_PLUGINS=\"\n\t+${PN}_plugins_backlight-linux\n\t+${PN}_plugins_battery-linux\n\t+${PN}_plugins_cpu-usage-linux\n\t+${PN}_plugins_file-contents-linux\n\t+${PN}_plugins_imap\n\t+${PN}_plugins_mem-usage-linux\n\t+${PN}_plugins_pipe\n\"\n\nPLUGINS=\"\n\t${PROPER_PLUGINS}\n\t${DERIVED_PLUGINS}\n\"\n\nLICENSE=\"LGPL-3+\"\nSLOT=\"0\"\nIUSE=\"doc examples luajit ${BARLIBS} ${PLUGINS}\"\nREQUIRED_USE=\"\n\t${PN}_plugins_backlight-linux? ( ${PN}_plugins_udev )\n\t${PN}_plugins_battery-linux? ( ${PN}_plugins_udev )\n\t${PN}_plugins_cpu-usage-linux? ( ${PN}_plugins_timer )\n\t${PN}_plugins_file-contents-linux? ( ${PN}_plugins_inotify )\n\t${PN}_plugins_imap? ( ${PN}_plugins_timer )\n\t${PN}_plugins_mem-usage-linux? ( ${PN}_plugins_timer )\n\t${PN}_plugins_pipe? ( ${PN}_plugins_timer )\n\"\n\nDEPEND=\"\n\tdoc? ( dev-python/docutils )\n\"\nRDEPEND=\"\n\tluajit? ( dev-lang/luajit:2 )\n\t!luajit? ( dev-lang/lua )\n\t${PN}_barlibs_dwm? ( x11-libs/libxcb )\n\t${PN}_barlibs_i3? ( >=dev-libs/yajl-2.0.4 )\n\t${PN}_plugins_alsa? ( media-libs/alsa-lib )\n\t${PN}_plugins_dbus? ( dev-libs/glib )\n\t${PN}_plugins_network-linux? ( sys-kernel/linux-headers dev-libs/libnl )\n\t${PN}_plugins_pulse? ( media-sound/pulseaudio )\n\t${PN}_plugins_udev? ( virtual/libudev )\n\t${PN}_plugins_xkb? ( x11-libs/libX11 )\n\t${PN}_plugins_xtitle? ( x11-libs/libxcb x11-libs/xcb-util-wm x11-libs/xcb-util )\n\"\n\nsrc_configure() {\n\tlocal mycmakeargs=(\n\t\t$(use luajit && echo -DWITH_LUA_LIBRARY=luajit)\n\t\t-DBUILD_DOCS=$(usex doc)\n\t\t-DBUILD_BARLIB_DWM=$(usex ${PN}_barlibs_dwm)\n\t\t-DBUILD_BARLIB_I3=$(usex ${PN}_barlibs_i3)\n\t\t-DBUILD_BARLIB_LEMONBAR=$(usex ${PN}_barlibs_lemonbar)\n\t\t-DBUILD_BARLIB_STDOUT=$(usex ${PN}_barlibs_stdout)\n\t\t-DBUILD_PLUGIN_ALSA=$(usex ${PN}_plugins_alsa)\n\t\t-DBUILD_PLUGIN_BACKLIGHT_LINUX=$(usex ${PN}_plugins_backlight-linux)\n\t\t-DBUILD_PLUGIN_BATTERY_LINUX=$(usex ${PN}_plugins_battery-linux)\n\t\t-DBUILD_PLUGIN_CPU_USAGE_LINUX=$(usex ${PN}_plugins_cpu-usage-linux)\n\t\t-DBUILD_PLUGIN_DBUS=$(usex ${PN}_plugins_dbus)\n\t\t-DBUILD_PLUGIN_FILE_CONTENTS_LINUX=$(usex ${PN}_plugins_file-contents-linux)\n\t\t-DBUILD_PLUGIN_FS=$(usex ${PN}_plugins_fs)\n\t\t-DBUILD_PLUGIN_IMAP=$(usex ${PN}_plugins_imap)\n\t\t-DBUILD_PLUGIN_INOTIFY=$(usex ${PN}_plugins_inotify)\n\t\t-DBUILD_PLUGIN_MEM_USAGE_LINUX=$(usex ${PN}_plugins_mem-usage-linux)\n\t\t-DBUILD_PLUGIN_MPD=$(usex ${PN}_plugins_mpd)\n\t\t-DBUILD_PLUGIN_NETWORK_LINUX=$(usex ${PN}_plugins_network-linux)\n\t\t-DBUILD_PLUGIN_PIPE=$(usex ${PN}_plugins_pipe)\n\t\t-DBUILD_PLUGIN_PULSE=$(usex ${PN}_plugins_pulse)\n\t\t-DBUILD_PLUGIN_TIMER=$(usex ${PN}_plugins_timer)\n\t\t-DBUILD_PLUGIN_UDEV=$(usex ${PN}_plugins_udev)\n\t\t-DBUILD_PLUGIN_UNIXSOCK=$(usex ${PN}_plugins_unixsock)\n\t\t-DBUILD_PLUGIN_XKB=$(usex ${PN}_plugins_xkb)\n\t\t-DBUILD_PLUGIN_XTITLE=$(usex ${PN}_plugins_xtitle)\n\t)\n\tcmake_src_configure\n}\n\nsrc_install() {\n\tcmake_src_install\n\tlocal i\n\tif use examples; then\n\t\tdodir /usr/share/doc/${PF}/examples\n\t\tdocinto examples\n\t\tfor i in ${BARLIBS//+/}; do\n\t\t\tif use ${i}; then\n\t\t\t\tbarlib=${i#${PN}_barlibs_}\n\t\t\t\tdodoc -r examples/${barlib}\n\t\t\t\tdocompress -x /usr/share/doc/${PF}/examples/${barlib}\n\t\t\tfi\n\t\tdone\n\tfi\n}\n"
  },
  {
    "path": "contrib/luastatus.spec",
    "content": "Name:           luastatus\nVersion:        0.3.0\nRelease:        1%{?dist}\nSummary:        universal statusbar content generator\n\nLicense:        LGPL3+\nURL:            https://github.com/shdown/luastatus\nSource0:        https://github.com/shdown/luastatus/archive/v%version.tar.gz\n\nBuildRequires:  cmake\nBuildRequires:  luajit-devel\nBuildRequires:  libxcb-devel\nBuildRequires:  yajl-devel\nBuildRequires:  alsa-lib-devel\nBuildRequires:  xcb-util-wm-devel\nBuildRequires:  xcb-util-devel\nBuildRequires:  glib2-devel\n\n%description\na universal status bar content generator\n\n%package plugins\nSummary:    luastatus plugins\nRequires:   %{name} = %{version}-%{release}\n\n%description plugins\nluastatus plugins\n\n%prep\n%setup -q\n\n%build\n%cmake -DWITH_LUA_LIBRARY=luajit .\n\n%make_build\n\n%install\n%make_install\n\n%files\n%doc COPYING.txt COPYING.LESSER.txt README.md\n%{_bindir}/luastatus\n%{_bindir}/luastatus-i3-wrapper\n%{_bindir}/luastatus-lemonbar-launcher\n%{_mandir}/man1/luastatus.1*\n%{_libdir}/luastatus/barlibs/*\n\n%files plugins\n%{_libdir}/luastatus/plugins/*\n\n%changelog\n\n"
  },
  {
    "path": "debian/changelog",
    "content": "luastatus (1:9999) UNRELEASED; urgency=medium\n\n  * local build\n\n -- Local User <localuser@localhost>  Sat, 08 Feb 2014 01:01:01 -0800\n"
  },
  {
    "path": "debian/compat",
    "content": "10\n"
  },
  {
    "path": "debian/control",
    "content": "Source: luastatus\nSection: misc\nPriority: optional\nMaintainer: Viktor Krapivensky <shdownnine@gmail.com>\nStandards-Version: 4.5.0\nBuild-Depends:\n cmake (>= 3.1.3),\n debhelper (>= 10),\n valgrind,\n jq,\n liblua5.3-dev,\n pkg-config,\n python3-docutils,\n libyajl-dev,\n libasound2-dev,\n libglib2.0-dev,\n libpulse-dev,\n libudev-dev,\n linux-libc-dev,\n libnl-3-dev,\n libnl-genl-3-dev,\n libx11-dev,\n libxcb1-dev,\n libxcb-ewmh-dev,\n libxcb-icccm4-dev,\n libxcb-util0-dev\nHomepage: https://github.com/shdown/luastatus\nRules-Requires-Root: no\n\nPackage: luastatus\nArchitecture: any\nDepends: ${shlibs:Depends}, ${misc:Depends}\nRecommends:\nSuggests:\nDescription: Universal status bar content generator\n luastatus is a universal status bar content generator.  It allows the user to\n configure the way the data from event sources is processed and shown, with\n Lua.  Its main feature is that the content can be updated immediately as some\n event occurs.\n .\n This package contains the main binary and the following plugins: timer, fs,\n inotify, udev, backlight-linux, battery-linux, cpu-usage-linux,\n file-contents-linux, mem-usage-linux, pipe.\n\nPackage: luastatus-barlib-i3\nArchitecture: any\nDepends: ${shlibs:Depends}, ${misc:Depends}, luastatus\nRecommends:\nSuggests: i3-wm\nDescription: i3 barlib for luastatus\n This package contains the i3 barlib for luastatus.  This barlib talks to i3bar\n (part of the i3 window manager), or any other program compatible with it on\n the protocol level.  This package also provides luastatus-i3-wrapper script.\n .\n For more information, see \"man 7 luastatus-barlib-i3\".\n\nPackage: luastatus-barlib-dwm\nArchitecture: any\nDepends: ${shlibs:Depends}, ${misc:Depends}, luastatus\nRecommends:\nSuggests: dwm\nDescription: dwm barlib for luastatus\n This package contains the dwm barlib for luastatus.  It updates the name of\n the root window.  Although named dwm, it can really work with any program that\n outputs the root window name somewhere.\n .\n For more information, see \"man 7 luastatus-barlib-dwm\".\n\nPackage: luastatus-barlib-lemonbar\nArchitecture: any\nDepends: ${shlibs:Depends}, ${misc:Depends}, luastatus\nRecommends:\nSuggests: lemonbar\nDescription: lemonbar barlib for luastatus\n This package contains the lemonbar barlib for luastatus.  The package also\n provides luastatus-lemonbar-launcher script.\n .\n For more information, see \"man 7 luastatus-barlib-lemonbar\".\n\nPackage: luastatus-barlib-stdout\nArchitecture: any\nDepends: ${shlibs:Depends}, ${misc:Depends}, luastatus\nRecommends:\nSuggests: dzen2 | xmobar | yabar | dvtm\nDescription: stdout barlib for luastatus\n This is stdout barlib for luastatus.  It simply outputs lines to given file\n descriptor.  It can be used with, for example, dzen2, xmobar, yabar, dvtm.\n This package also provides luastatus-stdout-wrapper script.\n .\n For more information, see \"man 7 luastatus-barlib-stdout\".\n\nPackage: luastatus-plugin-alsa\nArchitecture: any\nDepends: ${shlibs:Depends}, ${misc:Depends}, luastatus\nRecommends:\nSuggests:\nDescription: ALSA plugin for luastatus\n This package contains the ALSA plugin for luastatus.  It  monitors volume and\n mute state of an ALSA channel.\n .\n For more information, see \"man 7 luastatus-plugin-alsa\".\n\nPackage: luastatus-plugin-dbus\nArchitecture: any\nDepends: ${shlibs:Depends}, ${misc:Depends}, luastatus\nRecommends:\nSuggests:\nDescription: D-Bus plugin for luastatus\n This package contains the D-Bus plugin for luastatus.  It subscribes to and\n reports D-Bus signals.\n .\n For more information, see \"man 7 luastatus-plugin-dbus\".\n\nPackage: luastatus-plugin-mpd\nArchitecture: any\nDepends: ${shlibs:Depends}, ${misc:Depends}, luastatus\nRecommends:\nSuggests: mpd\nDescription: MPD plugin for luastatus\n This package contains the MPD plugin for luastatus.  It monitors the state of\n an MPD server.\n .\n For more information, see \"man 7 luastatus-plugin-mpd\".\n\nPackage: luastatus-plugin-network-linux\nArchitecture: any\nDepends: ${shlibs:Depends}, ${misc:Depends}, luastatus\nRecommends:\nSuggests:\nDescription: network-linux plugin for luastatus\n This package contains the network-linux plugin for luastatus.  It monitors\n network routing and link updates, and can report IP addresses used for\n outgoing connections by various network interfaces, information about a\n wireless connection, and speed of an ethernet connection.\n .\n For more information, see \"man 7 luastatus-plugin-network-linux\".\n\nPackage: luastatus-plugin-pulse\nArchitecture: any\nDepends: ${shlibs:Depends}, ${misc:Depends}, luastatus\nRecommends:\nSuggests: pulseaudio\nDescription: PulseAudio plugin for luastatus\n This package contains the PulseAudio plugin for luastatus.  It monitors the\n volume and mute status of a PulseAudio sink.\n .\n For more information, see \"man 7 luastatus-plugin-pulse\".\n\nPackage: luastatus-plugin-xkb\nArchitecture: any\nDepends: ${shlibs:Depends}, ${misc:Depends}, luastatus\nRecommends:\nSuggests:\nDescription: XKB plugin for luastatus\n This package contains the XKB plugin for luastatus.  It monitors the current\n keyboard layout, and, optionally, the state of LED indicators, such as\n Caps Lock and Num Lock.\n .\n For more information, see \"man 7 luastatus-plugin-xkb\".\n\nPackage: luastatus-plugin-xtitle\nArchitecture: any\nDepends: ${shlibs:Depends}, ${misc:Depends}, luastatus\nRecommends:\nSuggests:\nDescription: xtitle plugin for luastatus\n This package conntains the xtitle plugin for luastatus.  It monitors the\n active window title.\n .\n For more information, see \"man 7 luastatus-plugin-xtitle\".\n\nPackage: luastatus-plugin-imap\nArchitecture: all\nDepends: ${misc:Depends}, luastatus,\n lua5.3-socket,\n lua5.3-sec\nRecommends:\nSuggests:\nDescription: IMAP plugin for luastatus\n This package contains the IMAP plugin for luastatus.  It monitors the number\n of unread mails in an IMAP mailbox.\n .\n For more information, see \"man 7 luastatus-plugin-imap\".\n"
  },
  {
    "path": "debian/copyright",
    "content": "Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/\nUpstream-Name: luastatus\nUpstream-Contact: Viktor Krapivensky <shdownnine@gmail.com>\nSource: https://github.com/shdown/luastatus\n\nFiles: *\nCopyright: 2015-2021, luastatus developers\nLicense: LGPL-3.0+\n\nLicense: LGPL-3.0+\n This program is free software: you can redistribute it and/or modify\n it under the terms of the GNU Lesser General Public License as published by\n the Free Software Foundation, either version 3 of the License, or\n (at your option) any later version.\n .\n This program is distributed in the hope that it will be useful,\n but WITHOUT ANY WARRANTY; without even the implied warranty of\n MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n GNU Lesser General Public License for more details.\n .\n You should have received a copy of the GNU Lesser General Public License\n along with this package.  If not, see <https://www.gnu.org/licenses/>.\n .\n On Debian systems, the full text of the GNU Lesser General Public License\n version 3 can be found in the file `/usr/share/common-licenses/LGPL-3'.\n"
  },
  {
    "path": "debian/rules",
    "content": "#! /usr/bin/make -f\n\nexport V:=1\nexport DEB_BUILD_MAINT_OPTIONS:=hardening=+all\n\nBUILD_DIR := _deb_build\n\noverride_dh_auto_clean:\n\trm -rf $(BUILD_DIR)\n\noverride_dh_installexamples:\n\tdh_installexamples examples/*\n\noverride_dh_installchangelogs:\n\tset -e; if [ -e RELEASE_NOTES ]; then \\\n\t\tdh_installchangelogs RELEASE_NOTES; \\\n\telse \\\n\t\tdh_installchangelogs; \\\n\tfi\n\noverride_dh_auto_configure:\n\tset -e; mkdir $(BUILD_DIR); cd $(BUILD_DIR); cmake \\\n\t\t-DCMAKE_INSTALL_PREFIX=/usr \\\n\t\t-DWITH_LUA_LIBRARY=lua5.3 \\\n\t\t-DBUILD_PLUGIN_PULSE=on \\\n\t\t-DBUILD_PLUGIN_UNIXSOCK=on \\\n\t\t-DBUILD_TESTS=on \\\n\t\t-S .. -B .\n\noverride_dh_auto_build:\n\t$(MAKE) -C $(BUILD_DIR)\n\noverride_dh_auto_install:\n\t# luastatus itself\n\t$(MAKE) -C $(BUILD_DIR)/luastatus install \\\n\t\tDESTDIR=../../debian/luastatus\n\t# barlibs\n\tset -e; for x in dwm i3 lemonbar stdout; do \\\n\t\t$(MAKE) -C $(BUILD_DIR)/barlibs/$$x install \\\n\t\t\tDESTDIR=../../../debian/luastatus-barlib-$$x; \\\n\tdone\n\t# \"core\" plugins (that should go into luastatus pkg)\n\tset -e; for x in \\\n\t\tfs timer inotify udev \\\n\t\tbacklight-linux \\\n\t\tbattery-linux \\\n\t\tcpu-usage-linux \\\n\t\tfile-contents-linux \\\n\t\tmem-usage-linux \\\n\t\tpipe \\\n\t\tunixsock; \\\n\tdo \\\n\t\t$(MAKE) -C $(BUILD_DIR)/plugins/$$x install \\\n\t\t\tDESTDIR=../../../debian/luastatus; \\\n\tdone\n\t# other plugins\n\tset -e; for x in \\\n\t\talsa dbus inotify mpd network-linux pulse xkb xtitle \\\n\t\timap; \\\n\tdo \\\n\t\t$(MAKE) -C $(BUILD_DIR)/plugins/$$x install \\\n\t\t\tDESTDIR=../../../debian/luastatus-plugin-$$x; \\\n\tdone\n\t# man page symlink: luastatus-i3-wrapper(1) -> luastatus-barlib-i3(7)\n\tmkdir debian/luastatus-barlib-i3/usr/share/man/man1\n\tln -s ../man7/luastatus-barlib-i3.7 \\\n\t\tdebian/luastatus-barlib-i3/usr/share/man/man1/luastatus-i3-wrapper.1\n\t# man page symlink: luastatus-lemonbar-launcher(1) -> luastatus-barlib-lemonbar(7)\n\tmkdir debian/luastatus-barlib-lemonbar/usr/share/man/man1\n\tln -s ../man7/luastatus-barlib-lemonbar.7 \\\n\t\tdebian/luastatus-barlib-lemonbar/usr/share/man/man1/luastatus-lemonbar-launcher.1\n\t# man page symlink: luastatus-stdout-wrapper(1) -> luastatus-barlib-stdout(7)\n\tmkdir debian/luastatus-barlib-stdout/usr/share/man/man1\n\tln -s ../man7/luastatus-barlib-stdout.7 \\\n\t\tdebian/luastatus-barlib-stdout/usr/share/man/man1/luastatus-stdout-wrapper.1\n\noverride_dh_auto_test:\nifeq (,$(filter nocheck,$(DEB_BUILD_OPTIONS)))\n\t./tests/pt.sh $(BUILD_DIR) skip:plugin-dbus skip:plugin-pulse\n\t./tests/torture.sh $(BUILD_DIR)\nendif\n\n%:\n\texec dh $@\n"
  },
  {
    "path": "examples/dwm/alsa-gauge.lua",
    "content": "local GAUGE_NCHARS = 10\n\nlocal function mk_gauge(level, full, empty)\n    local nfull = math.floor(level * GAUGE_NCHARS + 0.5)\n    return full:rep(nfull) .. empty:rep(GAUGE_NCHARS - nfull)\nend\n\nwidget = {\n    plugin = 'alsa',\n    cb = function(t)\n        local level = (t.vol.cur - t.vol.min) / (t.vol.max - t.vol.min)\n        if t.mute then\n            return mk_gauge(level, '×', '—')\n        else\n            return mk_gauge(level, '●', '○')\n        end\n    end,\n}\n"
  },
  {
    "path": "examples/dwm/alsa.lua",
    "content": "widget = {\n    plugin = 'alsa',\n    cb = function(t)\n        if t.mute then\n            return '[mute]'\n        else\n            local percent = (t.vol.cur - t.vol.min) / (t.vol.max - t.vol.min) * 100\n            return string.format('[%3d%%]', math.floor(0.5 + percent))\n        end\n    end,\n}\n"
  },
  {
    "path": "examples/dwm/backlight.lua",
    "content": "-- Note that this widget only shows backlight level when it changes.\nwidget = luastatus.require_plugin('backlight-linux').widget{\n    cb = function(level)\n        if level ~= nil then\n            return string.format('*%3.0f%%', level * 100)\n        end\n    end,\n}\n"
  },
  {
    "path": "examples/dwm/battery.lua",
    "content": "widget = luastatus.require_plugin('battery-linux').widget{\n    period = 2,\n    cb = function(t)\n        local symbol = ({\n            Charging    = '↑',\n            Discharging = '↓',\n        })[t.status] or ' '\n        local rem_seg\n        if t.rem_time then\n            local h = math.floor(t.rem_time)\n            local m = math.floor(60 * (t.rem_time - h))\n            rem_seg = string.format('%2dh %02dm', h, m)\n        end\n        return {\n            string.format('%3d%%%s', t.capacity, symbol),\n            rem_seg,\n        }\n    end,\n}\n"
  },
  {
    "path": "examples/dwm/bluetooth.lua",
    "content": "-- A widget to display currently connected and paired bluetooth devices.\n-- To change output format modify reprint_devices function.\n\nif not luastatus.execute('command -v bluetoothctl >/dev/null') then\n    error('\"bluetoothctl\" command, which is required for this widget to work, was not found')\nend\n\nlocal separator = \" \"\n\n-- Object paths look like /org/bluez/hci0/dev_XX_XX_XX_XX_XX_XX/somethingsomething\nlocal function get_device_mac_address(device_object_path)\n    return device_object_path:gsub(\"/.*/dev_\", \"\"):gsub(\"/.*\", \"\"):gsub(\"_\", \":\")\nend\n\n-- For reference bluetoothctl devices output looks like that:\n-- Device XX:XX:XX:XX:XX:XX JBL T450BT\n-- Device YY:YY:YY:YY:YY:YY Redmi 8\n--\n-- Function returns mac addresses of all devices.\nlocal function get_devices()\n    local devices = {}\n    local handle = io.popen(string.format(\"bluetoothctl devices\"))\n    for line in handle:lines() do\n        local match = string.match(line, \"Device ([%x:]+)\")\n        if match then\n            table.insert(devices, math)\n        end\n    end\n    handle:close()\n    return devices\nend\n\n-- For reference bluetoothctl info output looks like that:\n-- Device XX:XX:XX:XX:XX:XX (public)\n--         Name: JBL T450BT\n--         Alias: JBL T450BT\n--         Class: 0xFFFFFFFF\n--         Icon: audio-card\n--         Paired: yes\n--         Trusted: yes\n--         Blocked: no\n--         Connected: yes\n--         LegacyPairing: no\n--         UUID: Headset                   (xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)\n--         ...\n--         UUID: Handsfree                 (xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)\n--\n-- Given this input function returns a following table:\n-- [alias]         string  JBL T450BT\n-- [blocked]       boolean false\n-- [class]         string  0x00240404\n-- [connected]     boolean true\n-- [icon]          string  audio\n-- [legacypairing] boolean false\n-- [name]          string  JBL T450BT\n-- [paired]        boolean true\n-- [trusted]       boolean true\nlocal function get_device_info(mac_address)\n    if mac_address == nil then\n        mac_address = \"\"\n    end\n\n    assert(string.match(mac_address, '^[%x:]*$') ~= nil)\n\n    local device_info = {}\n    local handle = io.popen(string.format(\"bluetoothctl info %s\", mac_address))\n    for line in handle:lines() do\n        local key, value = string.match(line, \"(%w+): (.*)\")\n        -- Filter junk\n        if key ~= \"UUID\" and key ~= nil and value ~= nil then\n            key = string.lower(key)\n            if key ~= \"name\" and key ~= \"alias\" and key ~= \"icon\" then\n                if value == \"yes\" then\n                    value = true\n                end\n                if value == \"no\" then\n                    value = false\n                end\n            end\n            device_info[key] = value\n        end\n    end\n    handle:close()\n    return device_info\nend\n\nlocal devices = {}\n\nlocal function reprint_devices()\n    local t = {}\n    for mac_address, device in pairs(devices) do\n        table.insert(t, string.format(\"%s(%s)\", device[\"name\"], mac_address))\n    end\n    return table.concat(t, separator)\nend\n\nwidget = {\n    plugin = \"dbus\",\n    opts = {\n        greet = true,\n        -- https://git.kernel.org/pub/scm/bluetooth/bluez.git/tree/doc/device-api.txt\n        signals = {\n            {\n                sender = \"org.bluez\",\n                interface = \"org.freedesktop.DBus.Properties\",\n                signal = \"PropertiesChanged\",\n                arg0 = \"org.bluez.Device1\",\n                bus = \"system\"\n            }\n        }\n    },\n    cb = function(t)\n        if t.what == \"hello\" then\n            local mac_addresses = get_devices()\n            for _, mac_address in ipairs(mac_addresses) do\n                local device = get_device_info(mac_address)\n                if device[\"connected\"] and device[\"paired\"] then\n                    devices[mac_address] = device\n                end\n            end\n        elseif t.what == \"signal\" then\n            -- For reference message from dbus looks like that:\n            -- table\n            -- [1]     string  org.bluez.Device1\n            -- [2]     table\n            -- [2]     [1]     table\n            -- [2]     [1]     [1]     string  SomethingSomething\n            -- [2]     [1]     [2]     boolean false\n            -- [2]     [2]     table\n            -- [2]     [2]     [1]     string  Connected\n            -- [2]     [2]     [2]     boolean true\n            -- [3]     table\n            if t.signal == \"PropertiesChanged\" then\n                for _, message in pairs(t.parameters[2]) do\n                    if message[1] == \"Connected\" or message[1] == \"Paired\" then\n                        local mac_address = get_device_mac_address(t.object_path)\n                        if message[2] then\n                            local device = get_device_info(mac_address)\n                            if device[\"paired\"] then\n                                devices[mac_address] = device\n                            end\n                        else\n                            devices[mac_address] = nil\n                        end\n                    end\n                end\n            end\n        end\n        return reprint_devices()\n    end\n}\n"
  },
  {
    "path": "examples/dwm/btc-price.lua",
    "content": "-- Bitcoin price widget.\n-- Updates on click and every 5 minutes.\n\nlocal custom_sleep_amt = nil\n\nlocal function check_error(t)\n    if t.error then\n        -- Low-level libcurl error\n        return t.error\n    end\n    if t.status < 200 or t.status > 299 then\n        -- Bad HTTP status\n        return string.format('HTTP status %d', t.status)\n    end\n    -- Everything's OK\n    return nil\nend\n\nwidget = {\n    plugin = 'web',\n    opts = {\n        planner = function()\n            while true do\n                coroutine.yield({action = 'request', params = {\n                    url = 'https://api.binance.com/api/v3/ticker/price?symbol=BTCUSDT',\n                    timeout = 5,\n                }})\n                local period = custom_sleep_amt or (5 * 60)\n                custom_sleep_amt = nil\n                coroutine.yield({action = 'sleep', period = period})\n            end\n        end,\n        make_self_pipe = true,\n    },\n    cb = function(t)\n        local text\n        local err_msg = check_error(t)\n        if err_msg then\n            print(string.format('WARNING: luastatus: btc-price widget: %s'), err_msg)\n            text = '......'\n            custom_sleep_amt = 5\n        else\n            local obj = assert(luastatus.plugin.json_decode(t.body))\n            text = obj.price:match('[^.]+')\n        end\n        return string.format('[$%s]', text)\n    end,\n    event = function(t)\n        if t.button == 1 then\n            luastatus.plugin.wake_up()\n        end\n    end,\n}\n"
  },
  {
    "path": "examples/dwm/cpu-freq.lua",
    "content": "local VBLOCKS = {'▂', '▃', '▄', '▅', '▆', '▇', '█'}\n\nlocal function make_chunk(entry)\n    local num = entry.cur - entry.min\n    local denom = entry.max - entry.min\n\n    local ratio\n    if denom ~= 0 then\n        ratio = num / denom\n    else\n        -- If max_freq == min_freq, set ratio to zero.\n        ratio = 0\n    end\n\n    local vblock_idx = math.min(\n        1 + math.floor(0.5 + ratio * #VBLOCKS),\n        #VBLOCKS)\n\n    return VBLOCKS[vblock_idx]\nend\n\nlocal plugin_data = {}\n\nlocal plugin_params = {\n    timer_opts = {\n        period = 2,\n    },\n    cb = function(t)\n        if t == nil then\n            return nil\n        end\n        local r = {}\n        for _, entry in ipairs(t) do\n            r[#r + 1] = make_chunk(entry)\n        end\n        return table.concat(r)\n    end,\n    event = function(t)\n        if t.button == 1 then\n            plugin_data.please_reload = true\n        end\n    end,\n}\n\nwidget = luastatus.require_plugin('cpu-freq-linux').widget(plugin_params, plugin_data)\n"
  },
  {
    "path": "examples/dwm/cpu-temperature.lua",
    "content": "local plugin_data = {}\n\nlocal plugin_params = {\n    timer_opts = {\n        period = 2,\n    },\n    cb = function(t)\n        if not t then\n            return nil\n        end\n        local r = {}\n        for _, entry in ipairs(t) do\n            r[#r + 1] = string.format('%.0f°', entry.value)\n        end\n        return r\n    end,\n    event = function(t)\n        if t.button == 1 then\n            plugin_data.please_reload = true\n        end\n    end,\n}\n\nwidget = luastatus.require_plugin('cpu-temp-linux').widget(plugin_params, plugin_data)\n"
  },
  {
    "path": "examples/dwm/cpu-usage.lua",
    "content": "widget = luastatus.require_plugin('cpu-usage-linux').widget{\n    cb = function(usage)\n        if usage ~= nil then\n            return string.format('[%5.1f%%]', usage * 100)\n        end\n    end,\n}\n"
  },
  {
    "path": "examples/dwm/disk-io.lua",
    "content": "widget = luastatus.require_plugin('disk-io-linux').widget{\n    period = 2,\n    cb = function(t)\n        -- Sort by name for determinism\n        table.sort(t, function(a, b) return a.name < b.name end)\n\n        local segments = {}\n        for _, entry in ipairs(t) do\n            local R = entry.read_bytes\n            local W = entry.written_bytes\n            if (R >= 0) and (W >= 0) then\n                segments[#segments + 1] = string.format(\n                    '%s: %.0fk↓ %.0fk↑',\n                    entry.name,\n                    R / 1024,\n                    W / 1024\n                )\n            end\n        end\n        return segments\n    end,\n}\n"
  },
  {
    "path": "examples/dwm/file-contents.lua",
    "content": "widget = luastatus.require_plugin('file-contents-linux').widget{\n    filename = os.getenv('HOME') .. '/status',\n    cb = function(f)\n        -- show the first line of the file\n        return f:read('*line')\n    end,\n}\n"
  },
  {
    "path": "examples/dwm/fs.lua",
    "content": "local function sorted_keys(tbl)\n    local keys = {}\n    for k, _ in pairs(tbl) do\n        keys[#keys + 1] = k\n    end\n    table.sort(keys)\n    return keys\nend\n\nwidget = {\n    plugin = 'fs',\n    opts = {\n        paths = {'/', '/home'},\n    },\n    cb = function(t)\n        -- Sort for determinism\n        local keys = sorted_keys(t)\n        local res = {}\n        for _, k in ipairs(keys) do\n            local v = t[k]\n            table.insert(res, string.format('%s %.0f%%', k,\n                (1 - v.avail / v.total) * 100))\n        end\n        return res\n    end,\n}\n"
  },
  {
    "path": "examples/dwm/gmail.lua",
    "content": "--[[\n-- Expects 'credentials.lua' to be present in the current directory; it may contain, e.g.,\n--     return {\n--         gmail = {\n--             login = 'john.smith',\n--             password = 'qwerty'\n--         }\n--     }\n--]]\nlocal credentials = require 'credentials'\n\nwidget = luastatus.require_plugin('imap').widget{\n    verbose = false,\n    host = 'imap.gmail.com',\n    port = 993,\n    mailbox = 'Inbox',\n    use_ssl = true,\n    timeout = 2 * 60,\n    handshake_timeout = 10,\n    login = credentials.gmail.login,\n    password = credentials.gmail.password,\n    error_sleep_period = 60,\n    cb = function(unseen)\n        if unseen == nil or unseen == 0 then\n            return nil\n        else\n            return string.format('[%d unseen]', unseen)\n        end\n    end,\n}\n"
  },
  {
    "path": "examples/dwm/ip.lua",
    "content": "widget = {\n    plugin = 'network-linux',\n    cb = function(t)\n        local r = {}\n        for iface, params in pairs(t) do\n            local iface_clean = iface\n            local addr = params.ipv6 or params.ipv4\n            if addr then\n                -- strip out \"label\" from the interface name\n                iface_clean = iface_clean:gsub(':.*', '')\n                -- strip out \"zone index\" from the address\n                addr = addr:gsub('%%.*', '')\n\n                if iface_clean ~= 'lo' then\n                    r[#r + 1] = string.format('[%s: %s]', iface_clean, addr)\n                end\n            end\n        end\n        return r\n    end,\n}\n"
  },
  {
    "path": "examples/dwm/loadavg-linux.lua",
    "content": "local function get_ncpus()\n    local f = assert(io.open('/proc/cpuinfo', 'r'))\n    local n = 0\n    for line in f:lines() do\n        if line:match('^processor\\t') then\n            n = n + 1\n        end\n    end\n    f:close()\n    return n\nend\n\nlocal function avg2str(x)\n    assert(x >= 0)\n    if x >= 1000 then\n        return '↑↑↑'\n    end\n    return string.format('%3.0f', x)\nend\n\nwidget = {\n    plugin = 'timer',\n    opts = {\n        period = 2,\n    },\n    cb = function(_)\n        local f = io.open('/proc/loadavg', 'r')\n        local avg1, avg5, avg15 = f:read('*number', '*number', '*number')\n        f:close()\n\n        assert(avg1 and avg5 and avg15)\n\n        local ncpus = get_ncpus()\n\n        return string.format(\n            '[%s%% %s%% %s%%]',\n            avg2str(avg1  / ncpus * 100),\n            avg2str(avg5  / ncpus * 100),\n            avg2str(avg15 / ncpus * 100)\n        )\n    end,\n}\n"
  },
  {
    "path": "examples/dwm/media-player-mpris.lua",
    "content": "local PLAYER = 'clementine'\n\nlocal PLAYBACK_STATUS_ICONS = {\n    Playing = '▶',\n    Paused  = '◆',\n    Stopped = '—',\n}\n\nlocal function fetch_metadata_field(t, key)\n    if t.Metadata then\n        return t.Metadata[key]\n    else\n        return nil\n    end\nend\n\nwidget = luastatus.require_plugin('mpris').widget{\n    player = PLAYER,\n    cb = function(t)\n        if not t.PlaybackStatus then\n            return nil\n        end\n\n        local title = fetch_metadata_field(t, 'xesam:title')\n        title = title or ''\n\n        title = luastatus.libwidechar.make_valid_and_printable(title, '?')\n        title = luastatus.libwidechar.truncate_to_width(title, 40)\n        title = title or ''\n\n        local icon = PLAYBACK_STATUS_ICONS[t.PlaybackStatus] or '?'\n\n        local result = icon\n        if title ~= '' then\n            result = result .. ' ' .. title\n        end\n        return result\n    end,\n}\n"
  },
  {
    "path": "examples/dwm/mem-usage.lua",
    "content": "widget = luastatus.require_plugin('mem-usage-linux').widget{\n    timer_opts = {period = 2},\n    cb = function(t)\n        local used_kb = t.total.value - t.avail.value\n        return string.format('[%3.2f GiB]', used_kb / 1024 / 1024)\n    end,\n}\n"
  },
  {
    "path": "examples/dwm/mpd.lua",
    "content": "local titlewidth = 40\n\nwidget = {\n    plugin = 'mpd',\n    cb = function(t)\n        if t.what == 'update' then\n            local title\n            if t.song.Title then\n                title = t.song.Title\n                if t.song.Artist then\n                    title = t.song.Artist .. ': ' .. title\n                end\n            else\n                title = t.song.file or ''\n            end\n\n            title = luastatus.libwidechar.make_valid(title, '?')\n\n            if assert(luastatus.libwidechar.width(title)) > titlewidth then\n                title = luastatus.libwidechar.truncate_to_width(title, titlewidth - 1) .. '…'\n            end\n\n            return string.format('%s %s',\n                ({play = '▶', pause = '‖', stop = '■'})[t.status.state],\n                title\n            )\n        else\n            -- 'connecting' or 'error'\n            return t.what\n        end\n    end\n}\n"
  },
  {
    "path": "examples/dwm/network-rate.lua",
    "content": "local function make_segment(iface, R, S)\n    return string.format('[%s %.0fk↓ %.0fk↑]', iface, R / 1000, S / 1000)\nend\n\nwidget = luastatus.require_plugin('network-rate-linux').widget{\n    iface_except = 'lo',\n    period = 3,\n    in_array_form = true,\n    cb = function(t)\n        local r = {}\n        for _, PQ in ipairs(t) do\n            local iface = PQ[1]\n            local R, S = PQ[2].R, PQ[2].S\n            r[#r + 1] = make_segment(iface, R, S)\n        end\n        return r\n    end,\n}\n"
  },
  {
    "path": "examples/dwm/pulse-gauge.lua",
    "content": "local GAUGE_NCHARS = 10\n\nlocal function mk_gauge(level, full, empty)\n    local nfull = math.floor(level * GAUGE_NCHARS + 0.5)\n    return full:rep(nfull) .. empty:rep(GAUGE_NCHARS - nfull)\nend\n\nwidget = {\n    plugin = 'pulse',\n    cb = function(t)\n        local level = t.cur / t.norm\n        if t.mute then\n            return mk_gauge(level, '×', '—')\n        else\n            return mk_gauge(level, '●', '○')\n        end\n    end,\n}\n"
  },
  {
    "path": "examples/dwm/pulse.lua",
    "content": "widget = {\n    plugin = 'pulse',\n    cb = function(t)\n        if t.mute then\n            return '[mute]'\n        end\n        local percent = (t.cur / t.norm) * 100\n        return string.format('[%3d%%]', math.floor(0.5 + percent))\n    end,\n}\n"
  },
  {
    "path": "examples/dwm/systemd-unit.lua",
    "content": "local function make_output(text)\n    return string.format('[Tor: %s]', text)\nend\n\nwidget = luastatus.require_plugin('systemd-unit').widget{\n    unit_name = 'tor.service',\n    cb = function(state)\n        if state == 'active' then\n            return make_output('✓')\n        elseif state == 'reloading' or state == 'activating' then\n            return make_output('•')\n        elseif state == 'inactive' or state == 'deactivating' then\n            return make_output('-')\n        elseif state == 'failed' then\n            return make_output('x')\n        else\n            return make_output('?')\n        end\n    end,\n}\n"
  },
  {
    "path": "examples/dwm/time-battery-combined.lua",
    "content": "local function get_bat_seg(t)\n    if not t then\n        return '[--×--]'\n    end\n    if t.status == 'Unknown' or t.status == 'Full' or t.status == 'Not charging' then\n        return nil\n    end\n    local sym = '?'\n    if t.status == 'Discharging' then\n        sym = '↓'\n    elseif t.status == 'Charging' then\n        sym = '↑'\n    end\n    return string.format('[%3d%%%s]', t.capacity, sym)\nend\n\nwidget = luastatus.require_plugin('battery-linux').widget{\n    period = 2,\n    cb = function(t)\n        return {\n            os.date('[%H:%M]'),\n            get_bat_seg(t),\n        }\n    end,\n}\n"
  },
  {
    "path": "examples/dwm/time-date.lua",
    "content": "local months = {\n    'января',\n    'февраля',\n    'марта',\n    'апреля',\n    'мая',\n    'июня',\n    'июля',\n    'августа',\n    'сентября',\n    'октября',\n    'ноября',\n    'декабря',\n}\nwidget = {\n    plugin = 'timer',\n    cb = function()\n        local d = os.date('*t')\n        return {\n            string.format('%d %s', d.day, months[d.month]),\n            string.format('%02d:%02d', d.hour, d.min),\n        }\n    end,\n}\n"
  },
  {
    "path": "examples/dwm/tor.lua",
    "content": "-- Trivial but somewhat useful widget showing if the Tor daemon is running.\n\nwidget = {\n    plugin = 'timer',\n    opts = {period = 5},\n    cb = function()\n        local f = io.open('/var/run/tor/tor.pid', 'r')\n        if f then\n            f:close()\n            return '[TOR]'\n        end\n    end,\n}\n"
  },
  {
    "path": "examples/dwm/uptime-linux.lua",
    "content": "local SUFFIXES_AND_DIVISORS = {\n    {'m', 60},\n    {'h', 60},\n    {'d', 24},\n    {'W', 7},\n    {'M', 30},\n    {'Y', 365 / 30},\n}\n\nlocal function seconds_to_human_readable_time(sec)\n    local prev_suffix = 's'\n    local found_suffix\n    for _, PQ in ipairs(SUFFIXES_AND_DIVISORS) do\n        local suffix = PQ[1]\n        local divisor = PQ[2]\n        if sec < divisor then\n            found_suffix = prev_suffix\n            break\n        end\n        sec = sec / divisor\n        prev_suffix = suffix\n    end\n    if not found_suffix then\n        found_suffix = prev_suffix\n    end\n    return string.format('%.0f%s', sec, found_suffix)\nend\n\nwidget = {\n    plugin = 'timer',\n    opts = {\n        period = 2,\n    },\n    cb = function(_)\n        local f = io.open('/proc/uptime', 'r')\n        local sec, _ = f:read('*number', '*number')\n        f:close()\n\n        assert(sec)\n\n        return string.format('[uptime: %s]', seconds_to_human_readable_time(sec))\n    end,\n}\n"
  },
  {
    "path": "examples/dwm/weather-rus.lua",
    "content": "local LOCATION_NAME = 'Moscow'\n\nlocal TIMEOUT = 10\n\nlocal INTERVAL = 9\nlocal SLEEP_AFTER_INITIAL = 5\n\nlocal coordinates = nil\n\nlocal function planner()\n    -- Get coordinates of LOCATION_NAME.\n    while true do\n        -- Form the URL\n        local url = string.format(\n            'https://geocoding-api.open-meteo.com/v1/search?name=%s&count=1',\n            luastatus.plugin.urlencode(LOCATION_NAME)\n        )\n        -- Make request\n        coroutine.yield({action = 'request', params = {\n            url = url,\n            timeout = TIMEOUT,\n        }})\n        -- If successful, break\n        if coordinates ~= nil then\n            break\n        end\n        -- Failure; sleep and retry\n        coroutine.yield({action = 'sleep', period = INTERVAL})\n    end\n\n    -- Coordinates fetched, sleep for SLEEP_AFTER_INITIAL.\n    coroutine.yield({action = 'sleep', period = SLEEP_AFTER_INITIAL})\n\n    -- Form the weather URL\n    local weather_url = string.format(\n        'https://api.open-meteo.com/v1/forecast' ..\n        '?latitude=%f' ..\n        '&longitude=%f' ..\n        '&current_weather=true',\n        coordinates.latitude,\n        coordinates.longitude\n    )\n\n    -- Fetch weather, periodically\n    while true do\n        -- Make request\n        coroutine.yield({action = 'request', params = {\n            url = weather_url,\n            timeout = TIMEOUT,\n        }})\n        -- Sleep and repeat\n        coroutine.yield({action = 'sleep', period = INTERVAL})\n    end\nend\n\nlocal function check_error(t)\n    if t.error then\n        -- Low-level libcurl error\n        return t.error\n    end\n    if t.status < 200 or t.status > 299 then\n        -- Bad HTTP status\n        return string.format('HTTP status %d', t.status)\n    end\n    -- Everything's OK\n    return nil\nend\n\nlocal WEATHER_CODES = {\n    [0] = 'Ясно',\n\n    [1] = 'Преимущественно ясно',\n    [2] = 'Переменная облачность',\n    [3] = 'Пасмурно',\n\n    [45] = 'Туман',\n    [48] = 'Осаждение инея и тумана',\n\n    [51] = 'Изморось, лёгкая',\n    [53] = 'Изморось, умеренная',\n    [55] = 'Изморось, сильная',\n\n    [56] = 'Ледяная изморось, лёгкая',\n    [57] = 'Ледяная изморось, сильная',\n\n    [61] = 'Дождь, лёгкий',\n    [63] = 'Дождь, умеренный',\n    [65] = 'Дождь, сильный',\n\n    [66] = 'Град, лёгкий',\n    [67] = 'Град, сильный',\n\n    [71] = 'Снегопад, лёгкий',\n    [73] = 'Снегопад, умеренный',\n    [75] = 'Снегопад, сильный',\n\n    [77] = 'Крупинки снега',\n\n    [80] = 'Ливень, лёгкий',\n    [81] = 'Ливень, умеренный',\n    [82] = 'Ливень, сильный',\n\n    [85] = 'Снеговые осадки, лёгкие',\n    [87] = 'Снеговые осадки, сильные',\n\n    [95] = 'Гроза',\n    [96] = 'Гроза, небольшой град',\n    [99] = 'Гроза, сильный град',\n}\n\nlocal function fmt_weather(temp, _, _, weather_code)\n    local segment1 = string.format('%.0f°', temp)\n    -- segment2 is either string or nil\n    local segment2 = WEATHER_CODES[weather_code]\n    return {segment1, segment2}\nend\n\nwidget = {\n    plugin = 'web',\n    opts = {\n        planner = planner,\n    },\n    cb = function(t)\n        local err_msg = check_error(t)\n        if err_msg then\n            print(string.format('WARNING: luastatus: weather widget: %s'), err_msg)\n            return '<!>'\n        end\n\n        if coordinates == nil then\n            local obj = assert(luastatus.plugin.json_decode(t.body))\n            local result = assert(obj.results[1])\n            local latitude = assert(result.latitude)\n            local longitude = assert(result.longitude)\n            coordinates = {latitude = latitude, longitude = longitude}\n            return '...'\n        end\n\n        local obj = assert(luastatus.plugin.json_decode(t.body))\n        local cw = assert(obj.current_weather)\n\n        local temp = assert(cw.temperature)\n        local wind_speed = assert(cw.windspeed)\n        -- is_day is an integer, either 0 or 1\n        local is_day = assert(cw.is_day) ~= 0\n        local weather_code = assert(cw.weathercode)\n\n        return fmt_weather(temp, wind_speed, is_day, weather_code)\n    end,\n}\n"
  },
  {
    "path": "examples/dwm/weather.lua",
    "content": "local LOCATION_NAME = 'Moscow'\n\nlocal TIMEOUT = 10\n\nlocal INTERVAL = 9\nlocal SLEEP_AFTER_INITIAL = 5\n\nlocal coordinates = nil\n\nlocal function planner()\n    -- Get coordinates of LOCATION_NAME.\n    while true do\n        -- Form the URL\n        local url = string.format(\n            'https://geocoding-api.open-meteo.com/v1/search?name=%s&count=1',\n            luastatus.plugin.urlencode(LOCATION_NAME)\n        )\n        -- Make request\n        coroutine.yield({action = 'request', params = {\n            url = url,\n            timeout = TIMEOUT,\n        }})\n        -- If successful, break\n        if coordinates ~= nil then\n            break\n        end\n        -- Failure; sleep and retry\n        coroutine.yield({action = 'sleep', period = INTERVAL})\n    end\n\n    -- Coordinates fetched, sleep for SLEEP_AFTER_INITIAL.\n    coroutine.yield({action = 'sleep', period = SLEEP_AFTER_INITIAL})\n\n    -- Form the weather URL\n    local weather_url = string.format(\n        'https://api.open-meteo.com/v1/forecast' ..\n        '?latitude=%f' ..\n        '&longitude=%f' ..\n        '&current_weather=true',\n        coordinates.latitude,\n        coordinates.longitude\n    )\n\n    -- Fetch weather, periodically\n    while true do\n        -- Make request\n        coroutine.yield({action = 'request', params = {\n            url = weather_url,\n            timeout = TIMEOUT,\n        }})\n        -- Sleep and repeat\n        coroutine.yield({action = 'sleep', period = INTERVAL})\n    end\nend\n\nlocal function check_error(t)\n    if t.error then\n        -- Low-level libcurl error\n        return t.error\n    end\n    if t.status < 200 or t.status > 299 then\n        -- Bad HTTP status\n        return string.format('HTTP status %d', t.status)\n    end\n    -- Everything's OK\n    return nil\nend\n\nlocal WEATHER_CODES = {\n    [0] = 'Clear sky',\n\n    [1] = 'Mainly clear',\n    [2] = 'Partly cloudy',\n    [3] = 'Overcast',\n\n    [45] = 'Fog',\n    [48] = 'Depositing rime fog',\n\n    [51] = 'Drizzle, light',\n    [53] = 'Drizzle, moderate',\n    [55] = 'Drizzle, dense',\n\n    [56] = 'Freezing drizzle, light',\n    [57] = 'Freezing drizzle, dense',\n\n    [61] = 'Rain, slight',\n    [63] = 'Rain, moderate',\n    [65] = 'Rain, heavy',\n\n    [66] = 'Freezing rain, light',\n    [67] = 'Freezing rain, heavy',\n\n    [71] = 'Snow fall, slight',\n    [73] = 'Snow fall, moderate',\n    [75] = 'Snow fall, heavy',\n\n    [77] = 'Snow grains',\n\n    [80] = 'Rain shower, slight',\n    [81] = 'Rain shower, moderate',\n    [82] = 'Rain shower, violent',\n\n    [85] = 'Snow showers, slight',\n    [87] = 'Snow showers, heavy',\n\n    [95] = 'Thunderstorm',\n    [96] = 'Thunderstorm, slight hail',\n    [99] = 'Thunderstorm, heavy hail',\n}\n\nlocal function fmt_weather(temp, _, _, weather_code)\n    local segment1 = string.format('%.0f°', temp)\n    -- segment2 is either string or nil\n    local segment2 = WEATHER_CODES[weather_code]\n    return {segment1, segment2}\nend\n\nwidget = {\n    plugin = 'web',\n    opts = {\n        planner = planner,\n    },\n    cb = function(t)\n        local err_msg = check_error(t)\n        if err_msg then\n            print(string.format('WARNING: luastatus: weather widget: %s'), err_msg)\n            return '<!>'\n        end\n\n        if coordinates == nil then\n            local obj = assert(luastatus.plugin.json_decode(t.body))\n            local result = assert(obj.results[1])\n            local latitude = assert(result.latitude)\n            local longitude = assert(result.longitude)\n            coordinates = {latitude = latitude, longitude = longitude}\n            return '...'\n        end\n\n        local obj = assert(luastatus.plugin.json_decode(t.body))\n        local cw = assert(obj.current_weather)\n\n        local temp = assert(cw.temperature)\n        local wind_speed = assert(cw.windspeed)\n        -- is_day is an integer, either 0 or 1\n        local is_day = assert(cw.is_day) ~= 0\n        local weather_code = assert(cw.weathercode)\n\n        return fmt_weather(temp, wind_speed, is_day, weather_code)\n    end,\n}\n"
  },
  {
    "path": "examples/dwm/wireless.lua",
    "content": "local MIN_DBM, MAX_DBM = -90, -20\nlocal NGAUGE = 5\n\nlocal function round(x)\n    return math.floor(x + 0.5)\nend\n\nlocal function make_wifi_gauge(dbm)\n    if dbm < MIN_DBM then dbm = MIN_DBM end\n    if dbm > MAX_DBM then dbm = MAX_DBM end\n    local nbright = round(NGAUGE * (1 - 0.7 * (MAX_DBM - dbm) / (MAX_DBM - MIN_DBM)))\n    return ('●'):rep(nbright) .. ('○'):rep(NGAUGE - nbright)\nend\n\nlocal function sorted_keys(tbl)\n    local keys = {}\n    for k, _ in pairs(tbl) do\n        keys[#keys + 1] = k\n    end\n    table.sort(keys)\n    return keys\nend\n\nwidget = {\n    plugin = 'network-linux',\n    opts = {\n        wireless = true,\n        timeout = 10,\n    },\n    cb = function(t)\n        if not t then\n            return nil\n        end\n\n        -- Sort for determinism\n        local ifaces = sorted_keys(t)\n\n        local r = {}\n        for _, iface in ipairs(ifaces) do\n            local params = t[iface]\n            if params.wireless then\n                if params.wireless.ssid then\n                    r[#r + 1] = params.wireless.ssid\n                end\n                if params.wireless.signal_dbm then\n                    r[#r + 1] = make_wifi_gauge(params.wireless.signal_dbm)\n                end\n            elseif iface ~= 'lo' and (params.ipv4 or params.ipv6) then\n                r[#r + 1] = string.format('[%s]', iface)\n            end\n        end\n        return r\n    end,\n}\n"
  },
  {
    "path": "examples/dwm/xkb.lua",
    "content": "widget = {\n    plugin = 'xkb',\n    cb = function(t)\n        if t.name then\n            local base_layout = t.name:match('[^(]+')\n            if base_layout == 'gb' or base_layout == 'us' then\n                return '[En]'\n            elseif base_layout == 'ru' then\n                return '[Ru]'\n            else\n                return '[' .. base_layout:sub(1, 1):upper() .. base_layout:sub(2) .. ']'\n            end\n        else\n            return '[? ID ' .. t.id .. ']'\n        end\n    end,\n}\n"
  },
  {
    "path": "examples/i3/alsa-gauge.lua",
    "content": "local GAUGE_NCHARS = 10\n\nlocal function mk_gauge(level, full, empty)\n    local nfull = math.floor(level * GAUGE_NCHARS + 0.5)\n    return full:rep(nfull) .. empty:rep(GAUGE_NCHARS - nfull)\nend\n\nwidget = {\n    plugin = 'alsa',\n    cb = function(t)\n        local level = (t.vol.cur - t.vol.min) / (t.vol.max - t.vol.min)\n        if t.mute then\n            return {full_text=mk_gauge(level, '×', '—'), color='#e03838'}\n        else\n            return {full_text=mk_gauge(level, '●', '○'), color='#718ba6'}\n        end\n    end,\n}\n"
  },
  {
    "path": "examples/i3/alsa-interactive-gauge.lua",
    "content": "-- So, it looks like this:\n--\n--     ••• | ██████████████       | [ 70%] | •••\n--\n--           ^ gauge block          ^ text block\n--\n-- The gauge block is initially hidden; click on the text block to toggle its visibility.\n-- Click anywhere at the gauge block to change the volume.\n-- Click with the right mouse button on either block to toggle mute state.\n\nlocal CARD = 'default'\nlocal CHANNEL = 'Master'\n\nlocal HBLOCKS = {' ', '▏', '▎', '▍', '▌', '▋', '▊', '▉', '█'}\nlocal GAUGE_NCHARS = 20\n\n-- i3bar adds the separator width to \"relative_x\" and \"width\" properties of a click event.\n--\n-- So let's do some multivariable equations (we assume a monospace font is used).\n--\n-- Let:\n--     * `x' be the separator width in pixels (unknown);\n--     * `y' be the width of one character in pixels (unknown);\n--     * `U' be the number of characters in the text block (known);\n--     * `V' be the number of characters in the gauge block (known);\n--     * `A' be the width, in pixels, of the text block (known when clicked);\n--     * `B' be the width, in pixels, on the gauge block (known when clicked).\n--\n-- We have:\n--     { x + U*y = A;\n--     { x + V*y = B.\n-- So,\n--     y = (A - B) / (U - V);\n--     x = A - U*y.\n\nlocal last_t = nil\nlocal text_block_nchars, text_block_width = nil, nil\nlocal gauge = false\n\nlocal function round(x)\n    return math.floor(x + 0.5)\nend\n\nlocal function mk_gauge(level)\n    local rel_level = level * GAUGE_NCHARS\n    local nfull = math.floor(rel_level)\n    local filled = HBLOCKS[#HBLOCKS]:rep(nfull)\n    if nfull == GAUGE_NCHARS then\n        return filled\n    end\n    local mid_idx = round((rel_level - nfull) * (#HBLOCKS - 1))\n    return filled .. HBLOCKS[1 + mid_idx] .. HBLOCKS[1]:rep(GAUGE_NCHARS - nfull - 1)\nend\n\nwidget = {\n    plugin = 'alsa',\n\n    opts = {\n        card = CARD,\n        channel = CHANNEL,\n        make_self_pipe = true,\n    },\n\n    cb = function(t)\n        last_t = t\n\n        local level = (t.vol.cur - t.vol.min) / (t.vol.max - t.vol.min)\n\n        local r = {}\n        if t.mute then\n            r[2] = {full_text='[mute]', color='#e03838'}\n        else\n            r[2] = {full_text=string.format('[%3d%%]', round(level * 100)), color='#718ba6'}\n        end\n        text_block_nchars = #r[2].full_text -- please note this does not work with Unicode.\n\n        if gauge then\n            local fg, bg = '#dcdcdc', '#444444'\n            if t.mute then\n                fg, bg = '#e03838', '#4a1414'\n            end\n            r[1] = {full_text=mk_gauge(level), color=fg, background=bg, instance='gauge'}\n        end\n\n        return r\n    end,\n\n    event = function(t)\n        if t.button == 1 then -- left mouse button\n            if t.instance == 'gauge' then\n                local char_width = round((t.width - text_block_width)\n                                         / (GAUGE_NCHARS - text_block_nchars))\n                local sep_width = text_block_width - text_block_nchars * char_width\n\n                local x = t.relative_x - sep_width\n                if x < 0 then\n                    return\n                end\n                local rawvol = round(\n                    last_t.vol.min +\n                    x / (t.width - sep_width) * (last_t.vol.max - last_t.vol.min))\n                assert(luastatus.execute(\n                    string.format('amixer -D \"%s\" set \"%s\" %s >/dev/null', CARD, CHANNEL, rawvol)))\n\n            else\n                gauge = not gauge\n                text_block_width = t.width\n                luastatus.plugin.wake_up()\n            end\n\n        elseif t.button == 3 then -- right mouse button\n            assert(luastatus.execute(string.format(\n                'amixer -D \"%s\" set \"%s\" toggle >/dev/null', CARD, CHANNEL)))\n        end\n    end,\n}\n"
  },
  {
    "path": "examples/i3/alsa.lua",
    "content": "widget = {\n    plugin = 'alsa',\n    cb = function(t)\n        if t.mute then\n            return {full_text = '[mute]', color = '#e03838'}\n        else\n            local percent = (t.vol.cur - t.vol.min) / (t.vol.max - t.vol.min) * 100\n            return {full_text = string.format('[%3d%%]', math.floor(0.5 + percent)), color = '#718ba6'}\n        end\n    end,\n}\n"
  },
  {
    "path": "examples/i3/backlight.lua",
    "content": "-- Note that this widget only shows backlight level when it changes.\nwidget = luastatus.require_plugin('backlight-linux').widget{\n    cb = function(level)\n        if level ~= nil then\n            return {full_text = string.format('*%3.0f%%', level * 100)}\n        end\n    end,\n}\n"
  },
  {
    "path": "examples/i3/battery.lua",
    "content": "widget = luastatus.require_plugin('battery-linux').widget{\n    period = 2,\n    cb = function(t)\n        local symbol = ({\n            Charging    = '↑',\n            Discharging = '↓',\n        })[t.status] or ' '\n        local rem_seg\n        if t.rem_time then\n            local h = math.floor(t.rem_time)\n            local m = math.floor(60 * (t.rem_time - h))\n            rem_seg = {full_text = string.format('%2dh %02dm', h, m), color = '#595959'}\n        end\n        return {\n            {full_text = string.format('%3d%%%s', t.capacity, symbol)},\n            rem_seg,\n        }\n    end,\n}\n"
  },
  {
    "path": "examples/i3/bluetooth.lua",
    "content": "-- A widget to display currently connected and paired bluetooth devices.\n-- To change output format modify reprint_devices function.\n\nif not luastatus.execute('command -v bluetoothctl >/dev/null') then\n    error('\"bluetoothctl\" command, which is required for this widget to work, was not found')\nend\n\nlocal separator = \" \"\n\n-- Object paths look like /org/bluez/hci0/dev_XX_XX_XX_XX_XX_XX/somethingsomething\nlocal function get_device_mac_address(device_object_path)\n    return device_object_path:gsub(\"/.*/dev_\", \"\"):gsub(\"/.*\", \"\"):gsub(\"_\", \":\")\nend\n\n-- For reference bluetoothctl devices output looks like that:\n-- Device XX:XX:XX:XX:XX:XX JBL T450BT\n-- Device YY:YY:YY:YY:YY:YY Redmi 8\n--\n-- Function returns mac addresses of all devices.\nlocal function get_devices()\n    local devices = {}\n    local handle = io.popen(string.format(\"bluetoothctl devices\"))\n    for line in handle:lines() do\n        local match = string.match(line, \"Device ([%x:]+)\")\n        if match then\n            table.insert(devices, match)\n        end\n    end\n    handle:close()\n    return devices\nend\n\n-- For reference bluetoothctl info output looks like that:\n-- Device XX:XX:XX:XX:XX:XX (public)\n--         Name: JBL T450BT\n--         Alias: JBL T450BT\n--         Class: 0xFFFFFFFF\n--         Icon: audio-card\n--         Paired: yes\n--         Trusted: yes\n--         Blocked: no\n--         Connected: yes\n--         LegacyPairing: no\n--         UUID: Headset                   (xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)\n--         ...\n--         UUID: Handsfree                 (xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)\n--\n-- Given this input function returns a following table:\n-- [alias]         string  JBL T450BT\n-- [blocked]       boolean false\n-- [class]         string  0x00240404\n-- [connected]     boolean true\n-- [icon]          string  audio\n-- [legacypairing] boolean false\n-- [name]          string  JBL T450BT\n-- [paired]        boolean true\n-- [trusted]       boolean true\nlocal function get_device_info(mac_address)\n    if mac_address == nil then\n        mac_address = \"\"\n    end\n\n    assert(string.match(mac_address, '^[%x:]*$') ~= nil)\n\n    local device_info = {}\n    local handle = io.popen(string.format(\"bluetoothctl info %s\", mac_address))\n    for line in handle:lines() do\n        local key, value = string.match(line, \"(%w+): (.*)\")\n        -- Filter junk\n        if key ~= \"UUID\" and key ~= nil and value ~= nil then\n            key = string.lower(key)\n            if key ~= \"name\" and key ~= \"alias\" and key ~= \"icon\" then\n                if value == \"yes\" then\n                    value = true\n                end\n                if value == \"no\" then\n                    value = false\n                end\n            end\n            device_info[key] = value\n        end\n    end\n    handle:close()\n    return device_info\nend\n\nlocal devices = {}\n\nlocal function reprint_devices()\n    local t = {}\n    for mac_address, device in pairs(devices) do\n        table.insert(t, string.format(\"%s(%s)\", device[\"name\"], mac_address))\n    end\n    return table.concat(t, separator)\nend\n\nwidget = {\n    plugin = \"dbus\",\n    opts = {\n        greet = true,\n        -- https://git.kernel.org/pub/scm/bluetooth/bluez.git/tree/doc/device-api.txt\n        signals = {\n            {\n                sender = \"org.bluez\",\n                interface = \"org.freedesktop.DBus.Properties\",\n                signal = \"PropertiesChanged\",\n                arg0 = \"org.bluez.Device1\",\n                bus = \"system\"\n            }\n        }\n    },\n    cb = function(t)\n        if t.what == \"hello\" then\n            local mac_addresses = get_devices()\n            for _, mac_address in ipairs(mac_addresses) do\n                local device = get_device_info(mac_address)\n                if device[\"connected\"] and device[\"paired\"] then\n                    devices[mac_address] = device\n                end\n            end\n        elseif t.what == \"signal\" then\n            -- For reference message from dbus looks like that:\n            -- table\n            -- [1]     string  org.bluez.Device1\n            -- [2]     table\n            -- [2]     [1]     table\n            -- [2]     [1]     [1]     string  SomethingSomething\n            -- [2]     [1]     [2]     boolean false\n            -- [2]     [2]     table\n            -- [2]     [2]     [1]     string  Connected\n            -- [2]     [2]     [2]     boolean true\n            -- [3]     table\n            if t.signal == \"PropertiesChanged\" then\n                for _, message in pairs(t.parameters[2]) do\n                    if message[1] == \"Connected\" or message[1] == \"Paired\" then\n                        local mac_address = get_device_mac_address(t.object_path)\n                        if message[2] then\n                            local device = get_device_info(mac_address)\n                            if device[\"paired\"] then\n                                devices[mac_address] = device\n                            end\n                        else\n                            devices[mac_address] = nil\n                        end\n                    end\n                end\n            end\n        end\n        return {full_text = reprint_devices()}\n    end\n}\n"
  },
  {
    "path": "examples/i3/btc-price.lua",
    "content": "-- Bitcoin price widget.\n-- Updates on click and every 5 minutes.\n\nlocal custom_sleep_amt = nil\n\nlocal function check_error(t)\n    if t.error then\n        -- Low-level libcurl error\n        return t.error\n    end\n    if t.status < 200 or t.status > 299 then\n        -- Bad HTTP status\n        return string.format('HTTP status %d', t.status)\n    end\n    -- Everything's OK\n    return nil\nend\n\nwidget = {\n    plugin = 'web',\n    opts = {\n        planner = function()\n            while true do\n                coroutine.yield({action = 'request', params = {\n                    url = 'https://api.binance.com/api/v3/ticker/price?symbol=BTCUSDT',\n                    timeout = 5,\n                }})\n                local period = custom_sleep_amt or (5 * 60)\n                custom_sleep_amt = nil\n                coroutine.yield({action = 'sleep', period = period})\n            end\n        end,\n        make_self_pipe = true,\n    },\n    cb = function(t)\n        local text\n        local err_msg = check_error(t)\n        if err_msg then\n            print(string.format('WARNING: luastatus: btc-price widget: %s'), err_msg)\n            text = '......'\n            custom_sleep_amt = 5\n        else\n            local obj = assert(luastatus.plugin.json_decode(t.body))\n            text = obj.price:match('[^.]+')\n        end\n        return {\n            full_text = string.format(\n                '[<span color=\"#C0863F\">$</span>%s]',\n                luastatus.barlib.pango_escape(text)\n            ),\n            color = '#586A4B',\n            markup = 'pango',\n        }\n    end,\n    event = function(t)\n        if t.button == 1 then\n            luastatus.plugin.wake_up()\n        end\n    end,\n}\n"
  },
  {
    "path": "examples/i3/cpu-freq.lua",
    "content": "local VBLOCKS = {'▂', '▃', '▄', '▅', '▆', '▇', '█'}\n\nlocal function make_color(ratio)\n    local red = math.floor(0.5 + 255 * ratio)\n    return string.format('#%02x%02x00', red, 255 - red)\nend\n\nlocal function make_chunk(entry)\n    local num = entry.cur - entry.min\n    local denom = entry.max - entry.min\n\n    local ratio\n    if denom ~= 0 then\n        ratio = num / denom\n    else\n        -- If max_freq == min_freq, set ratio to zero.\n        ratio = 0\n    end\n\n    local vblock_idx = math.min(\n        1 + math.floor(0.5 + ratio * #VBLOCKS),\n        #VBLOCKS)\n\n    return string.format(\"<span color='%s'>%s</span>\", make_color(ratio), VBLOCKS[vblock_idx])\nend\n\nlocal plugin_data = {}\n\nlocal plugin_params = {\n    timer_opts = {\n        period = 2,\n    },\n    cb = function(t)\n        if t == nil then\n            return nil\n        end\n        local r = {}\n        for _, entry in ipairs(t) do\n            r[#r + 1] = make_chunk(entry)\n        end\n        return {full_text = table.concat(r), markup = 'pango'}\n    end,\n    event = function(t)\n        if t.button == 1 then\n            plugin_data.please_reload = true\n        end\n    end,\n}\n\nwidget = luastatus.require_plugin('cpu-freq-linux').widget(plugin_params, plugin_data)\n"
  },
  {
    "path": "examples/i3/cpu-temperature.lua",
    "content": "local COOL_TEMP = 50\nlocal HEAT_TEMP = 75\nlocal function getcolor(temp)\n    local t = (temp - COOL_TEMP) / (HEAT_TEMP - COOL_TEMP)\n    if t < 0 then t = 0 end\n    if t > 1 then t = 1 end\n    local red = math.floor(t * 255 + 0.5)\n    return string.format('#%02x%02x00', red, 255 - red)\nend\n\nlocal plugin_data = {}\n\nlocal plugin_params = {\n    timer_opts = {\n        period = 2,\n    },\n    cb = function(t)\n        if not t then\n            return nil\n        end\n        local r = {}\n        for _, entry in ipairs(t) do\n            local temp = entry.value\n            r[#r + 1] = {full_text = string.format('%.0f°', temp), color = getcolor(temp)}\n        end\n        return r\n    end,\n    event = function(t)\n        if t.button == 1 then\n            plugin_data.please_reload = true\n        end\n    end,\n}\n\nwidget = luastatus.require_plugin('cpu-temp-linux').widget(plugin_params, plugin_data)\n"
  },
  {
    "path": "examples/i3/cpu-usage.lua",
    "content": "widget = luastatus.require_plugin('cpu-usage-linux').widget{\n    cb = function(usage)\n        if usage ~= nil then\n            return {full_text = string.format('[%5.1f%%]', usage * 100)}\n        end\n    end,\n}\n"
  },
  {
    "path": "examples/i3/disk-io.lua",
    "content": "widget = luastatus.require_plugin('disk-io-linux').widget{\n    period = 2,\n    cb = function(t)\n        -- Sort by name for determinism\n        table.sort(t, function(a, b) return a.name < b.name end)\n\n        local segments = {}\n        for _, entry in ipairs(t) do\n            local R = entry.read_bytes\n            local W = entry.written_bytes\n            if (R >= 0) and (W >= 0) then\n                segments[#segments + 1] = {\n                    full_text = string.format(\n                        '%s: <span color=\"#DCA3A3\">%.0fk↓</span> <span color=\"#72D5A3\">%.0fk↑</span>',\n                        entry.name,\n                        R / 1024,\n                        W / 1024\n                    ),\n                    markup = 'pango',\n                }\n            end\n        end\n        return segments\n    end,\n}\n"
  },
  {
    "path": "examples/i3/file-contents.lua",
    "content": "widget = luastatus.require_plugin('file-contents-linux').widget{\n    filename = os.getenv('HOME') .. '/status',\n    cb = function(f)\n        -- show the first line of the file\n        return {full_text = f:read('*line')}\n    end,\n}\n"
  },
  {
    "path": "examples/i3/fs.lua",
    "content": "local function sorted_keys(tbl)\n    local keys = {}\n    for k, _ in pairs(tbl) do\n        keys[#keys + 1] = k\n    end\n    table.sort(keys)\n    return keys\nend\n\nwidget = {\n    plugin = 'fs',\n    opts = {\n        paths = {'/', '/home'},\n    },\n    cb = function(t)\n        -- Sort for determinism\n        local keys = sorted_keys(t)\n        local res = {}\n        for _, k in ipairs(keys) do\n            local v = t[k]\n            table.insert(res, {\n                full_text = string.format('%s %.0f%%',\n                    k, (1 - v.avail / v.total) * 100),\n                instance = k,\n            })\n        end\n        return res\n    end,\n}\n"
  },
  {
    "path": "examples/i3/gmail.lua",
    "content": "--[[\n-- Expects 'credentials.lua' to be present in the current directory; it may contain, e.g.,\n--     return {\n--         gmail = {\n--             login = 'john.smith',\n--             password = 'qwerty'\n--         }\n--     }\n--]]\nlocal credentials = require 'credentials'\n\nwidget = luastatus.require_plugin('imap').widget{\n    verbose = false,\n    host = 'imap.gmail.com',\n    port = 993,\n    mailbox = 'Inbox',\n    use_ssl = true,\n    timeout = 2 * 60,\n    handshake_timeout = 10,\n    login = credentials.gmail.login,\n    password = credentials.gmail.password,\n    error_sleep_period = 60,\n    cb = function(unseen)\n        if unseen == nil then\n            return nil\n        elseif unseen == 0 then\n            return {full_text = '[-]', color = '#595959'}\n        else\n            return {full_text = string.format('[%d unseen]', unseen)}\n        end\n    end,\n    event = [[\n        local t = ...\n        if t.button == 1 then\n            os.execute('xdg-open https://gmail.com &')\n        end\n    ]],\n}\n"
  },
  {
    "path": "examples/i3/ip.lua",
    "content": "widget = {\n    plugin = 'network-linux',\n    cb = function(t)\n        local r = {}\n        for iface, params in pairs(t) do\n            local iface_clean = iface\n            local addr = params.ipv6 or params.ipv4\n            if addr then\n                -- strip out \"label\" from the interface name\n                iface_clean = iface_clean:gsub(':.*', '')\n                -- strip out \"zone index\" from the address\n                addr = addr:gsub('%%.*', '')\n\n                if iface_clean ~= 'lo' then\n                    r[#r + 1] = {\n                        full_text = string.format('[%s: %s]', iface_clean, addr),\n                        color = '#709080',\n                    }\n                end\n            end\n        end\n        return r\n    end,\n}\n"
  },
  {
    "path": "examples/i3/loadavg-linux.lua",
    "content": "local function get_ncpus()\n    local f = assert(io.open('/proc/cpuinfo', 'r'))\n    local n = 0\n    for line in f:lines() do\n        if line:match('^processor\\t') then\n            n = n + 1\n        end\n    end\n    f:close()\n    return n\nend\n\nlocal function avg2str(x)\n    assert(x >= 0)\n    if x >= 1000 then\n        return '↑↑↑'\n    end\n    return string.format('%3.0f', x)\nend\n\nwidget = {\n    plugin = 'timer',\n    opts = {\n        period = 2,\n    },\n    cb = function(_)\n        local f = io.open('/proc/loadavg', 'r')\n        local avg1, avg5, avg15 = f:read('*number', '*number', '*number')\n        f:close()\n\n        assert(avg1 and avg5 and avg15)\n\n        local ncpus = get_ncpus()\n\n        return {full_text = string.format(\n            '[%s%% %s%% %s%%]',\n            avg2str(avg1  / ncpus * 100),\n            avg2str(avg5  / ncpus * 100),\n            avg2str(avg15 / ncpus * 100)\n        )}\n    end,\n}\n"
  },
  {
    "path": "examples/i3/media-player-mpris.lua",
    "content": "local PLAYER = 'clementine'\n\nlocal PLAYBACK_STATUS_ICONS = {\n    Playing = '▶',\n    Paused  = '◆',\n    Stopped = '—',\n}\n\nlocal COLOR_BRIGHT = '#60b177'\nlocal COLOR_DIM = '#9fb15d'\n\nlocal function fetch_metadata_field(t, key)\n    if t.Metadata then\n        return t.Metadata[key]\n    else\n        return nil\n    end\nend\n\nlocal function do_call_method(method_name)\n    local is_ok, err_msg = luastatus.plugin.call_method_str({\n        bus = 'session',\n        dest = 'org.mpris.MediaPlayer2.' .. PLAYER,\n        object_path = '/org/mpris/MediaPlayer2',\n        interface = 'org.mpris.MediaPlayer2.Player',\n        method = method_name,\n        -- no arg_str\n    })\n    if not is_ok then\n        print(string.format(\n            'WARNING: luastatus: mpris widget: cannot call method \"%s\": %s',\n            method_name, err_msg\n        ))\n    end\nend\n\nwidget = luastatus.require_plugin('mpris').widget{\n    player = PLAYER,\n    cb = function(t)\n        if not t.PlaybackStatus then\n            return nil\n        end\n\n        local title = fetch_metadata_field(t, 'xesam:title')\n        title = title or ''\n\n        title = luastatus.libwidechar.make_valid_and_printable(title, '?')\n        title = luastatus.libwidechar.truncate_to_width(title, 40)\n        title = title or ''\n\n        local icon = PLAYBACK_STATUS_ICONS[t.PlaybackStatus] or '?'\n\n        local segments = {\n            {full_text = '←',  color = COLOR_DIM,    instance = 'prev'},\n            {full_text = icon, color = COLOR_BRIGHT, instance = 'status'},\n            {full_text = '→',  color = COLOR_DIM,    instance = 'next'},\n        }\n\n        if title ~= '' then\n            table.insert(segments, {\n                full_text = title,\n                color = COLOR_BRIGHT,\n            })\n        end\n\n        return segments\n    end,\n    event = function(t)\n        if t.button ~= 1 then\n            return\n        end\n        if t.instance == 'status' then\n            do_call_method('PlayPause')\n        elseif t.instance == 'prev' then\n            do_call_method('Previous')\n        elseif t.instance == 'next' then\n            do_call_method('Next')\n        end\n    end,\n}\n"
  },
  {
    "path": "examples/i3/mem-usage.lua",
    "content": "widget = luastatus.require_plugin('mem-usage-linux').widget{\n    timer_opts = {period = 2},\n    cb = function(t)\n        local used_kb = t.total.value - t.avail.value\n        return {full_text = string.format('[%3.2f GiB]', used_kb / 1024 / 1024), color = '#af8ec3'}\n    end,\n}\n"
  },
  {
    "path": "examples/i3/mpd-progressbar.lua",
    "content": "local text, time, total, is_playing = nil, nil, nil, false\nlocal timeout = 2\nlocal titlewidth = 40\n\nlocal function W_split(str, boundary)\n    local head, _ = luastatus.libwidechar.truncate_to_width(str, boundary)\n    assert(head)\n    local tail = str:sub(1 + #head)\n    return head, tail\nend\n\nwidget = {\n    plugin = 'mpd',\n    opts = {\n        timeout = timeout,\n    },\n    cb = function(t)\n        if t.what == 'update' then\n            -- build title\n            local title\n            if t.song.Title then\n                title = t.song.Title\n                if t.song.Artist then\n                    title = t.song.Artist .. ': ' .. title\n                end\n            else\n                title = t.song.file or ''\n            end\n\n            title = luastatus.libwidechar.make_valid(title, '?')\n\n            if assert(luastatus.libwidechar.width(title)) > titlewidth then\n                title = luastatus.libwidechar.truncate_to_width(title, titlewidth - 1) .. '…'\n            end\n\n            -- build text\n            text = string.format('%s %s',\n                ({play = '▶', pause = '‖', stop = '■'})[t.status.state],\n                title\n            )\n\n            -- update other globals\n            if t.status.time then\n                time, total = t.status.time:match('(%d+):(%d+)')\n                time, total = tonumber(time), tonumber(total)\n            else\n                time, total = nil, nil\n            end\n            is_playing = t.status.state == 'play'\n\n        elseif t.what == 'timeout' then\n            if is_playing then\n                time = math.min(time + timeout, total)\n            end\n\n        else\n            -- 'connecting' or 'error'\n            return {full_text = t.what}\n        end\n\n        -- calc progress\n        local width = assert(luastatus.libwidechar.width(text))\n        -- 'time' and 'total' can be nil here, we check for 'total' only\n        local ulpos = (total and total ~= 0)\n                        and math.floor(width / total * time + 0.5)\n                        or 0\n\n        local head, tail = W_split(text, ulpos)\n\n        return {full_text = '<u>' .. luastatus.barlib.pango_escape(head) ..\n                            '</u>' .. luastatus.barlib.pango_escape(tail),\n                markup = 'pango'}\n    end\n}\n"
  },
  {
    "path": "examples/i3/network-rate.lua",
    "content": "local function make_segment(iface, R, S)\n    return {\n        full_text = string.format('[%s <span color=\"#DCA3A3\">%.0fk↓</span> <span color=\"#72D5A3\">%.0fk↑</span>]',\n            iface,\n            R / 1000,\n            S / 1000\n        ),\n        markup = 'pango',\n    }\nend\n\nwidget = luastatus.require_plugin('network-rate-linux').widget{\n    iface_except = 'lo',\n    period = 3,\n    in_array_form = true,\n    cb = function(t)\n        local r = {}\n        for _, PQ in ipairs(t) do\n            local iface = PQ[1]\n            local R, S = PQ[2].R, PQ[2].S\n            r[#r + 1] = make_segment(iface, R, S)\n        end\n        return r\n    end,\n}\n"
  },
  {
    "path": "examples/i3/pulse-gauge.lua",
    "content": "local GAUGE_NCHARS = 10\n\nlocal function mk_gauge(level, full, empty)\n    local nfull = math.floor(level * GAUGE_NCHARS + 0.5)\n    return full:rep(nfull) .. empty:rep(GAUGE_NCHARS - nfull)\nend\n\nwidget = {\n    plugin = 'pulse',\n    cb = function(t)\n        local level = t.cur / t.norm\n        if t.mute then\n            return {full_text=mk_gauge(level, '×', '—'), color='#e03838'}\n        else\n            return {full_text=mk_gauge(level, '●', '○'), color='#718ba6'}\n        end\n    end,\n}\n"
  },
  {
    "path": "examples/i3/pulse-interactive-gauge.lua",
    "content": "-- So, it looks like this:\n--\n--     ••• | ██████████████       | [ 70%] | •••\n--\n--           ^ gauge block          ^ text block\n--\n-- The gauge block is initially hidden; click on the text block to toggle its visibility.\n-- Click anywhere at the gauge block to change the volume.\n-- Click with the right mouse button on either block to toggle mute state.\nlocal SINK = '@DEFAULT_SINK@'\n\nlocal HBLOCKS = {' ', '▏', '▎', '▍', '▌', '▋', '▊', '▉', '█'}\nlocal GAUGE_NCHARS = 20\n\n-- i3bar adds the separator width to \"relative_x\" and \"width\" properties of a click event.\n--\n-- So let's do some multivariable equations (we assume a monospace font is used).\n--\n-- Let:\n--     * `x' be the separator width in pixels (unknown);\n--     * `y' be the width of one character in pixels (unknown);\n--     * `U' be the number of characters in the text block (known);\n--     * `V' be the number of characters in the gauge block (known);\n--     * `A' be the width, in pixels, of the text block (known when clicked);\n--     * `B' be the width, in pixels, on the gauge block (known when clicked).\n--\n-- We have:\n--     { x + U*y = A;\n--     { x + V*y = B.\n-- So,\n--     y = (A - B) / (U - V);\n--     x = A - U*y.\n\nlocal last_t = nil\nlocal text_block_nchars, text_block_width = nil, nil\nlocal gauge = false\n\nlocal function round(x)\n    return math.floor(x + 0.5)\nend\n\nlocal function mk_gauge(level)\n    local rel_level = level * GAUGE_NCHARS\n    local nfull = math.floor(rel_level)\n    local filled = HBLOCKS[#HBLOCKS]:rep(nfull)\n    if nfull == GAUGE_NCHARS then\n        return filled\n    end\n    local mid_idx = round((rel_level - nfull) * (#HBLOCKS - 1))\n    return filled .. HBLOCKS[1 + mid_idx] .. HBLOCKS[1]:rep(GAUGE_NCHARS - nfull - 1)\nend\n\nwidget = {\n    plugin = 'pulse',\n\n    opts = {\n        sink = SINK,\n        make_self_pipe = true,\n    },\n\n    cb = function(t)\n        if t == nil then\n            t = last_t\n        else\n            last_t = t\n        end\n\n        local level = t.cur / t.norm\n\n        local r = {}\n        if t.mute then\n            r[2] = {full_text='[mute]', color='#e03838'}\n        else\n            r[2] = {full_text=string.format('[%3d%%]', round(level * 100)), color='#718ba6'}\n        end\n        text_block_nchars = #r[2].full_text -- please note this does not work with Unicode.\n\n        if gauge then\n            local fg, bg = '#dcdcdc', '#444444'\n            if t.mute then\n                fg, bg = '#e03838', '#4a1414'\n            end\n            r[1] = {full_text=mk_gauge(level), color=fg, background=bg, instance='gauge'}\n        end\n\n        return r\n    end,\n\n    event = function(t)\n        if t.button == 1 then -- left mouse button\n            if t.instance == 'gauge' then\n                local char_width = round((t.width - text_block_width)\n                                         / (GAUGE_NCHARS - text_block_nchars))\n                local sep_width = text_block_width - text_block_nchars * char_width\n\n                local x = t.relative_x - sep_width\n                if x < 0 then\n                    return\n                end\n                local rawvol = round(x / (t.width - sep_width) * last_t.norm)\n                os.execute(string.format(\n                    'pactl set-sink-volume \"%s\" %s', SINK, rawvol))\n\n            else\n                gauge = not gauge\n                text_block_width = t.width\n                luastatus.plugin.wake_up()\n            end\n\n        elseif t.button == 3 then -- right mouse button\n            os.execute(string.format(\n                'pactl set-sink-mute \"%s\" toggle', SINK))\n        end\n    end,\n}\n"
  },
  {
    "path": "examples/i3/pulse.lua",
    "content": "widget = {\n    plugin = 'pulse',\n    cb = function(t)\n        if t.mute then\n            return {full_text = '[mute]', color = '#e03838'}\n        end\n        local percent = (t.cur / t.norm) * 100\n        return {full_text = string.format('[%3d%%]', math.floor(0.5 + percent)), color = '#718ba6'}\n    end,\n}\n"
  },
  {
    "path": "examples/i3/systemd-unit.lua",
    "content": "local COLORS = {\n    pink   = '#ec93d3',\n    green  = '#60b48a',\n    yellow = '#dfaf8f',\n    red    = '#dca3a3',\n    dim    = '#909090',\n}\n\nlocal function make_output(text, color_name)\n    local color = assert(COLORS[color_name])\n    local body = string.format('[Tor: <span color=\"%s\">%s</span>]', color, text)\n    return {full_text = body, markup = 'pango'}\nend\n\nwidget = luastatus.require_plugin('systemd-unit').widget{\n    unit_name = 'tor.service',\n    cb = function(state)\n        if state == 'active' then\n            return make_output('✓', 'green')\n        elseif state == 'reloading' or state == 'activating' then\n            return make_output('•', 'yellow')\n        elseif state == 'inactive' or state == 'deactivating' then\n            return make_output('-', 'dim')\n        elseif state == 'failed' then\n            return make_output('x', 'red')\n        else\n            return make_output('?', 'pink')\n        end\n    end,\n}\n"
  },
  {
    "path": "examples/i3/time-battery-combined.lua",
    "content": "local function get_bat_seg(t)\n    if not t then\n        return {full_text = '[--×--]'}\n    end\n    if t.status == 'Unknown' or t.status == 'Full' or t.status == 'Not charging' then\n        return nil\n    end\n    local sym, color = '?', '#dcdcdc'\n    if t.status == 'Discharging' then\n        sym = '↓'\n        color = '#dca3a3'\n    elseif t.status == 'Charging' then\n        sym = '↑'\n        color = '#60b48a'\n    end\n    return {full_text = string.format('[%3d%%%s]', t.capacity, sym), color = color}\nend\n\nwidget = luastatus.require_plugin('battery-linux').widget{\n    period = 2,\n    cb = function(t)\n        return {\n            {full_text = os.date('[%H:%M]'), color = '#dc8cc3'},\n            get_bat_seg(t),\n        }\n    end,\n}\n"
  },
  {
    "path": "examples/i3/time-date.lua",
    "content": "local months = {'января', 'февраля', 'марта', 'апреля', 'мая', 'июня', 'июля', 'августа', 'сентября',\n    'октября', 'ноября', 'декабря'}\nwidget = {\n    plugin = 'timer',\n    cb = function()\n        local d = os.date('*t')\n        return {\n            {full_text = string.format('%d %s', d.day, months[d.month])},\n            {full_text = string.format('%02d:%02d', d.hour, d.min)},\n        }\n    end,\n}\n"
  },
  {
    "path": "examples/i3/tor.lua",
    "content": "-- Trivial but somewhat useful widget showing if the Tor daemon is running.\n\nwidget = {\n    plugin = 'timer',\n    opts = {period = 5},\n    cb = function()\n        local f = io.open('/var/run/tor/tor.pid', 'r')\n        if f then\n            f:close()\n            return {full_text = '[TOR]'}\n        else\n            return nil\n        end\n    end,\n}\n"
  },
  {
    "path": "examples/i3/update-on-click.lua",
    "content": "widget = {\n    plugin = 'timer',\n    opts = {\n        make_self_pipe = true,\n        period = 2.0,\n    },\n    cb = function(t)\n        if t == 'self_pipe' then\n            return {full_text = 'Thanks!'}\n        else\n            return {full_text = 'Click me'}\n        end\n    end,\n    event = function(_)\n        luastatus.plugin.wake_up()\n    end,\n}\n"
  },
  {
    "path": "examples/i3/uptime-linux.lua",
    "content": "local SUFFIXES_AND_DIVISORS = {\n    {'m', 60},\n    {'h', 60},\n    {'d', 24},\n    {'W', 7},\n    {'M', 30},\n    {'Y', 365 / 30},\n}\n\nlocal function seconds_to_human_readable_time(sec)\n    local prev_suffix = 's'\n    local found_suffix\n    for _, PQ in ipairs(SUFFIXES_AND_DIVISORS) do\n        local suffix = PQ[1]\n        local divisor = PQ[2]\n        if sec < divisor then\n            found_suffix = prev_suffix\n            break\n        end\n        sec = sec / divisor\n        prev_suffix = suffix\n    end\n    if not found_suffix then\n        found_suffix = prev_suffix\n    end\n    return string.format('%.0f%s', sec, found_suffix)\nend\n\nwidget = {\n    plugin = 'timer',\n    opts = {\n        period = 2,\n    },\n    cb = function(_)\n        local f = io.open('/proc/uptime', 'r')\n        local sec, _ = f:read('*number', '*number')\n        f:close()\n\n        assert(sec)\n\n        return {full_text = string.format(\n            '[uptime: %s]',\n            seconds_to_human_readable_time(sec)\n        )}\n    end,\n}\n"
  },
  {
    "path": "examples/i3/weather-rus.lua",
    "content": "local LOCATION_NAME = 'Moscow'\n\nlocal TIMEOUT = 10\n\nlocal INTERVAL = 9\nlocal SLEEP_AFTER_INITIAL = 5\n\nlocal coordinates = nil\n\nlocal function planner()\n    -- Get coordinates of LOCATION_NAME.\n    while true do\n        -- Form the URL\n        local url = string.format(\n            'https://geocoding-api.open-meteo.com/v1/search?name=%s&count=1',\n            luastatus.plugin.urlencode(LOCATION_NAME)\n        )\n        -- Make request\n        coroutine.yield({action = 'request', params = {\n            url = url,\n            timeout = TIMEOUT,\n        }})\n        -- If successful, break\n        if coordinates ~= nil then\n            break\n        end\n        -- Failure; sleep and retry\n        coroutine.yield({action = 'sleep', period = INTERVAL})\n    end\n\n    -- Coordinates fetched, sleep for SLEEP_AFTER_INITIAL.\n    coroutine.yield({action = 'sleep', period = SLEEP_AFTER_INITIAL})\n\n    -- Form the weather URL\n    local weather_url = string.format(\n        'https://api.open-meteo.com/v1/forecast' ..\n        '?latitude=%f' ..\n        '&longitude=%f' ..\n        '&current_weather=true',\n        coordinates.latitude,\n        coordinates.longitude\n    )\n\n    -- Fetch weather, periodically\n    while true do\n        -- Make request\n        coroutine.yield({action = 'request', params = {\n            url = weather_url,\n            timeout = TIMEOUT,\n        }})\n        -- Sleep and repeat\n        coroutine.yield({action = 'sleep', period = INTERVAL})\n    end\nend\n\nlocal function check_error(t)\n    if t.error then\n        -- Low-level libcurl error\n        return t.error\n    end\n    if t.status < 200 or t.status > 299 then\n        -- Bad HTTP status\n        return string.format('HTTP status %d', t.status)\n    end\n    -- Everything's OK\n    return nil\nend\n\nlocal WEATHER_CODES = {\n    [0] = 'Ясно',\n\n    [1] = 'Преимущественно ясно',\n    [2] = 'Переменная облачность',\n    [3] = 'Пасмурно',\n\n    [45] = 'Туман',\n    [48] = 'Осаждение инея и тумана',\n\n    [51] = 'Изморось, лёгкая',\n    [53] = 'Изморось, умеренная',\n    [55] = 'Изморось, сильная',\n\n    [56] = 'Ледяная изморось, лёгкая',\n    [57] = 'Ледяная изморось, сильная',\n\n    [61] = 'Дождь, лёгкий',\n    [63] = 'Дождь, умеренный',\n    [65] = 'Дождь, сильный',\n\n    [66] = 'Град, лёгкий',\n    [67] = 'Град, сильный',\n\n    [71] = 'Снегопад, лёгкий',\n    [73] = 'Снегопад, умеренный',\n    [75] = 'Снегопад, сильный',\n\n    [77] = 'Крупинки снега',\n\n    [80] = 'Ливень, лёгкий',\n    [81] = 'Ливень, умеренный',\n    [82] = 'Ливень, сильный',\n\n    [85] = 'Снеговые осадки, лёгкие',\n    [87] = 'Снеговые осадки, сильные',\n\n    [95] = 'Гроза',\n    [96] = 'Гроза, небольшой град',\n    [99] = 'Гроза, сильный град',\n}\n\nlocal function fmt_weather(temp, _, _, weather_code)\n    local segment1 = {full_text = string.format('%.0f°', temp)}\n    local segment2 = nil\n    local weather = WEATHER_CODES[weather_code]\n    if weather then\n        segment2 = {full_text = weather}\n    end\n    return {segment1, segment2}\nend\n\nwidget = {\n    plugin = 'web',\n    opts = {\n        planner = planner,\n    },\n    cb = function(t)\n        local err_msg = check_error(t)\n        if err_msg then\n            print(string.format('WARNING: luastatus: weather widget: %s'), err_msg)\n            return {full_text = '...', color = '#e03838'}\n        end\n\n        if coordinates == nil then\n            local obj = assert(luastatus.plugin.json_decode(t.body))\n            local result = assert(obj.results[1])\n            local latitude = assert(result.latitude)\n            local longitude = assert(result.longitude)\n            coordinates = {latitude = latitude, longitude = longitude}\n            return {full_text = '...', color = '#709080'}\n        end\n\n        local obj = assert(luastatus.plugin.json_decode(t.body))\n        local cw = assert(obj.current_weather)\n\n        local temp = assert(cw.temperature)\n        local wind_speed = assert(cw.windspeed)\n        -- is_day is an integer, either 0 or 1\n        local is_day = assert(cw.is_day) ~= 0\n        local weather_code = assert(cw.weathercode)\n\n        return fmt_weather(temp, wind_speed, is_day, weather_code)\n    end,\n}\n"
  },
  {
    "path": "examples/i3/weather.lua",
    "content": "local LOCATION_NAME = 'Moscow'\n\nlocal TIMEOUT = 10\n\nlocal INTERVAL = 9\nlocal SLEEP_AFTER_INITIAL = 5\n\nlocal coordinates = nil\n\nlocal function planner()\n    -- Get coordinates of LOCATION_NAME.\n    while true do\n        -- Form the URL\n        local url = string.format(\n            'https://geocoding-api.open-meteo.com/v1/search?name=%s&count=1',\n            luastatus.plugin.urlencode(LOCATION_NAME)\n        )\n        -- Make request\n        coroutine.yield({action = 'request', params = {\n            url = url,\n            timeout = TIMEOUT,\n        }})\n        -- If successful, break\n        if coordinates ~= nil then\n            break\n        end\n        -- Failure; sleep and retry\n        coroutine.yield({action = 'sleep', period = INTERVAL})\n    end\n\n    -- Coordinates fetched, sleep for SLEEP_AFTER_INITIAL.\n    coroutine.yield({action = 'sleep', period = SLEEP_AFTER_INITIAL})\n\n    -- Form the weather URL\n    local weather_url = string.format(\n        'https://api.open-meteo.com/v1/forecast' ..\n        '?latitude=%f' ..\n        '&longitude=%f' ..\n        '&current_weather=true',\n        coordinates.latitude,\n        coordinates.longitude\n    )\n\n    -- Fetch weather, periodically\n    while true do\n        -- Make request\n        coroutine.yield({action = 'request', params = {\n            url = weather_url,\n            timeout = TIMEOUT,\n        }})\n        -- Sleep and repeat\n        coroutine.yield({action = 'sleep', period = INTERVAL})\n    end\nend\n\nlocal function check_error(t)\n    if t.error then\n        -- Low-level libcurl error\n        return t.error\n    end\n    if t.status < 200 or t.status > 299 then\n        -- Bad HTTP status\n        return string.format('HTTP status %d', t.status)\n    end\n    -- Everything's OK\n    return nil\nend\n\nlocal WEATHER_CODES = {\n    [0] = 'Clear sky',\n\n    [1] = 'Mainly clear',\n    [2] = 'Partly cloudy',\n    [3] = 'Overcast',\n\n    [45] = 'Fog',\n    [48] = 'Depositing rime fog',\n\n    [51] = 'Drizzle, light',\n    [53] = 'Drizzle, moderate',\n    [55] = 'Drizzle, dense',\n\n    [56] = 'Freezing drizzle, light',\n    [57] = 'Freezing drizzle, dense',\n\n    [61] = 'Rain, slight',\n    [63] = 'Rain, moderate',\n    [65] = 'Rain, heavy',\n\n    [66] = 'Freezing rain, light',\n    [67] = 'Freezing rain, heavy',\n\n    [71] = 'Snow fall, slight',\n    [73] = 'Snow fall, moderate',\n    [75] = 'Snow fall, heavy',\n\n    [77] = 'Snow grains',\n\n    [80] = 'Rain shower, slight',\n    [81] = 'Rain shower, moderate',\n    [82] = 'Rain shower, violent',\n\n    [85] = 'Snow showers, slight',\n    [87] = 'Snow showers, heavy',\n\n    [95] = 'Thunderstorm',\n    [96] = 'Thunderstorm, slight hail',\n    [99] = 'Thunderstorm, heavy hail',\n}\n\nlocal function fmt_weather(temp, _, _, weather_code)\n    local segment1 = {full_text = string.format('%.0f°', temp)}\n    local segment2 = nil\n    local weather = WEATHER_CODES[weather_code]\n    if weather then\n        segment2 = {full_text = weather}\n    end\n    return {segment1, segment2}\nend\n\nwidget = {\n    plugin = 'web',\n    opts = {\n        planner = planner,\n    },\n    cb = function(t)\n        local err_msg = check_error(t)\n        if err_msg then\n            print(string.format('WARNING: luastatus: weather widget: %s'), err_msg)\n            return {full_text = '...', color = '#e03838'}\n        end\n\n        if coordinates == nil then\n            local obj = assert(luastatus.plugin.json_decode(t.body))\n            local result = assert(obj.results[1])\n            local latitude = assert(result.latitude)\n            local longitude = assert(result.longitude)\n            coordinates = {latitude = latitude, longitude = longitude}\n            return {full_text = '...', color = '#709080'}\n        end\n\n        local obj = assert(luastatus.plugin.json_decode(t.body))\n        local cw = assert(obj.current_weather)\n\n        local temp = assert(cw.temperature)\n        local wind_speed = assert(cw.windspeed)\n        -- is_day is an integer, either 0 or 1\n        local is_day = assert(cw.is_day) ~= 0\n        local weather_code = assert(cw.weathercode)\n\n        return fmt_weather(temp, wind_speed, is_day, weather_code)\n    end,\n}\n"
  },
  {
    "path": "examples/i3/wireless.lua",
    "content": "local ORIGIN = '•'  -- '●' '○' '◌' '◍' '⬤'\nlocal RAY = '}'     -- '›' '〉' '❭' '❯' '❱' '⟩' '⟫' '》'\nlocal MIN_DBM, MAX_DBM = -90, -20\nlocal NGAUGE = 5\nlocal COLOR_DIM = '#709080'\n\nlocal function round(x)\n    return math.floor(x + 0.5)\nend\n\nlocal function make_wifi_gauge(dbm)\n    if dbm < MIN_DBM then dbm = MIN_DBM end\n    if dbm > MAX_DBM then dbm = MAX_DBM end\n    local nbright = round(NGAUGE * (1 - 0.7 * (MAX_DBM - dbm) / (MAX_DBM - MIN_DBM)))\n    return {\n        full_text = string.format(\n            '%s%s<span color=\"%s\">%s</span>',\n            ORIGIN, RAY:rep(nbright), COLOR_DIM, RAY:rep(NGAUGE - nbright)\n        ),\n        markup = 'pango',\n    }\nend\n\nlocal function sorted_keys(tbl)\n    local keys = {}\n    for k, _ in pairs(tbl) do\n        keys[#keys + 1] = k\n    end\n    table.sort(keys)\n    return keys\nend\n\nwidget = {\n    plugin = 'network-linux',\n    opts = {\n        wireless = true,\n        timeout = 10,\n    },\n    cb = function(t)\n        if not t then\n            return nil\n        end\n\n        -- Sort for determinism\n        local ifaces = sorted_keys(t)\n\n        local r = {}\n        for _, iface in ipairs(ifaces) do\n            local params = t[iface]\n            if params.wireless then\n                if params.wireless.ssid then\n                    r[#r + 1] = {\n                        full_text = params.wireless.ssid,\n                        color = COLOR_DIM,\n                    }\n                end\n                if params.wireless.signal_dbm then\n                    r[#r + 1] = make_wifi_gauge(params.wireless.signal_dbm)\n                end\n            elseif iface ~= 'lo' and (params.ipv4 or params.ipv6) then\n                r[#r + 1] = {\n                    full_text = string.format('[%s]', iface),\n                    color = COLOR_DIM,\n                }\n            end\n        end\n        return r\n    end,\n}\n"
  },
  {
    "path": "examples/i3/xkb.lua",
    "content": "widget = {\n    plugin = 'xkb',\n    cb = function(t)\n        if t.name then\n            local base_layout = t.name:match('[^(]+')\n            if base_layout == 'gb' or base_layout == 'us' then\n                return {full_text = '[En]', color = '#9c9c9c'}\n            elseif base_layout == 'ru' then\n                return {full_text = '[Ru]', color = '#eab93d'}\n            else\n                return {full_text = '[' .. base_layout:sub(1, 1):upper() .. base_layout:sub(2) .. ']'}\n            end\n        else\n            return {full_text = '[? ID ' .. t.id .. ']'}\n        end\n    end,\n}\n"
  },
  {
    "path": "examples/lemonbar/alsa-gauge.lua",
    "content": "local GAUGE_NCHARS = 10\n\nlocal function mk_gauge(level, full, empty)\n    local nfull = math.floor(level * GAUGE_NCHARS + 0.5)\n    return full:rep(nfull) .. empty:rep(GAUGE_NCHARS - nfull)\nend\n\nwidget = {\n    plugin = 'alsa',\n    cb = function(t)\n        local level = (t.vol.cur - t.vol.min) / (t.vol.max - t.vol.min)\n        if t.mute then\n            return \"%{F#e03838}\" .. mk_gauge(level, '×', '—') .. \"%{F-}\"\n        else\n            return \"%{F#718ba6}\" .. mk_gauge(level, '●', '○') .. \"%{F-}\"\n        end\n    end,\n}\n"
  },
  {
    "path": "examples/lemonbar/alsa.lua",
    "content": "widget = {\n    plugin = 'alsa',\n    cb = function(t)\n        if t.mute then\n            return \"%{F#e03838}\" .. '[mute]' .. \"%{F-}\"\n        else\n            local percent = (t.vol.cur - t.vol.min) / (t.vol.max - t.vol.min) * 100\n            local body = string.format('[%3d%%]', math.floor(0.5 + percent))\n            return \"%{F#718ba6}\" .. luastatus.barlib.escape(body) .. \"%{F-}\"\n        end\n    end,\n}\n"
  },
  {
    "path": "examples/lemonbar/backlight.lua",
    "content": "-- Note that this widget only shows backlight level when it changes.\nwidget = luastatus.require_plugin('backlight-linux').widget{\n    cb = function(level)\n        if level ~= nil then\n            local body = string.format('*%3.0f%%', level * 100)\n            return luastatus.barlib.escape(body)\n        end\n    end,\n}\n"
  },
  {
    "path": "examples/lemonbar/battery.lua",
    "content": "widget = luastatus.require_plugin('battery-linux').widget{\n    period = 2,\n    cb = function(t)\n        local symbol = ({\n            Charging    = '↑',\n            Discharging = '↓',\n        })[t.status] or ' '\n        local rem_seg\n        if t.rem_time then\n            local h = math.floor(t.rem_time)\n            local m = math.floor(60 * (t.rem_time - h))\n            rem_seg = \"%{F#595959}\" .. string.format('%2dh %02dm', h, m) .. \"%{F-}\"\n        end\n        return {\n            luastatus.barlib.escape(string.format('%3d%%%s', t.capacity, symbol)),\n            rem_seg,\n        }\n    end,\n}\n"
  },
  {
    "path": "examples/lemonbar/bluetooth.lua",
    "content": "-- A widget to display currently connected and paired bluetooth devices.\n-- To change output format modify reprint_devices function.\n\nif not luastatus.execute('command -v bluetoothctl >/dev/null') then\n    error('\"bluetoothctl\" command, which is required for this widget to work, was not found')\nend\n\nlocal separator = \" \"\n\n-- Object paths look like /org/bluez/hci0/dev_XX_XX_XX_XX_XX_XX/somethingsomething\nlocal function get_device_mac_address(device_object_path)\n    return device_object_path:gsub(\"/.*/dev_\", \"\"):gsub(\"/.*\", \"\"):gsub(\"_\", \":\")\nend\n\n-- For reference bluetoothctl devices output looks like that:\n-- Device XX:XX:XX:XX:XX:XX JBL T450BT\n-- Device YY:YY:YY:YY:YY:YY Redmi 8\n--\n-- Function returns mac addresses of all devices.\nlocal function get_devices()\n    local devices = {}\n    local handle = io.popen(string.format(\"bluetoothctl devices\"))\n    for line in handle:lines() do\n        local match = string.match(line, \"Device ([%x:]+)\")\n        if match then\n            table.insert(devices, match)\n        end\n    end\n    handle:close()\n    return devices\nend\n\n-- For reference bluetoothctl info output looks like that:\n-- Device XX:XX:XX:XX:XX:XX (public)\n--         Name: JBL T450BT\n--         Alias: JBL T450BT\n--         Class: 0xFFFFFFFF\n--         Icon: audio-card\n--         Paired: yes\n--         Trusted: yes\n--         Blocked: no\n--         Connected: yes\n--         LegacyPairing: no\n--         UUID: Headset                   (xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)\n--         ...\n--         UUID: Handsfree                 (xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)\n--\n-- Given this input function returns a following table:\n-- [alias]         string  JBL T450BT\n-- [blocked]       boolean false\n-- [class]         string  0x00240404\n-- [connected]     boolean true\n-- [icon]          string  audio\n-- [legacypairing] boolean false\n-- [name]          string  JBL T450BT\n-- [paired]        boolean true\n-- [trusted]       boolean true\nlocal function get_device_info(mac_address)\n    if mac_address == nil then\n        mac_address = \"\"\n    end\n\n    assert(string.match(mac_address, '^[%x:]*$') ~= nil)\n\n    local device_info = {}\n    local handle = io.popen(string.format(\"bluetoothctl info %s\", mac_address))\n    for line in handle:lines() do\n        local key, value = string.match(line, \"(%w+): (.*)\")\n        -- Filter junk\n        if key ~= \"UUID\" and key ~= nil and value ~= nil then\n            key = string.lower(key)\n            if key ~= \"name\" and key ~= \"alias\" and key ~= \"icon\" then\n                if value == \"yes\" then\n                    value = true\n                end\n                if value == \"no\" then\n                    value = false\n                end\n            end\n            device_info[key] = value\n        end\n    end\n    handle:close()\n    return device_info\nend\n\nlocal devices = {}\n\nlocal function reprint_devices()\n    local t = {}\n    for mac_address, device in pairs(devices) do\n        table.insert(t, string.format(\"%s(%s)\", device[\"name\"], mac_address))\n    end\n    return table.concat(t, separator)\nend\n\nwidget = {\n    plugin = \"dbus\",\n    opts = {\n        greet = true,\n        -- https://git.kernel.org/pub/scm/bluetooth/bluez.git/tree/doc/device-api.txt\n        signals = {\n            {\n                sender = \"org.bluez\",\n                interface = \"org.freedesktop.DBus.Properties\",\n                signal = \"PropertiesChanged\",\n                arg0 = \"org.bluez.Device1\",\n                bus = \"system\"\n            }\n        }\n    },\n    cb = function(t)\n        if t.what == \"hello\" then\n            local mac_addresses = get_devices()\n            for _, mac_address in ipairs(mac_addresses) do\n                local device = get_device_info(mac_address)\n                if device[\"connected\"] and device[\"paired\"] then\n                    devices[mac_address] = device\n                end\n            end\n        elseif t.what == \"signal\" then\n            -- For reference message from dbus looks like that:\n            -- table\n            -- [1]     string  org.bluez.Device1\n            -- [2]     table\n            -- [2]     [1]     table\n            -- [2]     [1]     [1]     string  SomethingSomething\n            -- [2]     [1]     [2]     boolean false\n            -- [2]     [2]     table\n            -- [2]     [2]     [1]     string  Connected\n            -- [2]     [2]     [2]     boolean true\n            -- [3]     table\n            if t.signal == \"PropertiesChanged\" then\n                for _, message in pairs(t.parameters[2]) do\n                    if message[1] == \"Connected\" or message[1] == \"Paired\" then\n                        local mac_address = get_device_mac_address(t.object_path)\n                        if message[2] then\n                            local device = get_device_info(mac_address)\n                            if device[\"paired\"] then\n                                devices[mac_address] = device\n                            end\n                        else\n                            devices[mac_address] = nil\n                        end\n                    end\n                end\n            end\n        end\n        return luastatus.barlib.escape(reprint_devices())\n    end\n}\n"
  },
  {
    "path": "examples/lemonbar/btc-price.lua",
    "content": "-- Bitcoin price widget.\n-- Updates on click and every 5 minutes.\n\nlocal custom_sleep_amt = nil\n\nlocal function check_error(t)\n    if t.error then\n        -- Low-level libcurl error\n        return t.error\n    end\n    if t.status < 200 or t.status > 299 then\n        -- Bad HTTP status\n        return string.format('HTTP status %d', t.status)\n    end\n    -- Everything's OK\n    return nil\nend\n\nlocal function stylize(styles, fmt, ...)\n    local pos_args = {...}\n    local res, _ = fmt:gsub('([@%%])([0-9])', function(sigil, digit)\n        local i = tonumber(digit)\n        if sigil == '@' then\n            if i == 0 then\n                return '%{F-}'\n            end\n            return styles[i]\n        else\n            assert(i ~= 0)\n            return pos_args[i]\n        end\n    end)\n    return res\nend\n\nwidget = {\n    plugin = 'web',\n    opts = {\n        planner = function()\n            while true do\n                coroutine.yield({action = 'request', params = {\n                    url = 'https://api.binance.com/api/v3/ticker/price?symbol=BTCUSDT',\n                    timeout = 5,\n                }})\n                local period = custom_sleep_amt or (5 * 60)\n                custom_sleep_amt = nil\n                coroutine.yield({action = 'sleep', period = period})\n            end\n        end,\n        make_self_pipe = true,\n    },\n    cb = function(t)\n        local text\n        local err_msg = check_error(t)\n        if err_msg then\n            print(string.format('WARNING: luastatus: btc-price widget: %s'), err_msg)\n            text = '......'\n            custom_sleep_amt = 5\n        else\n            local obj = assert(luastatus.plugin.json_decode(t.body))\n            text = obj.price:match('[^.]+')\n        end\n        return stylize(\n            {'%{F#586A4B}', '%{F#C0863F}'},\n            '@1[@2$@1%1]@0',\n            luastatus.barlib.escape(text)\n        )\n    end,\n    event = function(t)\n        if t.button == 1 then\n            luastatus.plugin.wake_up()\n        end\n    end,\n}\n"
  },
  {
    "path": "examples/lemonbar/cpu-freq.lua",
    "content": "local VBLOCKS = {'▂', '▃', '▄', '▅', '▆', '▇', '█'}\n\nlocal function make_color(ratio)\n    local red = math.floor(0.5 + 255 * ratio)\n    return string.format('#%02x%02x00', red, 255 - red)\nend\n\nlocal function make_chunk(entry)\n    local num = entry.cur - entry.min\n    local denom = entry.max - entry.min\n\n    local ratio\n    if denom ~= 0 then\n        ratio = num / denom\n    else\n        -- If max_freq == min_freq, set ratio to zero.\n        ratio = 0\n    end\n\n    local vblock_idx = math.min(\n        1 + math.floor(0.5 + ratio * #VBLOCKS),\n        #VBLOCKS)\n\n    return '%{F' .. make_color(ratio) .. '}' .. VBLOCKS[vblock_idx]\nend\n\nlocal plugin_data = {}\n\nlocal plugin_params = {\n    timer_opts = {\n        period = 2,\n    },\n    cb = function(t)\n        if t == nil then\n            return nil\n        end\n        local r = {}\n        for _, entry in ipairs(t) do\n            r[#r + 1] = make_chunk(entry)\n        end\n        return table.concat(r) .. '%{F-}'\n    end,\n}\n\nwidget = luastatus.require_plugin('cpu-freq-linux').widget(plugin_params, plugin_data)\n"
  },
  {
    "path": "examples/lemonbar/cpu-temperature.lua",
    "content": "local COOL_TEMP = 50\nlocal HEAT_TEMP = 75\nlocal function getcolor(temp)\n    local t = (temp - COOL_TEMP) / (HEAT_TEMP - COOL_TEMP)\n    if t < 0 then t = 0 end\n    if t > 1 then t = 1 end\n    local red = math.floor(t * 255 + 0.5)\n    return string.format('#%02x%02x00', red, 255 - red)\nend\n\nlocal plugin_data = {}\n\nlocal plugin_params = {\n    timer_opts = {\n        period = 2,\n    },\n    cb = function(t)\n        if not t then\n            return nil\n        end\n        local r = {}\n        for _, entry in ipairs(t) do\n            local temp = entry.value\n            local body = string.format('%.0f°', temp)\n            r[#r + 1] = '%{F' .. getcolor(temp) .. '}' .. body .. '%{F-}'\n        end\n        return r\n    end,\n}\n\nwidget = luastatus.require_plugin('cpu-temp-linux').widget(plugin_params, plugin_data)\n"
  },
  {
    "path": "examples/lemonbar/cpu-usage.lua",
    "content": "widget = luastatus.require_plugin('cpu-usage-linux').widget{\n    cb = function(usage)\n        if usage ~= nil then\n            local body = string.format('[%5.1f%%]', usage * 100)\n            return luastatus.barlib.escape(body)\n        end\n    end,\n}\n"
  },
  {
    "path": "examples/lemonbar/disk-io.lua",
    "content": "local function stylize(styles, fmt, ...)\n    local pos_args = {...}\n    local res, _ = fmt:gsub('([@%%])([0-9])', function(sigil, digit)\n        local i = tonumber(digit)\n        if sigil == '@' then\n            if i == 0 then\n                return '%{F-}'\n            end\n            return styles[i]\n        else\n            assert(i ~= 0)\n            return pos_args[i]\n        end\n    end)\n    return res\nend\n\nwidget = luastatus.require_plugin('disk-io-linux').widget{\n    period = 2,\n    cb = function(t)\n        -- Sort by name for determinism\n        table.sort(t, function(a, b) return a.name < b.name end)\n\n        local segments = {}\n        for _, entry in ipairs(t) do\n            local R = entry.read_bytes\n            local W = entry.written_bytes\n            if (R >= 0) and (W >= 0) then\n                segments[#segments + 1] = stylize(\n                    {'%{F#DCA3A3}', '%{F#72D5A3}'},\n                    '%1: @1%2k↓ @2%3k↑@0',\n                    luastatus.barlib.escape(entry.name),\n                    string.format('%.0f', R / 1024),\n                    string.format('%.0f', W / 1024)\n                )\n            end\n        end\n        return segments\n    end,\n}\n"
  },
  {
    "path": "examples/lemonbar/file-contents.lua",
    "content": "widget = luastatus.require_plugin('file-contents-linux').widget{\n    filename = os.getenv('HOME') .. '/status',\n    cb = function(f)\n        -- show the first line of the file\n        return luastatus.barlib.escape(f:read('*line'))\n    end,\n}\n"
  },
  {
    "path": "examples/lemonbar/fs.lua",
    "content": "local function sorted_keys(tbl)\n    local keys = {}\n    for k, _ in pairs(tbl) do\n        keys[#keys + 1] = k\n    end\n    table.sort(keys)\n    return keys\nend\n\nwidget = {\n    plugin = 'fs',\n    opts = {\n        paths = {'/', '/home'},\n    },\n    cb = function(t)\n        -- Sort for determinism\n        local keys = sorted_keys(t)\n\n        local res = {}\n        for _, k in ipairs(keys) do\n            local v = t[k]\n            local body = string.format('%s %.0f%%', k, (1 - v.avail / v.total) * 100)\n            table.insert(res, luastatus.barlib.escape(body))\n        end\n        return res\n    end,\n}\n"
  },
  {
    "path": "examples/lemonbar/gmail.lua",
    "content": "--[[\n-- Expects 'credentials.lua' to be present in the current directory; it may contain, e.g.,\n--     return {\n--         gmail = {\n--             login = 'john.smith',\n--             password = 'qwerty'\n--         }\n--     }\n--]]\nlocal credentials = require 'credentials'\n\nwidget = luastatus.require_plugin('imap').widget{\n    verbose = false,\n    host = 'imap.gmail.com',\n    port = 993,\n    mailbox = 'Inbox',\n    use_ssl = true,\n    timeout = 2 * 60,\n    handshake_timeout = 10,\n    login = credentials.gmail.login,\n    password = credentials.gmail.password,\n    error_sleep_period = 60,\n    cb = function(unseen)\n        if unseen == nil then\n            return nil\n        elseif unseen == 0 then\n            return \"%{F#595959}\" .. '[-]' .. \"%{F-}\"\n        else\n            return string.format('[%d unseen]', unseen)\n        end\n    end,\n}\n"
  },
  {
    "path": "examples/lemonbar/ip.lua",
    "content": "widget = {\n    plugin = 'network-linux',\n    cb = function(t)\n        local r = {}\n        for iface, params in pairs(t) do\n            local iface_clean = iface\n            local addr = params.ipv6 or params.ipv4\n            if addr then\n                -- strip out \"label\" from the interface name\n                iface_clean = iface_clean:gsub(':.*', '')\n                -- strip out \"zone index\" from the address\n                addr = addr:gsub('%%.*', '')\n\n                if iface_clean ~= 'lo' then\n                    local body = string.format('[%s: %s]', iface_clean, addr)\n                    r[#r + 1] = \"%{F#709080}\" .. body .. \"%{F-}\"\n                end\n            end\n        end\n        return r\n    end,\n}\n"
  },
  {
    "path": "examples/lemonbar/loadavg-linux.lua",
    "content": "local function get_ncpus()\n    local f = assert(io.open('/proc/cpuinfo', 'r'))\n    local n = 0\n    for line in f:lines() do\n        if line:match('^processor\\t') then\n            n = n + 1\n        end\n    end\n    f:close()\n    return n\nend\n\nlocal function avg2str(x)\n    assert(x >= 0)\n    if x >= 1000 then\n        return '↑↑↑'\n    end\n    return string.format('%3.0f', x)\nend\n\nwidget = {\n    plugin = 'timer',\n    opts = {\n        period = 2,\n    },\n    cb = function(_)\n        local f = io.open('/proc/loadavg', 'r')\n        local avg1, avg5, avg15 = f:read('*number', '*number', '*number')\n        f:close()\n\n        assert(avg1 and avg5 and avg15)\n\n        local ncpus = get_ncpus()\n\n        return luastatus.barlib.escape(string.format(\n            '[%s%% %s%% %s%%]',\n            avg2str(avg1  / ncpus * 100),\n            avg2str(avg5  / ncpus * 100),\n            avg2str(avg15 / ncpus * 100)\n        ))\n    end,\n}\n"
  },
  {
    "path": "examples/lemonbar/media-player-mpris.lua",
    "content": "local PLAYER = 'clementine'\n\nlocal PLAYBACK_STATUS_ICONS = {\n    Playing = '▶',\n    Paused  = '◆',\n    Stopped = '—',\n}\n\nlocal COLOR_BRIGHT = '#60b177'\nlocal COLOR_DIM = '#9fb15d'\n\nlocal function fetch_metadata_field(t, key)\n    if t.Metadata then\n        return t.Metadata[key]\n    else\n        return nil\n    end\nend\n\nlocal function do_call_method(method_name)\n    local is_ok, err_msg = luastatus.plugin.call_method_str({\n        bus = 'session',\n        dest = 'org.mpris.MediaPlayer2.' .. PLAYER,\n        object_path = '/org/mpris/MediaPlayer2',\n        interface = 'org.mpris.MediaPlayer2.Player',\n        method = method_name,\n        -- no arg_str\n    })\n    if not is_ok then\n        print(string.format(\n            'WARNING: luastatus: mpris widget: cannot call method \"%s\": %s',\n            method_name, err_msg\n        ))\n    end\nend\n\nlocal function make_segment(text, color, event_text)\n    local color_begin = '%{F' .. color .. '}'\n    local color_end = '%{F-}'\n    local res = color_begin .. luastatus.barlib.escape(text) .. color_end\n    if event_text then\n        local a_begin = '%{A:' .. event_text .. ':}'\n        local a_end = '%{A}'\n        res = a_begin .. res .. a_end\n    end\n    return res\nend\n\nwidget = luastatus.require_plugin('mpris').widget{\n    player = PLAYER,\n    cb = function(t)\n        if not t.PlaybackStatus then\n            return nil\n        end\n\n        local title = fetch_metadata_field(t, 'xesam:title')\n        title = title or ''\n\n        title = luastatus.libwidechar.make_valid_and_printable(title, '?')\n        title = luastatus.libwidechar.truncate_to_width(title, 40)\n        title = title or ''\n\n        local icon = PLAYBACK_STATUS_ICONS[t.PlaybackStatus] or '?'\n\n        local chunks = {\n            make_segment('←', COLOR_DIM, 'prev'),\n            make_segment(icon, COLOR_BRIGHT, 'status'),\n            make_segment('→', COLOR_DIM, 'next'),\n        }\n\n        if title ~= '' then\n            table.insert(chunks, make_segment(title, COLOR_BRIGHT, nil))\n        end\n\n        return table.concat(chunks, ' ')\n    end,\n    event = function(t)\n        if t == 'status' then\n            do_call_method('PlayPause')\n        elseif t == 'prev' then\n            do_call_method('Previous')\n        elseif t == 'next' then\n            do_call_method('Next')\n        end\n    end,\n}\n"
  },
  {
    "path": "examples/lemonbar/mem-usage.lua",
    "content": "widget = luastatus.require_plugin('mem-usage-linux').widget{\n    timer_opts = {period = 2},\n    cb = function(t)\n        local used_kb = t.total.value - t.avail.value\n        local body = string.format('[%3.2f GiB]', used_kb / 1024 / 1024)\n        return \"%{F#af8ec3}\" .. body .. \"%{F-}\"\n    end,\n}\n"
  },
  {
    "path": "examples/lemonbar/mpd-progressbar.lua",
    "content": "local text, time, total, is_playing = nil, nil, nil, false\nlocal timeout = 2\nlocal titlewidth = 40\n\nlocal function W_split(str, boundary)\n    local head, _ = luastatus.libwidechar.truncate_to_width(str, boundary)\n    assert(head)\n    local tail = str:sub(1 + #head)\n    return head, tail\nend\n\nwidget = {\n    plugin = 'mpd',\n    opts = {\n        timeout = timeout,\n    },\n    cb = function(t)\n        if t.what == 'update' then\n            -- build title\n            local title\n            if t.song.Title then\n                title = t.song.Title\n                if t.song.Artist then\n                    title = t.song.Artist .. ': ' .. title\n                end\n            else\n                title = t.song.file or ''\n            end\n\n            title = luastatus.libwidechar.make_valid(title, '?')\n\n            if assert(luastatus.libwidechar.width(title)) > titlewidth then\n                title = luastatus.libwidechar.truncate_to_width(title, titlewidth - 1) .. '…'\n            end\n\n            -- build text\n            text = string.format('%s %s',\n                ({play = '▶', pause = '‖', stop = '■'})[t.status.state],\n                title\n            )\n\n            -- update other globals\n            if t.status.time then\n                time, total = t.status.time:match('(%d+):(%d+)')\n                time, total = tonumber(time), tonumber(total)\n            else\n                time, total = nil, nil\n            end\n            is_playing = t.status.state == 'play'\n\n        elseif t.what == 'timeout' then\n            if is_playing then\n                time = math.min(time + timeout, total)\n            end\n\n        else\n            -- 'connecting' or 'error'\n            return {full_text = t.what}\n        end\n\n        -- calc progress\n        local width = assert(luastatus.libwidechar.width(text))\n        -- 'time' and 'total' can be nil here, we check for 'total' only\n        local ulpos = (total and total ~= 0)\n                        and math.floor(width / total * time + 0.5)\n                        or 0\n\n        local head, tail = W_split(text, ulpos)\n\n        return '%{u+}' .. luastatus.barlib.escape(head) ..\n               '%{u-}' .. luastatus.barlib.escape(tail)\n    end\n}\n"
  },
  {
    "path": "examples/lemonbar/network-rate.lua",
    "content": "local function stylize(styles, fmt, ...)\n    local pos_args = {...}\n    local res, _ = fmt:gsub('([@%%])([0-9])', function(sigil, digit)\n        local i = tonumber(digit)\n        if sigil == '@' then\n            if i == 0 then\n                return '%{F-}'\n            end\n            return styles[i]\n        else\n            assert(i ~= 0)\n            return pos_args[i]\n        end\n    end)\n    return res\nend\n\nlocal function make_segment(iface, R, S)\n    return stylize(\n        {'%{F#DCA3A3}', '%{F#72D5A3}'},\n        '[%1 @1%2k @2%3k@0]',\n        luastatus.barlib.escape(iface),\n        string.format('%.0f↓', R / 1000),\n        string.format('%.0f↑', S / 1000)\n    )\nend\n\nwidget = luastatus.require_plugin('network-rate-linux').widget{\n    iface_except = 'lo',\n    period = 3,\n    in_array_form = true,\n    cb = function(t)\n        local r = {}\n        for _, PQ in ipairs(t) do\n            local iface = PQ[1]\n            local R, S = PQ[2].R, PQ[2].S\n            r[#r + 1] = make_segment(iface, R, S)\n        end\n        return r\n    end,\n}\n"
  },
  {
    "path": "examples/lemonbar/pulse-gauge.lua",
    "content": "local GAUGE_NCHARS = 10\n\nlocal function mk_gauge(level, full, empty)\n    local nfull = math.floor(level * GAUGE_NCHARS + 0.5)\n    return full:rep(nfull) .. empty:rep(GAUGE_NCHARS - nfull)\nend\n\nwidget = {\n    plugin = 'pulse',\n    cb = function(t)\n        local level = t.cur / t.norm\n        if t.mute then\n            return \"%{F#e03838}\" .. mk_gauge(level, '×', '—') .. \"%{F-}\"\n        else\n            return \"%{F#718ba6}\" .. mk_gauge(level, '●', '○') .. \"%{F-}\"\n        end\n    end,\n}\n"
  },
  {
    "path": "examples/lemonbar/pulse.lua",
    "content": "widget = {\n    plugin = 'pulse',\n    cb = function(t)\n        if t.mute then\n            return \"%{F#e03838}\" .. '[mute]' .. \"%{F-}\"\n        end\n        local percent = (t.cur / t.norm) * 100\n        local body = string.format('[%3d%%]', math.floor(0.5 + percent))\n        return \"%{F#718ba6}\" .. luastatus.barlib.escape(body) .. \"%{F-}\"\n    end,\n}\n"
  },
  {
    "path": "examples/lemonbar/systemd-unit.lua",
    "content": "local COLORS = {\n    pink   = '#ec93d3',\n    green  = '#60b48a',\n    yellow = '#dfaf8f',\n    red    = '#dca3a3',\n    dim    = '#909090',\n}\n\nlocal function make_output(text, color_name)\n    local color_begin = '%{F' .. assert(COLORS[color_name]) .. '}'\n    local color_end = '%{F-}'\n    return string.format('[Tor: %s%s%s]', color_begin, text, color_end)\nend\n\nwidget = luastatus.require_plugin('systemd-unit').widget{\n    unit_name = 'tor.service',\n    cb = function(state)\n        if state == 'active' then\n            return make_output('✓', 'green')\n        elseif state == 'reloading' or state == 'activating' then\n            return make_output('•', 'yellow')\n        elseif state == 'inactive' or state == 'deactivating' then\n            return make_output('-', 'dim')\n        elseif state == 'failed' then\n            return make_output('x', 'red')\n        else\n            return make_output('?', 'pink')\n        end\n    end,\n}\n"
  },
  {
    "path": "examples/lemonbar/time-battery-combined.lua",
    "content": "local function get_bat_seg(t)\n    if not t then\n        return '[--×--]'\n    end\n    if t.status == 'Unknown' or t.status == 'Full' or t.status == 'Not charging' then\n        return nil\n    end\n    local sym, color = '?', '#dcdcdc'\n    if t.status == 'Discharging' then\n        sym = '↓'\n        color = '#dca3a3'\n    elseif t.status == 'Charging' then\n        sym = '↑'\n        color = '#60b48a'\n    end\n    local body = string.format('[%3d%%%s]', t.capacity, sym)\n    return '%{F' .. color .. '}' .. luastatus.barlib.escape(body) .. '%{F-}'\nend\n\nwidget = luastatus.require_plugin('battery-linux').widget{\n    period = 2,\n    cb = function(t)\n        return {\n            \"%{F#dc8cc3}\" .. os.date('[%H:%M]') .. \"%{F-}\",\n            get_bat_seg(t),\n        }\n    end,\n}\n"
  },
  {
    "path": "examples/lemonbar/time-date.lua",
    "content": "local months = {\n    'января',\n    'февраля',\n    'марта',\n    'апреля',\n    'мая',\n    'июня',\n    'июля',\n    'августа',\n    'сентября',\n    'октября',\n    'ноября',\n    'декабря',\n}\nwidget = {\n    plugin = 'timer',\n    cb = function()\n        local d = os.date('*t')\n        return {\n            string.format('%d %s', d.day, months[d.month]),\n            string.format('%02d:%02d', d.hour, d.min),\n        }\n    end,\n}\n"
  },
  {
    "path": "examples/lemonbar/tor.lua",
    "content": "-- Trivial but somewhat useful widget showing if the Tor daemon is running.\n\nwidget = {\n    plugin = 'timer',\n    opts = {period = 5},\n    cb = function()\n        local f = io.open('/var/run/tor/tor.pid', 'r')\n        if f then\n            f:close()\n            return '[TOR]'\n        else\n            return nil\n        end\n    end,\n}\n"
  },
  {
    "path": "examples/lemonbar/uptime-linux.lua",
    "content": "local SUFFIXES_AND_DIVISORS = {\n    {'m', 60},\n    {'h', 60},\n    {'d', 24},\n    {'W', 7},\n    {'M', 30},\n    {'Y', 365 / 30},\n}\n\nlocal function seconds_to_human_readable_time(sec)\n    local prev_suffix = 's'\n    local found_suffix\n    for _, PQ in ipairs(SUFFIXES_AND_DIVISORS) do\n        local suffix = PQ[1]\n        local divisor = PQ[2]\n        if sec < divisor then\n            found_suffix = prev_suffix\n            break\n        end\n        sec = sec / divisor\n        prev_suffix = suffix\n    end\n    if not found_suffix then\n        found_suffix = prev_suffix\n    end\n    return string.format('%.0f%s', sec, found_suffix)\nend\n\nwidget = {\n    plugin = 'timer',\n    opts = {\n        period = 2,\n    },\n    cb = function(_)\n        local f = io.open('/proc/uptime', 'r')\n        local sec, _ = f:read('*number', '*number')\n        f:close()\n\n        assert(sec)\n\n        return string.format(\n            '[uptime: %s]',\n            seconds_to_human_readable_time(sec)\n        )\n    end,\n}\n"
  },
  {
    "path": "examples/lemonbar/weather-rus.lua",
    "content": "local LOCATION_NAME = 'Moscow'\n\nlocal TIMEOUT = 10\n\nlocal INTERVAL = 9\nlocal SLEEP_AFTER_INITIAL = 5\n\nlocal coordinates = nil\n\nlocal function planner()\n    -- Get coordinates of LOCATION_NAME.\n    while true do\n        -- Form the URL\n        local url = string.format(\n            'https://geocoding-api.open-meteo.com/v1/search?name=%s&count=1',\n            luastatus.plugin.urlencode(LOCATION_NAME)\n        )\n        -- Make request\n        coroutine.yield({action = 'request', params = {\n            url = url,\n            timeout = TIMEOUT,\n        }})\n        -- If successful, break\n        if coordinates ~= nil then\n            break\n        end\n        -- Failure; sleep and retry\n        coroutine.yield({action = 'sleep', period = INTERVAL})\n    end\n\n    -- Coordinates fetched, sleep for SLEEP_AFTER_INITIAL.\n    coroutine.yield({action = 'sleep', period = SLEEP_AFTER_INITIAL})\n\n    -- Form the weather URL\n    local weather_url = string.format(\n        'https://api.open-meteo.com/v1/forecast' ..\n        '?latitude=%f' ..\n        '&longitude=%f' ..\n        '&current_weather=true',\n        coordinates.latitude,\n        coordinates.longitude\n    )\n\n    -- Fetch weather, periodically\n    while true do\n        -- Make request\n        coroutine.yield({action = 'request', params = {\n            url = weather_url,\n            timeout = TIMEOUT,\n        }})\n        -- Sleep and repeat\n        coroutine.yield({action = 'sleep', period = INTERVAL})\n    end\nend\n\nlocal function check_error(t)\n    if t.error then\n        -- Low-level libcurl error\n        return t.error\n    end\n    if t.status < 200 or t.status > 299 then\n        -- Bad HTTP status\n        return string.format('HTTP status %d', t.status)\n    end\n    -- Everything's OK\n    return nil\nend\n\nlocal WEATHER_CODES = {\n    [0] = 'Ясно',\n\n    [1] = 'Преимущественно ясно',\n    [2] = 'Переменная облачность',\n    [3] = 'Пасмурно',\n\n    [45] = 'Туман',\n    [48] = 'Осаждение инея и тумана',\n\n    [51] = 'Изморось, лёгкая',\n    [53] = 'Изморось, умеренная',\n    [55] = 'Изморось, сильная',\n\n    [56] = 'Ледяная изморось, лёгкая',\n    [57] = 'Ледяная изморось, сильная',\n\n    [61] = 'Дождь, лёгкий',\n    [63] = 'Дождь, умеренный',\n    [65] = 'Дождь, сильный',\n\n    [66] = 'Град, лёгкий',\n    [67] = 'Град, сильный',\n\n    [71] = 'Снегопад, лёгкий',\n    [73] = 'Снегопад, умеренный',\n    [75] = 'Снегопад, сильный',\n\n    [77] = 'Крупинки снега',\n\n    [80] = 'Ливень, лёгкий',\n    [81] = 'Ливень, умеренный',\n    [82] = 'Ливень, сильный',\n\n    [85] = 'Снеговые осадки, лёгкие',\n    [87] = 'Снеговые осадки, сильные',\n\n    [95] = 'Гроза',\n    [96] = 'Гроза, небольшой град',\n    [99] = 'Гроза, сильный град',\n}\n\nlocal function fmt_weather(temp, _, _, weather_code)\n    local segment1 = string.format('%.0f°', temp)\n    -- segment2 is either string or nil\n    local segment2 = WEATHER_CODES[weather_code]\n    return {segment1, segment2}\nend\n\nwidget = {\n    plugin = 'web',\n    opts = {\n        planner = planner,\n    },\n    cb = function(t)\n        local err_msg = check_error(t)\n        if err_msg then\n            print(string.format('WARNING: luastatus: weather widget: %s'), err_msg)\n            return '%{F#e03838}...%{F-}'\n        end\n\n        if coordinates == nil then\n            local obj = assert(luastatus.plugin.json_decode(t.body))\n            local result = assert(obj.results[1])\n            local latitude = assert(result.latitude)\n            local longitude = assert(result.longitude)\n            coordinates = {latitude = latitude, longitude = longitude}\n            return '%{F#709080}...%{F-}'\n        end\n\n        local obj = assert(luastatus.plugin.json_decode(t.body))\n        local cw = assert(obj.current_weather)\n\n        local temp = assert(cw.temperature)\n        local wind_speed = assert(cw.windspeed)\n        -- is_day is an integer, either 0 or 1\n        local is_day = assert(cw.is_day) ~= 0\n        local weather_code = assert(cw.weathercode)\n\n        return fmt_weather(temp, wind_speed, is_day, weather_code)\n    end,\n}\n"
  },
  {
    "path": "examples/lemonbar/weather.lua",
    "content": "local LOCATION_NAME = 'Moscow'\n\nlocal TIMEOUT = 10\n\nlocal INTERVAL = 9\nlocal SLEEP_AFTER_INITIAL = 5\n\nlocal coordinates = nil\n\nlocal function planner()\n    -- Get coordinates of LOCATION_NAME.\n    while true do\n        -- Form the URL\n        local url = string.format(\n            'https://geocoding-api.open-meteo.com/v1/search?name=%s&count=1',\n            luastatus.plugin.urlencode(LOCATION_NAME)\n        )\n        -- Make request\n        coroutine.yield({action = 'request', params = {\n            url = url,\n            timeout = TIMEOUT,\n        }})\n        -- If successful, break\n        if coordinates ~= nil then\n            break\n        end\n        -- Failure; sleep and retry\n        coroutine.yield({action = 'sleep', period = INTERVAL})\n    end\n\n    -- Coordinates fetched, sleep for SLEEP_AFTER_INITIAL.\n    coroutine.yield({action = 'sleep', period = SLEEP_AFTER_INITIAL})\n\n    -- Form the weather URL\n    local weather_url = string.format(\n        'https://api.open-meteo.com/v1/forecast' ..\n        '?latitude=%f' ..\n        '&longitude=%f' ..\n        '&current_weather=true',\n        coordinates.latitude,\n        coordinates.longitude\n    )\n\n    -- Fetch weather, periodically\n    while true do\n        -- Make request\n        coroutine.yield({action = 'request', params = {\n            url = weather_url,\n            timeout = TIMEOUT,\n        }})\n        -- Sleep and repeat\n        coroutine.yield({action = 'sleep', period = INTERVAL})\n    end\nend\n\nlocal function check_error(t)\n    if t.error then\n        -- Low-level libcurl error\n        return t.error\n    end\n    if t.status < 200 or t.status > 299 then\n        -- Bad HTTP status\n        return string.format('HTTP status %d', t.status)\n    end\n    -- Everything's OK\n    return nil\nend\n\nlocal WEATHER_CODES = {\n    [0] = 'Clear sky',\n\n    [1] = 'Mainly clear',\n    [2] = 'Partly cloudy',\n    [3] = 'Overcast',\n\n    [45] = 'Fog',\n    [48] = 'Depositing rime fog',\n\n    [51] = 'Drizzle, light',\n    [53] = 'Drizzle, moderate',\n    [55] = 'Drizzle, dense',\n\n    [56] = 'Freezing drizzle, light',\n    [57] = 'Freezing drizzle, dense',\n\n    [61] = 'Rain, slight',\n    [63] = 'Rain, moderate',\n    [65] = 'Rain, heavy',\n\n    [66] = 'Freezing rain, light',\n    [67] = 'Freezing rain, heavy',\n\n    [71] = 'Snow fall, slight',\n    [73] = 'Snow fall, moderate',\n    [75] = 'Snow fall, heavy',\n\n    [77] = 'Snow grains',\n\n    [80] = 'Rain shower, slight',\n    [81] = 'Rain shower, moderate',\n    [82] = 'Rain shower, violent',\n\n    [85] = 'Snow showers, slight',\n    [87] = 'Snow showers, heavy',\n\n    [95] = 'Thunderstorm',\n    [96] = 'Thunderstorm, slight hail',\n    [99] = 'Thunderstorm, heavy hail',\n}\n\nlocal function fmt_weather(temp, _, _, weather_code)\n    local segment1 = string.format('%.0f°', temp)\n    -- segment2 is either string or nil\n    local segment2 = WEATHER_CODES[weather_code]\n    return {segment1, segment2}\nend\n\nwidget = {\n    plugin = 'web',\n    opts = {\n        planner = planner,\n    },\n    cb = function(t)\n        local err_msg = check_error(t)\n        if err_msg then\n            print(string.format('WARNING: luastatus: weather widget: %s'), err_msg)\n            return '%{F#e03838}...%{F-}'\n        end\n\n        if coordinates == nil then\n            local obj = assert(luastatus.plugin.json_decode(t.body))\n            local result = assert(obj.results[1])\n            local latitude = assert(result.latitude)\n            local longitude = assert(result.longitude)\n            coordinates = {latitude = latitude, longitude = longitude}\n            return '%{F#709080}...%{F-}'\n        end\n\n        local obj = assert(luastatus.plugin.json_decode(t.body))\n        local cw = assert(obj.current_weather)\n\n        local temp = assert(cw.temperature)\n        local wind_speed = assert(cw.windspeed)\n        -- is_day is an integer, either 0 or 1\n        local is_day = assert(cw.is_day) ~= 0\n        local weather_code = assert(cw.weathercode)\n\n        return fmt_weather(temp, wind_speed, is_day, weather_code)\n    end,\n}\n"
  },
  {
    "path": "examples/lemonbar/wireless.lua",
    "content": "local ORIGIN = '•'  -- '●' '○' '◌' '◍' '⬤'\nlocal RAY = '}'     -- '›' '〉' '❭' '❯' '❱' '⟩' '⟫' '》'\nlocal MIN_DBM, MAX_DBM = -90, -20\nlocal NGAUGE = 5\nlocal COLOR_DIM = '#709080'\n\nlocal function round(x)\n    return math.floor(x + 0.5)\nend\n\nlocal function colorize_as_dim(s)\n    s = luastatus.barlib.escape(s)\n    return '%{F' .. COLOR_DIM .. '}' .. s .. '%{F-}'\nend\n\nlocal function make_wifi_gauge(dbm)\n    if dbm < MIN_DBM then dbm = MIN_DBM end\n    if dbm > MAX_DBM then dbm = MAX_DBM end\n    local nbright = round(NGAUGE * (1 - 0.7 * (MAX_DBM - dbm) / (MAX_DBM - MIN_DBM)))\n    return ORIGIN .. RAY:rep(nbright) .. colorize_as_dim(RAY:rep(NGAUGE - nbright))\nend\n\nlocal function sorted_keys(tbl)\n    local keys = {}\n    for k, _ in pairs(tbl) do\n        keys[#keys + 1] = k\n    end\n    table.sort(keys)\n    return keys\nend\n\nwidget = {\n    plugin = 'network-linux',\n    opts = {\n        wireless = true,\n        timeout = 10,\n    },\n    cb = function(t)\n        if not t then\n            return nil\n        end\n\n        -- Sort for determinism\n        local ifaces = sorted_keys(t)\n\n        local r = {}\n        for _, iface in ipairs(ifaces) do\n            local params = t[iface]\n            if params.wireless then\n                if params.wireless.ssid then\n                    r[#r + 1] = colorize_as_dim(params.wireless.ssid)\n                end\n                if params.wireless.signal_dbm then\n                    r[#r + 1] = make_wifi_gauge(params.wireless.signal_dbm)\n                end\n            elseif iface ~= 'lo' and (params.ipv4 or params.ipv6) then\n                r[#r + 1] = colorize_as_dim(string.format('[%s]', iface))\n            end\n        end\n        return r\n    end,\n}\n"
  },
  {
    "path": "examples/lemonbar/xkb.lua",
    "content": "widget = {\n    plugin = 'xkb',\n    cb = function(t)\n        if t.name then\n            local base_layout = t.name:match('[^(]+')\n            if base_layout == 'gb' or base_layout == 'us' then\n                return \"%{F#9c9c9c}\" .. '[En]' .. \"%{F-}\"\n            elseif base_layout == 'ru' then\n                return \"%{F#eab93d}\" .. '[Ru]' .. \"%{F-}\"\n            else\n                local body = string.format('[%s]', base_layout)\n                return luastatus.barlib.escape(body)\n            end\n        else\n            return '[? ID ' .. t.id .. ']'\n        end\n    end,\n}\n"
  },
  {
    "path": "fuzz_utils/do_fuzz_everything.sh",
    "content": "#!/usr/bin/env bash\n\nset -e\n\nshopt -s nullglob\n\nif [[ -z \"$XXX_AFL_DIR\" ]]; then\n    echo >&2 \"You must set the 'XXX_AFL_DIR' environment variable.\"\n    exit 1\nfi\n\ncase \"$XXX_AFL_IS_PP\" in\n0)\n    ;;\n1)\n    ;;\n*)\n    echo >&2 \"You must set 'XXX_AFL_IS_PP' environment variable to either 0 or 1.\"\n    echo >&2 \"Set it to 0 if AFL is the original Google's version (not AFL++).\"\n    echo >&2 \"Set it to 1 if AFL is actually AFL++.\"\n    exit 1\n    ;;\nesac\n\ncd -- \"$(dirname \"$(readlink \"$0\" || printf '%s\\n' \"$0\")\")\"\n\ncd ..\n\nfuzz_dirs=(\n    plugins/*/fuzz*\n    barlibs/*/fuzz*\n)\n\nif (( XXX_AFL_IS_PP )); then\n    afl_cc=\"$XXX_AFL_DIR\"/afl-cc\nelse\n    afl_cc=\"$XXX_AFL_DIR\"/afl-gcc\nfi\n\nfor d in \"${fuzz_dirs[@]}\"; do\n    [[ -d \"$d\" ]] || continue\n\n    \"$d\"/clear.sh\n\n    CC=\"$afl_cc\" \"$d\"/build.sh\n    if [[ -x \"$d\"/fuzz_all.sh ]]; then\n        \"$d\"/fuzz_all.sh\n    else\n        \"$d\"/fuzz.sh\n    fi\ndone\n"
  },
  {
    "path": "fuzz_utils/find_interesting.sh",
    "content": "#!/usr/bin/env bash\n\nset -e\n\nshopt -s nullglob\n\ncd -- \"$(dirname \"$(readlink \"$0\" || printf '%s\\n' \"$0\")\")\"\n\ncd ..\n\nis_dir_empty() {\n    local -a files\n    files=( \"$1\"/* \"$1\"/.* )\n    (( \"${#files[@]}\" == 2 ))\n}\n\nsay() {\n    printf '%s\\n' \"$*\" >&2\n}\n\nfuzz_dirs=(\n    plugins/*/fuzz*\n    barlibs/*/fuzz*\n)\n\nresults=()\n\nfor d1 in \"${fuzz_dirs[@]}\"; do\n    [[ -d \"$d1\" ]] || continue\n\n    for d2 in \"$d1\"/findgins*; do\n        [[ -d \"$d2\" ]] || continue\n\n        for d3 in \"$d2\"/crashes \"$d2\"/hangs; do\n            [[ -d \"$d3\" ]] || continue\n\n            if ! is_dir_empty \"$d3\"; then\n                results+=(\"$d3\")\n            fi\n        done\n    done\ndone\n\nif (( ${#results[@]} != 0 )); then\n    say 'The following \"interesting\" directories are non-empty:'\n    for res in \"${results[@]}\"; do\n        say \" * $res\"\n    done\n    exit 1\nfi\n\nsay 'Everything is OK.'\nexit 0\n"
  },
  {
    "path": "fuzz_utils/fuzz_utils.h",
    "content": "/*\n * Copyright (C) 2025  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifndef luastatus_fuzz_utils_h_\n#define luastatus_fuzz_utils_h_\n\n#include <stdlib.h>\n#include <stdint.h>\n#include <stdio.h>\n#include <sys/types.h>\n#include <unistd.h>\n\n// At least for fuzzing, please use a reasonably modern GNU C-compatible compiler.\n\n#define FUZZ_UTILS_INHEADER \\\n    static inline __attribute__((unused))\n\n#define FUZZ_UTILS_ATTR_WARN_UNUSED \\\n    __attribute__((warn_unused_result))\n\n#define FUZZ_UTILS_OOM() \\\n    do { \\\n        fputs(\"fuzz_utils: out of memory.\\n\", stderr); \\\n        abort(); \\\n    } while (0)\n\ntypedef struct {\n    char *data;\n    size_t size;\n    size_t capacity;\n} FuzzInput;\n\nFUZZ_UTILS_INHEADER FuzzInput fuzz_input_new_prealloc(size_t prealloc)\n{\n    if (!prealloc) {\n        return (FuzzInput) {0};\n    }\n    char *data = malloc(prealloc);\n    if (!data) {\n        FUZZ_UTILS_OOM();\n    }\n    return (FuzzInput) {\n        .data = data,\n        .size = 0,\n        .capacity = prealloc,\n    };\n}\n\nFUZZ_UTILS_INHEADER void fuzz_input__ensure1(FuzzInput *x)\n{\n    if (x->size != x->capacity) {\n        return;\n    }\n\n    if (x->capacity) {\n        if (x->capacity > SIZE_MAX / 2) {\n            goto oom;\n        }\n        x->capacity *= 2;\n    } else {\n        x->capacity = 1;\n    }\n    if (!(x->data = realloc(x->data, x->capacity))) {\n        goto oom;\n    }\n    return;\n\noom:\n    FUZZ_UTILS_OOM();\n}\n\nFUZZ_UTILS_INHEADER FUZZ_UTILS_ATTR_WARN_UNUSED\nint fuzz_input_read(int fd, FuzzInput *dst)\n{\n    for (;;) {\n        fuzz_input__ensure1(dst);\n        ssize_t r = read(fd, dst->data + dst->size, dst->capacity - dst->size);\n        if (r < 0) {\n            return -1;\n        } else if (r == 0) {\n            return 0;\n        }\n        dst->size += r;\n    }\n}\n\nFUZZ_UTILS_INHEADER void fuzz_input_free(FuzzInput x)\n{\n    free(x.data);\n}\n\nFUZZ_UTILS_INHEADER void fuzz_utils_used(const char *ptr, size_t len)\n{\n    // This is a compiler barrier: there's a contract between the compiler and\n    // the user that the compiler doesn't try to guess what inline assembly does,\n    // even if its body is empty.\n    //\n    // So after this, the compiler conservately thinks the span is used, because\n    // the inline assembly might have dereferenced it and done something that has\n    // side effects (e.g. made a system call \"write(1, ptr, len)\").\n    __asm__ __volatile__ (\n        \"\"\n        :\n        : \"r\"(ptr), \"r\"(len)\n        : \"memory\"\n    );\n}\n\n#endif\n"
  },
  {
    "path": "fuzz_utils/gen_testcases/.gitignore",
    "content": "__pycache__\n"
  },
  {
    "path": "fuzz_utils/gen_testcases/__init__.py",
    "content": "# Copyright (C) 2025  luastatus developers\n#\n# This file is part of luastatus.\n#\n# luastatus is free software: you can redistribute it and/or modify\n# it under the terms of the GNU Lesser General Public License as published by\n# the Free Software Foundation, either version 3 of the License, or\n# (at your option) any later version.\n#\n# luastatus is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n# GNU Lesser General Public License for more details.\n#\n# You should have received a copy of the GNU Lesser General Public License\n# along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n"
  },
  {
    "path": "fuzz_utils/gen_testcases/gen_testcases.py",
    "content": "#!/usr/bin/env python3\n\n# Copyright (C) 2025  luastatus developers\n#\n# This file is part of luastatus.\n#\n# luastatus is free software: you can redistribute it and/or modify\n# it under the terms of the GNU Lesser General Public License as published by\n# the Free Software Foundation, either version 3 of the License, or\n# (at your option) any later version.\n#\n# luastatus is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n# GNU Lesser General Public License for more details.\n#\n# You should have received a copy of the GNU Lesser General Public License\n# along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n\nimport argparse\nimport sys\nimport random\nimport typing\n\nimport mod_generator\nimport mod_mutator\nimport mod_mutators\n\n\nLENGTH_DEFAULT = 16\n\n\nNUM_FILES_DEFAULT = 20\n\n\nclass ChunkSpec:\n    def __init__(self, weight: int, chunk: bytes, is_homogeneous: bool) -> None:\n        assert weight > 0\n        assert chunk\n        self.weight = weight\n        self.chunk = chunk\n        self.is_homogeneous = is_homogeneous\n\n\ndef make_chunk_spec_checked(weight: int, chunk: bytes, is_homogeneous: bool) -> ChunkSpec:\n    if weight <= 0:\n        raise ValueError('weight must be positive')\n    if not chunk:\n        raise ValueError('chunk is empty')\n    return ChunkSpec(weight=weight, chunk=chunk, is_homogeneous=is_homogeneous)\n\n\ndef into_h_w_and_rest(s: str) -> typing.Tuple[bool, int, str]:\n    is_homogeneous = False\n    if s.startswith('h:'):\n        is_homogeneous = True\n        s = s[2:]\n\n    weight_s, rest = s.split(':', maxsplit=1)\n    weight = int(weight_s)\n    return (is_homogeneous, weight, rest)\n\n\ndef parse_arg_ab(s: str) -> ChunkSpec:\n    is_homogeneous, weight, content = into_h_w_and_rest(s)\n    return make_chunk_spec_checked(\n        weight=weight,\n        chunk=content.encode(),\n        is_homogeneous=is_homogeneous)\n\n\ndef parse_arg_ab_range(s: str) -> ChunkSpec:\n    is_homogeneous, weight, content = into_h_w_and_rest(s)\n\n    first_s, last_s = content.split('-')\n    first, last = int(first_s), int(last_s)\n\n    chunk = bytes(list(range(first, last + 1)))\n\n    return make_chunk_spec_checked(\n        weight=weight,\n        chunk=chunk,\n        is_homogeneous=is_homogeneous)\n\n\ndef parse_arg_length(s: str) -> typing.Tuple[int, int]:\n    if '-' in s:\n        n_min_s, n_max_s = s.split('-')\n        n_min, n_max = int(n_min_s), int(n_max_s)\n    else:\n        n = int(s)\n        n_min, n_max = n, n\n\n    if (n_min > n_max) or (n_min < 0) or (n_max < 0):\n        raise ValueError('invalid length specifier')\n\n    return (n_min, n_max)\n\n\ndef parse_arg_num_files(s: str) -> int:\n    n = int(s)\n    if n < 3:\n        raise ValueError('--num-files value must be at least 3')\n    return n\n\n\ndef parse_arg_prefix(s: str) -> bytes:\n    return s.encode()\n\n\ndef parse_arg_substrings(s: str) -> typing.List[bytes]:\n    separator = s[0]\n    parts = s[1:].split(separator)\n    return [part.encode() for part in parts]\n\n\ndef parse_arg_extra_testcase(s: str) -> typing.Tuple[str, bytes]:\n    file_suffix, content_s = s.split(':', maxsplit=1)\n    return (file_suffix, content_s.encode())\n\n\ndef main() -> None:\n    ap = argparse.ArgumentParser()\n\n    ap.add_argument('dest_dir')\n\n    def _add_list_arg(spelling: str, **kwargs: typing.Any) -> None:\n        ap.add_argument(spelling, action='append', default=[], **kwargs)\n\n    _add_list_arg('--a',       type=parse_arg_ab,       metavar='<CHUNK_SPEC>')\n    _add_list_arg('--a-range', type=parse_arg_ab_range, metavar='<RANGE_CHUNK_SPEC>')\n    _add_list_arg('--b',       type=parse_arg_ab,       metavar='<CHUNK_SPEC>')\n    _add_list_arg('--b-range', type=parse_arg_ab_range, metavar='<RANGE_CHUNK_SPEC>')\n\n    ap.add_argument(\n        '--a-is-important',\n        action='store_true')\n\n    ap.add_argument(\n        '--length',\n        default=(LENGTH_DEFAULT, LENGTH_DEFAULT),\n        type=parse_arg_length,\n        metavar='<NUMBER>|<MIN>-<MAX>')\n\n    ap.add_argument(\n        '--num-files',\n        default=NUM_FILES_DEFAULT,\n        type=parse_arg_num_files,\n        metavar='<NUMBER>')\n\n    ap.add_argument(\n        '--mut-prefix',\n        type=parse_arg_prefix,\n        required=False)\n\n    _add_list_arg('--extra-testcase', type=parse_arg_extra_testcase)\n\n    ap.add_argument(\n        '--mut-substrings',\n        type=parse_arg_substrings,\n        required=False)\n\n    ap.add_argument(\n        '--file-prefix',\n        default='')\n\n    ap.add_argument(\n        '--random-seed',\n        type=int,\n        required=False,\n        metavar='<NUMBER>')\n\n    args = ap.parse_args()\n\n    if args.random_seed is not None:\n        random.seed(args.random_seed, version=2)\n\n    g = mod_generator.Generator()\n\n    def _add_choices(chunk_specs: typing.List[ChunkSpec], key: str) -> None:\n        for chunk_spec in chunk_specs:\n            sheaf = mod_generator.Sheaf(\n                chunk_spec.chunk,\n                is_homogeneous=chunk_spec.is_homogeneous)\n            g.add_sheaf_for_key(\n                key=key,\n                sheaf=sheaf,\n                weight=chunk_spec.weight)\n\n    _add_choices(args.a, key='A')\n    _add_choices(args.a_range, key='A')\n    _add_choices(args.b, key='B')\n    _add_choices(args.b_range, key='B')\n\n    if not (g.has_choices_for_key('A') and g.has_choices_for_key('B')):\n        if not g.has_choices_for_key('A'):\n            print('Ether \"--a\" or \"--a-range\" must be specified.', file=sys.stderr)\n        else:\n            print('Ether \"--b\" or \"--b-range\" must be specified.', file=sys.stderr)\n        ap.print_help(sys.stderr)\n        sys.exit(2)\n\n    mutators: typing.List[mod_mutator.Mutator] = []\n    if args.mut_substrings is not None:\n        mutators.append(mod_mutators.SubstringMutator(args.mut_substrings))\n    if args.mut_prefix is not None:\n        mutators.append(mod_mutators.PrefixMutator(args.mut_prefix))\n\n    max_variants = max((m.get_number_of_variants() for m in mutators), default=-1)\n    if max_variants > args.num_files:\n        print(\n            'Specified mutators have too many variants (%d) for --num-files=%d' % (\n                max_variants, args.num_files\n            ),\n            file=sys.stderr)\n        sys.exit(2)\n\n    def _gen_filename(suffix: str) -> str:\n        return f'{args.dest_dir}/{args.file_prefix}testcase_{suffix}'\n\n    length_min, length_max = args.length\n\n    if args.a_is_important:\n        whole_length = g.get_whole_length_for_key('A')\n        if length_min < whole_length:\n            print(\n                'Minimal length (%d) is less than number of whole bytes in A (%d)' % (\n                    length_min, whole_length\n                ),\n                file=sys.stderr)\n            sys.exit(2)\n\n    for i in range(args.num_files):\n        with open(_gen_filename(f'{i:03d}'), 'wb') as f:\n\n            length = random.randint(length_min, length_max)\n            include_all_of_A = (i == 0) and args.a_is_important\n\n            content = mod_mutator.generate_with_mutators(\n                g=g,\n                mutators=mutators,\n                length=length,\n                i=i,\n                n=args.num_files,\n                include_all_of_A=include_all_of_A)\n            f.write(content)\n\n    for file_suffix, content in args.extra_testcase:\n        with open(_gen_filename(file_suffix), 'wb') as f:\n            f.write(content)\n\n\nif __name__ == '__main__':\n    main()\n"
  },
  {
    "path": "fuzz_utils/gen_testcases/mod_generator.py",
    "content": "# Copyright (C) 2025  luastatus developers\n#\n# This file is part of luastatus.\n#\n# luastatus is free software: you can redistribute it and/or modify\n# it under the terms of the GNU Lesser General Public License as published by\n# the Free Software Foundation, either version 3 of the License, or\n# (at your option) any later version.\n#\n# luastatus is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n# GNU Lesser General Public License for more details.\n#\n# You should have received a copy of the GNU Lesser General Public License\n# along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n\nimport random\nimport typing\n\n\ndef _clamp(x: int, min_value: int, max_value: int) -> int:\n    assert min_value <= max_value\n    if x < min_value:\n        return min_value\n    if x > max_value:\n        return max_value\n    return x\n\n\nclass Sheaf:\n    def __init__(self, b: bytes, is_homogeneous: bool) -> None:\n        assert b\n        self.b = b\n        self.is_homogeneous = is_homogeneous\n\n    def get_length(self) -> int:\n        return len(self.b)\n\n    def random_byte(self) -> bytes:\n        i = random.randint(0, len(self.b) - 1)\n        return self.b[i:i + 1]\n\n    def all_bytes(self) -> typing.Iterator[bytes]:\n        for i in range(len(self.b)):\n            yield self.b[i:i + 1]\n\n\nclass WeightedSheafList:\n    def __init__(self) -> None:\n        self.sheaves: typing.List[Sheaf] = []\n        self.weights: typing.List[int] = []\n\n    def add_sheaf(self, sheaf: Sheaf, weight: int) -> None:\n        assert weight > 0\n        self.sheaves.append(sheaf)\n        self.weights.append(weight)\n\n    def is_nonempty(self) -> bool:\n        return bool(self.sheaves)\n\n    def random_sheaf(self) -> Sheaf:\n        assert self.is_nonempty()\n        res, = random.choices(self.sheaves, weights=self.weights)\n        return res\n\n    def get_whole_length(self) -> int:\n        res = 0\n        for sheaf in self.sheaves:\n            if sheaf.is_homogeneous:\n                res += 1\n            else:\n                res += sheaf.get_length()\n        return res\n\n    def make_whole_iterator(self) -> typing.Iterator[bytes]:\n        for sheaf in self.sheaves:\n            if sheaf.is_homogeneous:\n                yield sheaf.random_byte()\n            else:\n                for byte in sheaf.all_bytes():\n                    yield byte\n        while True:\n            yield self.random_sheaf().random_byte()\n\n\nclass Generator:\n    def __init__(self) -> None:\n        self.wsls = {'A': WeightedSheafList(), 'B': WeightedSheafList()}\n\n    def add_sheaf_for_key(self, key: str, sheaf: Sheaf, weight: int) -> None:\n        self.wsls[key].add_sheaf(sheaf, weight=weight)\n\n    def has_choices_for_key(self, key: str) -> bool:\n        return self.wsls[key].is_nonempty()\n\n    def get_whole_length_for_key(self, key: str) -> int:\n        return self.wsls[key].get_whole_length()\n\n    def random_bytes(self, length: int, i: int, n: int, include_all_of_A: bool) -> bytes:\n        assert length >= 0\n        assert n > 0\n        assert 0 <= i < n\n\n        ratio = i / (n - 1)\n\n        num_B = _clamp(round(ratio * length), min_value=0, max_value=length)\n        num_A = length - num_B\n\n        formula = list('A' * num_A + 'B' * num_B)\n        random.shuffle(formula)\n\n        A_iter = None\n        if include_all_of_A:\n            assert self.wsls['A'].get_whole_length() <= length\n            A_iter = self.wsls['A'].make_whole_iterator()\n\n        chunks = []\n        for letter in formula:\n            if letter == 'A' and A_iter is not None:\n                chunks.append(next(A_iter))\n            else:\n                wsl = self.wsls[letter]\n                sheaf = wsl.random_sheaf()\n                chunks.append(sheaf.random_byte())\n        return b''.join(chunks)\n"
  },
  {
    "path": "fuzz_utils/gen_testcases/mod_mutator.py",
    "content": "# Copyright (C) 2025  luastatus developers\n#\n# This file is part of luastatus.\n#\n# luastatus is free software: you can redistribute it and/or modify\n# it under the terms of the GNU Lesser General Public License as published by\n# the Free Software Foundation, either version 3 of the License, or\n# (at your option) any later version.\n#\n# luastatus is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n# GNU Lesser General Public License for more details.\n#\n# You should have received a copy of the GNU Lesser General Public License\n# along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n\nimport typing\n\nimport mod_generator\n\n\nclass Mutator:\n    def mutate(self, b: bytes, variant: int) -> bytes:\n        raise NotImplementedError()\n\n    def get_number_of_variants(self) -> int:\n        raise NotImplementedError()\n\n\ndef generate_with_mutators(\n        g: mod_generator.Generator,\n        mutators: typing.List[Mutator],\n        length: int,\n        i: int,\n        n: int,\n        include_all_of_A: bool\n    ) -> bytes:\n\n    assert n > 0\n    assert 0 <= i < n\n\n    res = g.random_bytes(length=length, i=i, n=n, include_all_of_A=include_all_of_A)\n    for m in mutators:\n        variant = i % m.get_number_of_variants()\n        res = m.mutate(res, variant=variant)\n    return res\n"
  },
  {
    "path": "fuzz_utils/gen_testcases/mod_mutators.py",
    "content": "# Copyright (C) 2025  luastatus developers\n#\n# This file is part of luastatus.\n#\n# luastatus is free software: you can redistribute it and/or modify\n# it under the terms of the GNU Lesser General Public License as published by\n# the Free Software Foundation, either version 3 of the License, or\n# (at your option) any later version.\n#\n# luastatus is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n# GNU Lesser General Public License for more details.\n#\n# You should have received a copy of the GNU Lesser General Public License\n# along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n\nimport random\nimport typing\n\nimport mod_mutator\n\n\nclass PrefixMutator(mod_mutator.Mutator):\n    def __init__(self, prefix: bytes) -> None:\n        self.prefix = prefix\n\n    def mutate(self, b: bytes, variant: int) -> bytes:\n        return self.prefix[:variant] + b\n\n    def get_number_of_variants(self) -> int:\n        return len(self.prefix) + 1\n\n\nclass SubstringMutator(mod_mutator.Mutator):\n    def __init__(self, substr_variants: typing.List[bytes]) -> None:\n        self.substr_variants = substr_variants\n\n    def mutate(self, b: bytes, variant: int) -> bytes:\n        pos = random.randint(0, len(b))\n        substr = self.substr_variants[variant]\n        return b[:pos] + substr + b[pos:]\n\n    def get_number_of_variants(self) -> int:\n        return len(self.substr_variants)\n"
  },
  {
    "path": "generate-man.sh",
    "content": "#!/bin/sh\n\nif [ \"$#\" -ne 2 ]; then\n    printf '%s\\n' >&2 \"USAGE: $0 <source .rst file> <destination>\"\n    exit 2\nfi\n\nhas() {\n    command -v \"$1\" >/dev/null\n}\n\nif has rst2man; then\n    RST2MAN=rst2man\nelif has rst2man.py; then\n    RST2MAN=rst2man.py\nelse\n    echo >&2 \"ERROR: neither 'rst2man' nor 'rst2man.py' were found.\"\n    exit 1\nfi\n\nsed 's/^\\.\\. :X-man-page-only: \\?//' -- \"$1\" | $RST2MAN > \"$2\"\n"
  },
  {
    "path": "include/barlib_data.h",
    "content": "/*\n * Copyright (C) 2015-2025  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifndef luastatus_include_barlib_data_h_\n#define luastatus_include_barlib_data_h_\n\n#include <lua.h>\n#include <stddef.h>\n\n#include \"common.h\"\n\n#ifndef LUASTATUS_BARLIB_SAYF_ATTRIBUTE\n#   ifdef __GNUC__\n#       define LUASTATUS_BARLIB_SAYF_ATTRIBUTE __attribute__((format(printf, 3, 4)))\n#   else\n#       define LUASTATUS_BARLIB_SAYF_ATTRIBUTE /*nothing*/\n#   endif\n#endif\n\ntypedef LUASTATUS_BARLIB_SAYF_ATTRIBUTE void LuastatusBarlibSayf_v1(\n    void *userdata, int level, const char *fmt, ...);\n\ntypedef struct {\n    // This barlib's private data.\n    void *priv;\n\n    // A black-box user data.\n    void *userdata;\n\n    // Should be used for logging.\n    LuastatusBarlibSayf_v1 *sayf;\n\n    // See DOCS/design/map_get.md.\n    void ** (*map_get)(void *userdata, const char *key);\n} LuastatusBarlibData_v1;\n\ntypedef struct {\n    lua_State *(*call_begin) (void *userdata, size_t widget_idx);\n    void       (*call_end)   (void *userdata, size_t widget_idx);\n    void       (*call_cancel)(void *userdata, size_t widget_idx);\n} LuastatusBarlibEWFuncs_v1;\n\ntypedef struct {\n    // This function should initialize a barlib by assigning something to /bd->priv/.\n    // You would typically do:\n    //\n    //     typedef struct {\n    //         ...\n    //     } Priv;\n    //     ...\n    //     static\n    //     int\n    //     init(LuastatusBarlibData *bd, const char *const *opts, size_t nwidgets)\n    //     {\n    //         Priv *p = bd->priv = LS_XNEW(Priv, 1);\n    //         ...\n    //\n    // /opts/ are options passed to luastatus with /-B/ switches, with a sentinel /NULL/ entry.\n    //\n    // /nwidgets/ is the number of widgets. It stays unchanged during the entire life cycle of a\n    // barlib.\n    //\n    // It should return:\n    //\n    //     /LUASTATUS_ERR/ on failure;\n    //\n    //     /LUASTATUS_OK/ on success.\n    //\n    int (*init)(LuastatusBarlibData_v1 *bd, const char *const *opts, size_t nwidgets);\n\n    // This function should register Lua functions provided by the barlib into the table on the top\n    // of /L/'s stack.\n    //\n    // These functions should be thread-safe!\n    //\n    // It is guaranteed that /L/ stack has at least 15 free stack slots.\n    //\n    // May be /NULL/.\n    //\n    void (*register_funcs)(LuastatusBarlibData_v1 *bd, lua_State *L);\n\n    // This function should update the widget with index /widget_idx/. The data is located on the\n    // top of /L/'s stack; its format defined by the barlib itself.\n    //\n    // This function may push elements onto /L/'s stack (to iterate over tables), but should not\n    // modify elements below the initial top, or interact with /L/ in any other way.\n    //\n    // It is guaranteed that /L/'s stack has at least 15 free slots.\n    //\n    // It must return:\n    //\n    //     /LUASTATUS_OK/ on success.\n    //     In this case, /L/'s stack must not contain any extra elements pushed onto it;\n    //\n    //     /LUASTATUS_NONFATAL_ERR/ on a non-fatal error, e.g., if the format of the data on top of\n    //     /L/'s stack is invalid.\n    //     In this case, /L/'s stack may contain extra elements pushed onto it;\n    //\n    //     /LUASTATUS_ERR/ on a fatal error, e.g. if the connection to the display has been lost.\n    //     In this case, /L/'s stack may contain extra elements pushed onto it.\n    //\n    int (*set)(LuastatusBarlibData_v1 *bd, lua_State *L, size_t widget_idx);\n\n    // This function should update the widget with index /widget_idx/ and set its content to\n    // something that indicates an error.\n    //\n    // It must return:\n    //\n    //     /LUASTATUS_OK/ on success;\n    //\n    //     /LUASTATUS_ERR/ on a fatal error, e.g. if the connection to the display has been lost.\n    //\n    int (*set_error)(LuastatusBarlibData_v1 *bd, size_t widget_idx);\n\n    // This function should run barlib's event watcher. Once the barlib wants to report an event\n    // occurred on widget with index /i/, it should call\n    //     /lua_State *L = funcs.call_begin(i, bd->userdata)/,\n    // thus obtaining a /lua_State */ object /L/. Then, it must either:\n    // 1.  make the following call, with exactly one additional value pushed onto /L/'s stack:\n    //         /funcs.call_end(i, bd->userdata)/,\n    //     or\n    // 2.  make the following call, with any number of additional values push onto /L/'s stack:\n    //         /funcs.call_cancel(i, bd->userdata)/.\n    //\n    // Both /funcs.call_end/ and /funcs.call_cancel/ invalidate /L/ as a pointer.\n    //\n    // It is guaranteed that each /L/ object returned from /funcs.call_begin/ has at least 15 free\n    // stack slots.\n    //\n    // It must return:\n    //     /LUASTATUS_NONFATAL_ERR/ if, in some reason other than a fatal error, no events will be\n    //     reported anymore;\n    //\n    //     /LUASTATUS_ERR/ on a fatal error, e.g. if the connection to the display has been lost.\n    //\n    // May be /NULL/ (and should be /NULL/ if events are not supported).\n    //\n    int (*event_watcher)(LuastatusBarlibData_v1 *bd, LuastatusBarlibEWFuncs_v1 funcs);\n\n    // This function must destroy a previously successfully initialized barlib.\n    void (*destroy)(LuastatusBarlibData_v1 *bd);\n} LuastatusBarlibIface_v1;\n\n#endif\n"
  },
  {
    "path": "include/barlib_data_v1.h",
    "content": "/*\n * Copyright (C) 2015-2025  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifndef luastatus_include_barlib_data_v1_h_\n#define luastatus_include_barlib_data_v1_h_\n\n#include \"barlib_data.h\"\n\n#define LuastatusBarlibIface   LuastatusBarlibIface_v1\n#define LuastatusBarlibSayf    LuastatusBarlibSayf_v1\n#define LuastatusBarlibData    LuastatusBarlibData_v1\n#define LuastatusBarlibEWFuncs LuastatusBarlibEWFuncs_v1\n\n#endif\n"
  },
  {
    "path": "include/barlib_v1.h",
    "content": "/*\n * Copyright (C) 2015-2025  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifndef luastatus_include_barlib_v1_h_\n#define luastatus_include_barlib_v1_h_\n\n#include <lua.h>\n\n#include \"barlib_data_v1.h\"\n#include \"common.h\"\n\nconst int LUASTATUS_BARLIB_LUA_VERSION_NUM = LUA_VERSION_NUM;\n\nextern LuastatusBarlibIface_v1 luastatus_barlib_iface_v1;\n\n#endif\n"
  },
  {
    "path": "include/common.h",
    "content": "/*\n * Copyright (C) 2015-2025  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifndef luastatus_include_common_h_\n#define luastatus_include_common_h_\n\nenum {\n    LUASTATUS_OK,\n    LUASTATUS_ERR,\n    LUASTATUS_NONFATAL_ERR,\n};\n\nenum {\n    LUASTATUS_LOG_FATAL,\n    LUASTATUS_LOG_ERR,\n    LUASTATUS_LOG_WARN,\n    LUASTATUS_LOG_INFO,\n    LUASTATUS_LOG_VERBOSE,\n    LUASTATUS_LOG_DEBUG,\n    LUASTATUS_LOG_TRACE,\n\n    LUASTATUS_LOG_LAST,\n};\n\n#endif\n"
  },
  {
    "path": "include/plugin_data.h",
    "content": "/*\n * Copyright (C) 2015-2025  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifndef luastatus_include_plugin_data_h_\n#define luastatus_include_plugin_data_h_\n\n#include <lua.h>\n#include <stddef.h>\n\n#include \"common.h\"\n\n#ifndef LUASTATUS_PLUGIN_SAYF_ATTRIBUTE\n#   ifdef __GNUC__\n#       define LUASTATUS_PLUGIN_SAYF_ATTRIBUTE __attribute__((format(printf, 3, 4)))\n#   else\n#       define LUASTATUS_PLUGIN_SAYF_ATTRIBUTE /*nothing*/\n#   endif\n#endif\n\ntypedef LUASTATUS_PLUGIN_SAYF_ATTRIBUTE void LuastatusPluginSayf_v1(\n    void *userdata, int level, const char *fmt, ...);\n\ntypedef struct {\n    // This widget's private data.\n    void *priv;\n\n    // A black-box user data.\n    void *userdata;\n\n    // Should be used for logging.\n    LuastatusPluginSayf_v1 *sayf;\n\n    // See DOCS/design/map_get.md.\n    void ** (*map_get)(void *userdata, const char *key);\n} LuastatusPluginData_v1;\n\ntypedef struct {\n    lua_State *(*call_begin) (void *userdata);\n    void       (*call_end)   (void *userdata);\n    void       (*call_cancel)(void *userdata);\n} LuastatusPluginRunFuncs_v1;\n\ntypedef struct {\n    // This function should initialize a widget by assigning something to /pd->priv/.\n    // You would typically do:\n    //\n    //     typedef struct {\n    //         ...\n    //     } Priv;\n    //     ...\n    //     static\n    //     int\n    //     init(LuastatusPluginData *pd, lua_State *L) {\n    //         Priv *p = pd->priv = LS_XNEW(Priv, 1);\n    //         ...\n    //\n    // The options table is on the top of /L/'s stack.\n    //\n    // This function may push elements onto /L/'s stack to iterate over tables, but should not\n    // modify elements below the initial top, or interact with /L/ in any other way.\n    //\n    // It is guaranteed that /L/'s stack has at least 15 free slots.\n    //\n    // It should return:\n    //\n    //     /LUASTATUS_OK/ on success.\n    //     In this case, /L/'s stack should not contain any extra elements pushed onto it;\n    //\n    //     /LUASTATUS_ERR/ on failure.\n    //     In this case, /L/'s stack may contain extra elements pushed onto it.\n    //\n    int (*init)(LuastatusPluginData_v1 *pd, lua_State *L);\n\n    // This function should register Lua functions provided by the plugin into table on top of /L/'s\n    // stack.\n    //\n    // These functions, in current implementation, are not required to be thread-safe with respect\n    // to the widget instance, that is, /register_funcs/ in only called once per plugin instance,\n    // and while the widget is calling a function registered by this plugin instance, no other Lua\n    // state may do so.\n    //\n    // This may change in the future.\n    //\n    // Note that the above does not mean they are allowed to be genereally thread-unsafe.\n    //\n    // It is guaranteed that /L/'s stack has at least 15 free slots.\n    //\n    // May be /NULL/.\n    void (*register_funcs)(LuastatusPluginData_v1 *pd, lua_State *L);\n\n    // This function should run a widget. Once the plugin wants to update the widget, it should call\n    //     /lua_State *L = funcs.call_begin(pd->userdata)/,\n    // thus obtaining a /lua_State */ object /L/. Then it must either:\n    // 1.  make the following call, with exactly one additional value pushed onto /L/'s stack:\n    //         /funcs.call_end(bd->userdata)/,\n    //     or\n    // 2.  make the following call, with any number of additional values push onto /L/'s stack:\n    //         /funcs.call_cancel(bd->userdata)/.\n    //\n    // Both /funcs.call_end/ and /funcs.call_cancel/ invalidate /L/ as a pointer.\n    //\n    // It is guaranteed that each /L/ object returned from /funcs.call_begin/ has at least 15 free\n    // stack slots.\n    //\n    // It should only return on an unrecoverable failure.\n    void (*run)(LuastatusPluginData_v1 *pd, LuastatusPluginRunFuncs_v1 funcs);\n\n    // This function should destroy a previously successfully initialized widget.\n    void (*destroy)(LuastatusPluginData_v1 *pd);\n} LuastatusPluginIface_v1;\n\n#endif\n"
  },
  {
    "path": "include/plugin_data_v1.h",
    "content": "/*\n * Copyright (C) 2015-2025  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifndef luastatus_include_plugin_data_v1_h_\n#define luastatus_include_plugin_data_v1_h_\n\n#include \"plugin_data.h\"\n\n#define LuastatusPluginIface    LuastatusPluginIface_v1\n#define LuastatusPluginSayf     LuastatusPluginSayf_v1\n#define LuastatusPluginData     LuastatusPluginData_v1\n#define LuastatusPluginRunFuncs LuastatusPluginRunFuncs_v1\n\n#endif\n"
  },
  {
    "path": "include/plugin_v1.h",
    "content": "/*\n * Copyright (C) 2015-2025  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifndef luastatus_include_plugin_v1_h_\n#define luastatus_include_plugin_v1_h_\n\n#include <lua.h>\n\n#include \"plugin_data_v1.h\"\n#include \"common.h\"\n\nconst int LUASTATUS_PLUGIN_LUA_VERSION_NUM = LUA_VERSION_NUM;\n\nextern LuastatusPluginIface_v1 luastatus_plugin_iface_v1;\n\n#endif\n"
  },
  {
    "path": "include/sayf_macros.h",
    "content": "/*\n * Copyright (C) 2015-2025  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifndef luastatus_include_sayf_macros_\n#define luastatus_include_sayf_macros_\n\n#define LS_FATALF(Data_, ...)   Data_->sayf(Data_->userdata, LUASTATUS_LOG_FATAL,   __VA_ARGS__)\n#define LS_ERRF(Data_, ...)     Data_->sayf(Data_->userdata, LUASTATUS_LOG_ERR,     __VA_ARGS__)\n#define LS_WARNF(Data_, ...)    Data_->sayf(Data_->userdata, LUASTATUS_LOG_WARN,    __VA_ARGS__)\n#define LS_INFOF(Data_, ...)    Data_->sayf(Data_->userdata, LUASTATUS_LOG_INFO,    __VA_ARGS__)\n#define LS_VERBOSEF(Data_, ...) Data_->sayf(Data_->userdata, LUASTATUS_LOG_VERBOSE, __VA_ARGS__)\n#define LS_DEBUGF(Data_, ...)   Data_->sayf(Data_->userdata, LUASTATUS_LOG_DEBUG,   __VA_ARGS__)\n#define LS_TRACEF(Data_, ...)   Data_->sayf(Data_->userdata, LUASTATUS_LOG_TRACE,   __VA_ARGS__)\n\n#endif\n"
  },
  {
    "path": "libhackyfix/CMakeLists.txt",
    "content": "file (GLOB sources \"*.c\")\nadd_library (hackyfix OBJECT ${sources})\n\ntarget_compile_definitions (hackyfix PUBLIC -D_POSIX_C_SOURCE=200809L)\n\n# link against dl\ntarget_link_libraries (hackyfix PUBLIC ${CMAKE_DL_LIBS})\n\nset_target_properties (ls PROPERTIES POSITION_INDEPENDENT_CODE ON)\n"
  },
  {
    "path": "libhackyfix/README.txt",
    "content": "Lua uses non-thread-safe functions all over the place and does another things\nthat are very problematic in multi-threaded environments.\n\nExamples of things that are very problematic in multi-threaded environments:\n  * Calling \"fflush(NULL)\" before calling popen() in its implementation of\n    io.popen() function. The reasons why Lua does this are unknown to us.\n\n    fflush(NULL) traverses all open FILE* objects, including read-only ones,\n    acquires the lock on each one, flushes it, and then releases the lock.\n\n    The problem is that \"fflush(NULL)\" hangs if, at the time of the call, at\n    least one FILE* object is waiting on an input (e.g. blocked in fgetc(),\n    getline() or similar) or is blocked on writing (e.g. when writing to a\n    pipe).\n\n    It only returns once all blocking stdio functions that are in-flight\n    return, because all stdio functions (except unlocked_* ones) also lock the\n    file.\n\n    This makes using stdio in any of the threads very risky: Lua's io.popen()\n    in another thread now may block for indefinite time. Note that Lua's stdlib\n    also uses stdio all over the place.\n\nExamples of usage of non-thread-safe (according to POSIX-2008) functions:\n  * exit(): only used in implementation of os.exit().\n    - we replace this Lua function in luastatus.\n\n  * setlocale(): only used in implementation of os.setlocale().\n    - we replace this Lua function in luastatus.\n\n  * system(): only used in implementation of os.execute().\n    - we replace this Lua function in luastatus.\n\n  * dlerror(): used internally!\n\n  * getenv(): used internally! And also in implementation of os.getenv().\n\n  * strerror(): used internally!\n\n  * localeconv(): used internally!\n\nSo this library replaces the following functions with thread-safe counterparts\nand/or different semantics to work around things that are very problematic in\nmulti-threaded environments:\n\n  * fflush();\n\n  * strerror();\n\n  * getenv();\n\n  * localeconv();\n\n  * dlerror() (unless compiled with glibc, where it *is* thread-safe).\n"
  },
  {
    "path": "libhackyfix/fatal.c",
    "content": "/*\n * Copyright (C) 2025  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#include \"fatal.h\"\n#include <unistd.h>\n#include <sys/types.h>\n#include <stdlib.h>\n#include <string.h>\n\nstatic void full_write(int fd, const char *buf, size_t nbuf)\n{\n    size_t nwritten = 0;\n    while (nwritten < nbuf) {\n        ssize_t w = write(fd, buf + nwritten, nbuf - nwritten);\n        if (w < 0) {\n            break;\n        }\n        nwritten += w;\n    }\n}\n\nvoid libhackyfix_fatal(const char *msg)\n{\n    full_write(2, msg, strlen(msg));\n    abort();\n}\n"
  },
  {
    "path": "libhackyfix/fatal.h",
    "content": "/*\n * Copyright (C) 2025  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifndef libhackyfix_fatal_h_\n#define libhackyfix_fatal_h_\n\nvoid libhackyfix_fatal(const char *msg);\n\n#endif\n"
  },
  {
    "path": "libhackyfix/get_rtld_next_handle.c",
    "content": "/*\n * Copyright (C) 2025  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifndef _GNU_SOURCE\n#   define _GNU_SOURCE\n#endif\n#include \"get_rtld_next_handle.h\"\n#include <dlfcn.h>\n\nvoid *libhackyfix_get_rtld_next_handle(void)\n{\n    return RTLD_NEXT;\n}\n"
  },
  {
    "path": "libhackyfix/get_rtld_next_handle.h",
    "content": "/*\n * Copyright (C) 2025  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifndef libhackyfix_get_rtld_next_handle_h_\n#define libhackyfix_get_rtld_next_handle_h_\n\nvoid *libhackyfix_get_rtld_next_handle(void);\n\n#endif\n"
  },
  {
    "path": "libhackyfix/libhackyfix.c",
    "content": "/*\n * Copyright (C) 2025  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#include <dlfcn.h>\n#include <stdio.h>\n#include <string.h>\n#include <stdlib.h>\n#include <locale.h>\n#include \"get_rtld_next_handle.h\"\n#include \"fatal.h\"\n\n// dlerror() is not thread-safe at least on NetBSD and musl.\n// Let's assume other *BSDs and/or libcs have non-thread-safe dlerror() as well.\n\n#if defined(__GLIBC__)\n#   define REPLACE_DLERROR 0\n#else\n#   define REPLACE_DLERROR 1\n#endif\n\nstatic int (*orig_fflush)(FILE *f);\nstatic struct lconv *(*orig_localeconv)(void);\n\nstatic struct lconv my_lconv;\n\nstatic char *xstrdup_or_null(const char *s)\n{\n    if (!s) {\n        return NULL;\n    }\n    size_t ns = strlen(s);\n    char *res = malloc(ns + 1);\n    if (!res) {\n        libhackyfix_fatal(\"FATAL: libhackyfix: malloc() failed\\n\");\n    }\n    memcpy(res, s, ns + 1);\n    return res;\n}\n\nstatic void query_and_copy_lconv(void)\n{\n    struct lconv *cur = orig_localeconv();\n    my_lconv = *cur;\n\n#define copy(field) (my_lconv.field = xstrdup_or_null(cur->field))\n\n    copy(decimal_point);\n    copy(thousands_sep);\n    copy(grouping);\n    copy(int_curr_symbol);\n    copy(currency_symbol);\n    copy(mon_decimal_point);\n    copy(mon_thousands_sep);\n    copy(mon_grouping);\n    copy(positive_sign);\n    copy(negative_sign);\n\n#undef copy\n}\n\n__attribute__((constructor))\nstatic void initialize(void)\n{\n    void *rtld_next_handle = libhackyfix_get_rtld_next_handle();\n\n    *(void **) &orig_fflush = dlsym(rtld_next_handle, \"fflush\");\n    if (!orig_fflush) {\n        libhackyfix_fatal(\"FATAL: libhackyfix: dlsym(..., \\\"fflush\\\") failed\\n\");\n    }\n\n    *(void **) &orig_localeconv = dlsym(rtld_next_handle, \"localeconv\");\n    if (!orig_localeconv) {\n        libhackyfix_fatal(\"FATAL: libhackyfix: dlsym(..., \\\"localeconv\\\") failed\\n\");\n    }\n\n    query_and_copy_lconv();\n}\n\n__attribute__((destructor))\nstatic void deinitialize(void)\n{\n    free(my_lconv.decimal_point);\n    free(my_lconv.thousands_sep);\n    free(my_lconv.grouping);\n    free(my_lconv.int_curr_symbol);\n    free(my_lconv.currency_symbol);\n    free(my_lconv.mon_decimal_point);\n    free(my_lconv.mon_thousands_sep);\n    free(my_lconv.mon_grouping);\n    free(my_lconv.positive_sign);\n    free(my_lconv.negative_sign);\n}\n\n// ==================================================\n// Replacement for /fflush/\n// ==================================================\n\nint fflush(FILE *f)\n{\n    if (!f) {\n        return 0;\n    }\n    return orig_fflush(f);\n}\n\n// ==================================================\n// Replacement for /strerror/\n// ==================================================\n\n#if _POSIX_C_SOURCE < 200112L || defined(_GNU_SOURCE)\n#   error \"Unsupported feature test macros.\"\n#endif\n\nstatic __thread char errbuf[512];\n\nchar *strerror(int errnum)\n{\n    // We introduce an /int/ variable in order to get a compilation warning if /strerror_r()/ is\n    // still GNU-specific and returns a pointer to char.\n    int r = strerror_r(errnum, errbuf, sizeof(errbuf));\n    return r == 0 ? errbuf : (char *) \"unknown error or truncated error message\";\n}\n\n// ==================================================\n// Replacement for /getenv/\n// ==================================================\n\nextern char **environ;\n\nchar *getenv(const char *name)\n{\n    if (!environ) {\n        return NULL;\n    }\n    if ((strchr(name, '='))) {\n        return NULL;\n    }\n    size_t nname = strlen(name);\n    for (char **s = environ; *s; ++s) {\n        const char *entry = *s;\n        if (strncmp(entry, name, nname) == 0 && entry[nname] == '=') {\n            return (char *) (entry + nname + 1);\n        }\n    }\n    return NULL;\n}\n\n// ==================================================\n// Replacement for /localeconv/\n// ==================================================\nstruct lconv *localeconv(void)\n{\n    return &my_lconv;\n}\n\n#if REPLACE_DLERROR\n\n// ==================================================\n// Replacement for /dlerror/\n// ==================================================\n\nchar *dlerror(void)\n{\n    return (char *) \"(no error message for you because on your system dlerror() isn't thread-safe)\";\n}\n\n#endif\n"
  },
  {
    "path": "libls/CMakeLists.txt",
    "content": "file (GLOB sources \"*.c\")\nadd_library (ls OBJECT ${sources})\n\ninclude (CheckSymbolExists)\nset (CMAKE_REQUIRED_DEFINITIONS \"-D_GNU_SOURCE\")\ncheck_symbol_exists (pipe2 \"fcntl.h;unistd.h\" LS_HAVE_GNU_PIPE2)\ncheck_symbol_exists (SOCK_CLOEXEC \"sys/socket.h\" LS_HAVE_GNU_SOCK_CLOEXEC)\nconfigure_file (\"ls_probes.in.h\" \"ls_probes.generated.h\")\n\ntarget_compile_definitions (ls PUBLIC -D_POSIX_C_SOURCE=200809L)\nluastatus_target_compile_with (ls LUA)\ntarget_include_directories (ls PUBLIC \"${PROJECT_SOURCE_DIR}\" \"${CMAKE_CURRENT_BINARY_DIR}\")\n\nfind_library (MATH_LIBRARY m)\nif (MATH_LIBRARY)\n    target_link_libraries (ls PUBLIC ${MATH_LIBRARY})\nendif ()\n\nset_target_properties (ls PROPERTIES POSITION_INDEPENDENT_CODE ON)\n"
  },
  {
    "path": "libls/ls_algo.h",
    "content": "/*\n * Copyright (C) 2015-2025  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifndef ls_algo_h_\n#define ls_algo_h_\n\n#define LS_ARRAY_SIZE(X_) (sizeof(X_) / sizeof((X_)[0]))\n\n#define LS_SWAP(Type_, X_, Y_) \\\n    do { \\\n        Type_ ls_swap_tmp_ = (X_); \\\n        (X_) = (Y_); \\\n        (Y_) = ls_swap_tmp_; \\\n    } while (0)\n\n#endif\n"
  },
  {
    "path": "libls/ls_alloc_utils.c",
    "content": "/*\n * Copyright (C) 2015-2025  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#include \"ls_alloc_utils.h\"\n\n#include <stdlib.h>\n#include <stdint.h>\n#include <stdbool.h>\n#include \"ls_panic.h\"\n\nstatic inline bool mul_zu(size_t *dst, size_t a, size_t b)\n{\n    if (b && a > SIZE_MAX / b) {\n        return false;\n    }\n    *dst = a * b;\n    return true;\n}\n\nvoid *ls_xmalloc(size_t nelems, size_t elemsz)\n{\n    size_t total_bytes;\n    if (!mul_zu(&total_bytes, nelems, elemsz)) {\n        goto oom;\n    }\n    void *r = malloc(total_bytes);\n    if (!r && total_bytes) {\n        goto oom;\n    }\n    return r;\n\noom:\n    ls_oom();\n}\n\nvoid *ls_xcalloc(size_t nelems, size_t elemsz)\n{\n    void *r = calloc(nelems, elemsz);\n    if (nelems && elemsz && !r) {\n        ls_oom();\n    }\n    return r;\n}\n\nvoid *ls_xrealloc(void *p, size_t nelems, size_t elemsz)\n{\n    size_t total_bytes;\n    if (!mul_zu(&total_bytes, nelems, elemsz)) {\n        goto oom;\n    }\n    if (!total_bytes) {\n        free(p);\n        return NULL;\n    }\n    void *r = realloc(p, total_bytes);\n    if (!r) {\n        goto oom;\n    }\n    return r;\n\noom:\n    ls_oom();\n}\n\nvoid *ls_x2realloc(void *p, size_t *pnelems, size_t elemsz)\n{\n    LS_ASSERT(elemsz != 0);\n\n    if (*pnelems) {\n        if (!mul_zu(pnelems, *pnelems, 2)) {\n            ls_oom();\n        }\n    } else {\n        *pnelems = 1;\n    }\n\n    return ls_xrealloc(p, *pnelems, elemsz);\n}\n\nvoid *ls_xmemdup(const void *p, size_t n)\n{\n    void *r = malloc(n);\n    if (n) {\n        if (!r) {\n            ls_oom();\n        }\n        memcpy(r, p, n);\n    }\n    return r;\n}\n"
  },
  {
    "path": "libls/ls_alloc_utils.h",
    "content": "/*\n * Copyright (C) 2015-2025  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifndef ls_alloc_utils_h_\n#define ls_alloc_utils_h_\n\n#include <stddef.h>\n#include <string.h>\n\n#include \"ls_panic.h\"\n#include \"ls_compdep.h\"\n\n#define LS_XNEW(Type_, NElems_) \\\n    ((Type_ *) ls_xmalloc(NElems_, sizeof(Type_)))\n\n#define LS_XNEW0(Type_, NElems_) \\\n    ((Type_ *) ls_xcalloc(NElems_, sizeof(Type_)))\n\n#define LS_M_X2REALLOC(Ptr_, NElemsPtr_) \\\n    ((__typeof__(Ptr_)) ls_x2realloc((Ptr_), (NElemsPtr_), sizeof((Ptr_)[0])))\n\n#define LS_M_XMEMDUP(Ptr_, N_) \\\n    ((__typeof__(Ptr_)) ls_xmemdup((Ptr_), ((size_t) (N_)) * sizeof((Ptr_)[0])))\n\n#define LS_M_XREALLOC(Ptr_, N_) \\\n    ((__typeof__(Ptr_)) ls_xrealloc((Ptr_), (N_), sizeof((Ptr_)[0])))\n\n// Out-of-memory handler; should be called when an allocation fails.\n//\n// Should not return.\n#define ls_oom() LS_PANIC(\"out of memory\")\n\n// The behavior is same as calling\n//     /malloc(nelems * elemsz)/,\n// except when the multiplication overflows, or the allocation fails. In these cases, this function\n// panics.\nLS_ATTR_WARN_UNUSED\nvoid *ls_xmalloc(size_t nelems, size_t elemsz);\n\n// The behavior is same as calling\n//     /calloc(nelems, elemsz)/,\n// except when the allocation fails. In that case, this function panics.\nLS_ATTR_WARN_UNUSED\nvoid *ls_xcalloc(size_t nelems, size_t elemsz);\n\n// The behavior is same as calling\n//     /realloc(p, nelems * elemsz)/,\n// except when the multiplication overflows, or the reallocation fails. In these cases, this\n// function panics.\n//\n// Zero /nelems/ and/or /elemsz/ are supported (even though C23 declared /realloc/ to zero size\n// undefined behavior): if the total size requested is zero, this function /free/s the pointer\n// and returns NULL.\nLS_ATTR_WARN_UNUSED\nvoid *ls_xrealloc(void *p, size_t nelems, size_t elemsz);\n\n// The behavior is same as calling\n//     /realloc(p, (*pnelems = F(*pnelems)) * elemsz)/,\n// where F(n) = max(1, 2 * n),\n// except when a multiplication overflows, or the reallocation fails. In these cases, this function\n// panics.\n//\n// Zero /elemsz/ is NOT supported: it isn't clear what the semantics of this function should be in\n// this case: technically you can store any number of zero-sized elements in an allocation of any\n// size, but /*pnelems/ is limited to /SIZE_MAX/ (should we alter /*pnelems/ at all? If yes, what\n// should we do in case of an overflow?). Also, C99 says there can't be any zero-sized types: empty\n// structs or unions, and arrays of size 0, are prohibited.\nLS_ATTR_WARN_UNUSED\nvoid *ls_x2realloc(void *p, size_t *pnelems, size_t elemsz);\n\n// Duplicates (as if with /malloc/) /n/ bytes of memory at address /p/. Panics on failure.\nLS_ATTR_WARN_UNUSED\nvoid *ls_xmemdup(const void *p, size_t n);\n\n// The behavior is same as calling\n//     /strdup(s)/,\n// except when the allocation fails. In that case, this function panics.\nLS_INHEADER LS_ATTR_WARN_UNUSED\nchar *ls_xstrdup(const char *s)\n{\n    LS_ASSERT(s != NULL);\n\n    return ls_xmemdup(s, strlen(s) + 1);\n}\n\n#endif\n"
  },
  {
    "path": "libls/ls_compdep.h",
    "content": "/*\n * Copyright (C) 2015-2025  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifndef ls_compdep_h_\n#define ls_compdep_h_\n\n// ------------------------------------------------------------\n// GCC version (set by clang to some very old version)\n// ------------------------------------------------------------\n\n#define LS_GCC_VERSION (__GNUC__ * 10000 + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__)\n\n// ------------------------------------------------------------\n// Clang stuff\n// ------------------------------------------------------------\n\n#if defined(__has_attribute)\n#   define LS_CLANG_HAS_ATTRIBUTE(X_) __has_attribute(X_)\n#else\n#   define LS_CLANG_HAS_ATTRIBUTE(X_) 0\n#endif\n\n#if defined(__has_builtin)\n#   define LS_CLANG_HAS_BUILTIN(X_) __has_builtin(X_)\n#else\n#   define LS_CLANG_HAS_BUILTIN(X_) 0\n#endif\n\n// ------------------------------------------------------------\n// Attributes\n// ------------------------------------------------------------\n\n#if __GNUC__ >= 2\n#   define LS_ATTR_UNUSED           __attribute__((unused))\n#   define LS_ATTR_PRINTF(N_, M_)   __attribute__((format(printf, N_, M_)))\n#   define LS_ATTR_NORETURN         __attribute__((noreturn))\n#else\n#   define LS_ATTR_UNUSED           /*nothing*/\n#   define LS_ATTR_PRINTF(N_, M_)   /*nothing*/\n#   define LS_ATTR_NORETURN         /*nothing*/\n#endif\n\n#if LS_GCC_VERSION >= 30406 || LS_CLANG_HAS_ATTRIBUTE(warn_unused_result)\n#   define LS_ATTR_WARN_UNUSED __attribute__((warn_unused_result))\n#else\n#   define LS_ATTR_WARN_UNUSED /*nothing*/\n#endif\n\n// ------------------------------------------------------------\n// LS_INHEADER\n// ------------------------------------------------------------\n#define LS_INHEADER static inline LS_ATTR_UNUSED\n\n#endif\n"
  },
  {
    "path": "libls/ls_cstring_utils.c",
    "content": "/*\n * Copyright (C) 2015-2025  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#if _POSIX_C_SOURCE < 200112L || defined(_GNU_SOURCE)\n#   error \"Unsupported feature test macros; either tune them or change the code.\"\n#endif\n\n#include \"ls_cstring_utils.h\"\n\n#include <string.h>\n#include <errno.h>\n\nconst char *ls_strerror_r(int errnum, char *buf, size_t nbuf)\n{\n    // luastatus-specific \"fake\" errno values\n    switch (errnum) {\n    case -EINVAL:\n        return \"Not a FIFO\";\n    }\n\n    // We introduce an /int/ variable in order to get a compilation warning if /strerror_r()/ is\n    // still GNU-specific and returns a pointer to char.\n    int r = strerror_r(errnum, buf, nbuf);\n    return r == 0 ? buf : \"unknown error or truncated error message\";\n}\n"
  },
  {
    "path": "libls/ls_cstring_utils.h",
    "content": "/*\n * Copyright (C) 2015-2025  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifndef ls_cstring_utils_h_\n#define ls_cstring_utils_h_\n\n#include <stddef.h>\n#include <string.h>\n\n#include \"ls_compdep.h\"\n#include \"ls_panic.h\"\n\n// If zero-terminated string /str/ starts with zero-terminated string /prefix/, returns\n// /str + strlen(prefix)/; otherwise, returns /NULL/.\nLS_INHEADER const char *ls_strfollow(const char *str, const char *prefix)\n{\n    LS_ASSERT(str != NULL);\n    LS_ASSERT(prefix != NULL);\n\n    size_t nprefix = strlen(prefix);\n    return strncmp(str, prefix, nprefix) == 0 ? str + nprefix : NULL;\n}\n\n// Behaves like the GNU-specific /strerror_r/: either fills /buf/ and returns it, or returns a\n// pointer to a static string.\nconst char *ls_strerror_r(int errnum, char *buf, size_t nbuf);\n\n#endif\n"
  },
  {
    "path": "libls/ls_evloop_lfuncs.h",
    "content": "/*\n * Copyright (C) 2015-2025  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifndef ls_evloop_lfuncs_h_\n#define ls_evloop_lfuncs_h_\n\n#include <stddef.h>\n#include <math.h>\n#include <pthread.h>\n#include <unistd.h>\n#include <sys/types.h>\n#include <lua.h>\n#include <lauxlib.h>\n#include \"ls_compdep.h\"\n#include \"ls_panic.h\"\n#include \"ls_osdep.h\"\n#include \"ls_io_utils.h\"\n#include \"ls_time_utils.h\"\n\n// Some plugins provide a \"push_timeout\"/\"push_period\" function that allows a widget to specify the\n// next timeout for an otherwise constant timeout-based plugin's event loop.\n//\n// The pushed timeout value has to be guarded with a lock as the \"push_timeout\"/\"push_period\"\n// function may be called from the /event/ function of a widget (that is, asynchronously from the\n// plugin's viewpoint).\n//\n// /LS_PushedTimeout/ is a structure containing the pushed timeout value (or an absence of such, as\n// indicated by the value being negative), and a lock.\n//\n// <!!!>\n// This structure must reside at a constant address throughout its whole life; this is required for\n// the Lua closure created with /ls_pushed_timeout_push_luafunc()/.\n// </!!!>\ntypedef struct {\n    double value;\n    pthread_mutex_t lock;\n} LS_PushedTimeout;\n\n// Initializes /p/ with an absence of pushed timeout value and a newly-created lock.\nLS_INHEADER void ls_pushed_timeout_init(LS_PushedTimeout *p)\n{\n    p->value = -1;\n    LS_PTH_CHECK(pthread_mutex_init(&p->lock, NULL));\n}\n\n// Does the following actions atomically:\n// * checks if /p/ has a pushed timeout value;\n//   * if it does, clears it and returns the timeout value that /p/ has previously had;\n//   * if it does not, returns /alt/.\nLS_INHEADER LS_TimeDelta ls_pushed_timeout_fetch(LS_PushedTimeout *p, LS_TimeDelta alt)\n{\n    LS_TimeDelta r;\n    LS_PTH_CHECK(pthread_mutex_lock(&p->lock));\n    if (p->value < 0) {\n        r = alt;\n    } else {\n        r = ls_double_to_TD(p->value, LS_TD_FOREVER);\n        p->value = -1;\n    }\n    LS_PTH_CHECK(pthread_mutex_unlock(&p->lock));\n    return r;\n}\n\nLS_INHEADER int ls_pushed_timeout_lfunc(lua_State *L)\n{\n    double arg = luaL_checknumber(L, 1);\n    if (!isgreaterequal(arg, 0.0)) {\n        return luaL_argerror(L, 1, \"invalid timeout\");\n    }\n\n    LS_PushedTimeout *p = lua_touserdata(L, lua_upvalueindex(1));\n\n    LS_PTH_CHECK(pthread_mutex_lock(&p->lock));\n    p->value = arg;\n    LS_PTH_CHECK(pthread_mutex_unlock(&p->lock));\n\n    return 0;\n}\n\n// Creates a \"push_timeout\" function (a \"C closure\" with /p/'s address, in Lua terminology) on /L/'s\n// stack.\n//\n// The resulting Lua function takes one numeric argument (the timeout in seconds) and does not\n// return anything. It may throw an error if an invalid argument is provided. Once called, it will\n// (atomically) alter /p/'s timeout value.\n//\n// The caller must ensure that the /L/'s stack has at least 2 free slots.\nLS_INHEADER void ls_pushed_timeout_push_luafunc(LS_PushedTimeout *p, lua_State *L)\n{\n    lua_pushlightuserdata(L, p);\n    lua_pushcclosure(L, ls_pushed_timeout_lfunc, 1);\n}\n\n// Destroys /p/.\nLS_INHEADER void ls_pushed_timeout_destroy(LS_PushedTimeout *p)\n{\n    LS_PTH_CHECK(pthread_mutex_destroy(&p->lock));\n}\n\n// Some plugins provide the so-called self-pipe facility; that is, the ability to \"wake up\" the\n// widget's event loop (and force a call to the widget's /cb()/ function) from within the widget's\n// /event()/ function via a special \"wake_up\" Lua function.\n//\n// Such a pipe consists of an array, /int fds[2]/, of two file descriptors; the /fds[0]/ file\n// descriptor is to be read from, and /fds[1]/ is to be written to.\n//\n// <!!!>\n// This array must reside at a constant address throughout its whole life; this is required for\n// the Lua closure created with /ls_self_pipe_push_luafunc()/.\n// </!!!>\n\n// Creates a new self-pipe.\n//\n// On success, /0/ is returned.\n//\n// On failure, /-1/ is returned, /fds[0]/ and /fds[1]/ are set to -1, and /errno/ is set.\nLS_INHEADER int ls_self_pipe_open(int fds[2])\n{\n    if (ls_cloexec_pipe(fds) < 0) {\n        fds[0] = -1;\n        fds[1] = -1;\n        return -1;\n    }\n    ls_make_nonblock(fds[0]);\n    ls_make_nonblock(fds[1]);\n    return 0;\n}\n\nLS_INHEADER int ls_self_pipe_lfunc(lua_State *L)\n{\n    int *fds = lua_touserdata(L, lua_upvalueindex(1));\n\n    int fd = fds[1];\n    if (fd < 0) {\n        return luaL_error(L, \"self-pipe has not been opened\");\n    }\n\n    ssize_t unused = write(fd, \"\", 1); // write '\\0'\n    (void) unused;\n\n    return 0;\n}\n\n// Creates a \"wake_up\" function (a \"C closure\" with /s/'s address, in Lua terminology) on /L/'s\n// stack.\n//\n// The resulting Lua function takes no arguments and does not return anything. Once called, it will\n// write a single byte to /fds[1]/ (in a thread-safe manner), or throw an error if the self-pipe\n// has not been opened.\n//\n// The caller must ensure that the /L/'s stack has at least 2 free slots.\nLS_INHEADER void ls_self_pipe_push_luafunc(int fds[2], lua_State *L)\n{\n    lua_pushlightuserdata(L, fds);\n    lua_pushcclosure(L, ls_self_pipe_lfunc, 1);\n}\n\n#endif\n"
  },
  {
    "path": "libls/ls_fifo_device.h",
    "content": "/*\n * Copyright (C) 2015-2025  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifndef ls_fifo_device_h_\n#define ls_fifo_device_h_\n\n#include <unistd.h>\n#include \"ls_compdep.h\"\n#include \"ls_time_utils.h\"\n#include \"ls_io_utils.h\"\n\n// A \"FIFO device\" is a device that emits an event whenever somebody does\n// touch(1) (that is, opens for writing and then closes) on a FIFO file with\n// a specified path.\n//\n// If specified path is NULL, no events are ever reported.\n//\n// The suggested usage is as follows:\n//\n//     LS_FifoDevice dev = ls_fifo_device_new();\n//     for (;;) {\n//         // ...\n//         if (ls_fifo_device_open(&dev, fifo_path) < 0) {\n//             // ... handle error ...\n//         }\n//         if (ls_fifo_device_wait(&dev, timeout)) {\n///           // ... somebody touched the FIFO, do something ...\n//         }\n//         // ...\n//     }\n//     ls_fifo_device_close(&dev);\n\ntypedef struct {\n    int fd;\n} LS_FifoDevice;\n\nLS_INHEADER LS_FifoDevice ls_fifo_device_new(void)\n{\n    return (LS_FifoDevice) {-1};\n}\n\nLS_INHEADER int ls_fifo_device_open(LS_FifoDevice *dev, const char *path)\n{\n    if (dev->fd >= 0) {\n        return 0;\n    }\n    if (!path) {\n        return 0;\n    }\n    dev->fd = ls_open_fifo(path);\n    if (dev->fd < 0) {\n        return -1;\n    }\n    return 0;\n}\n\nLS_INHEADER int ls_fifo_device_wait(LS_FifoDevice *dev, LS_TimeDelta tmo)\n{\n    int r = ls_wait_input_on_fd(dev->fd, tmo);\n    if (r > 0) {\n        close(dev->fd);\n        dev->fd = -1;\n    }\n    return r;\n}\n\nLS_INHEADER void ls_fifo_device_close(LS_FifoDevice *dev)\n{\n    ls_close(dev->fd);\n}\n\nLS_INHEADER void ls_fifo_device_reset(LS_FifoDevice *dev)\n{\n    ls_fifo_device_close(dev);\n    *dev = ls_fifo_device_new();\n}\n\n#endif\n"
  },
  {
    "path": "libls/ls_freemem.h",
    "content": "/*\n * Copyright (C) 2025  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifndef ls_freemem_h_\n#define ls_freemem_h_\n\n#include <stddef.h>\n#include \"ls_alloc_utils.h\"\n#include \"ls_compdep.h\"\n#include \"ls_panic.h\"\n\n// This function is called whenever a dynamic array is cleared.\n// We want to \"preserve\" at most 1 Kb of the previous capacity.\nLS_INHEADER LS_ATTR_WARN_UNUSED\nvoid *ls_freemem(void *data, size_t *psize, size_t *pcapacity, size_t elemsz)\n{\n    LS_ASSERT(elemsz != 0);\n    size_t new_capacity = 1024 / elemsz;\n    if (*pcapacity > new_capacity) {\n        data = ls_xrealloc(data, new_capacity, elemsz);\n        *pcapacity = new_capacity;\n    }\n    *psize = 0;\n    return data;\n}\n\n#define LS_M_FREEMEM(Data_, SizePtr_, CapacityPtr_) \\\n    ((__typeof__(Data_)) ls_freemem( \\\n        (Data_),        \\\n        (SizePtr_),     \\\n        (CapacityPtr_), \\\n        sizeof(*(Data_))))\n\n#endif\n"
  },
  {
    "path": "libls/ls_getenv_r.c",
    "content": "/*\n * Copyright (C) 2015-2025  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#include \"ls_getenv_r.h\"\n\n#include <stddef.h>\n#include <string.h>\n#include \"ls_panic.h\"\n\nextern char **environ;\n\nconst char *ls_getenv_r(const char *name)\n{\n    LS_ASSERT(name != NULL);\n\n    if (!environ) {\n        return NULL;\n    }\n    if ((strchr(name, '='))) {\n        return NULL;\n    }\n    size_t nname = strlen(name);\n    for (char **s = environ; *s; ++s) {\n        const char *entry = *s;\n        if (strncmp(entry, name, nname) == 0 && entry[nname] == '=') {\n            return entry + nname + 1;\n        }\n    }\n    return NULL;\n}\n"
  },
  {
    "path": "libls/ls_getenv_r.h",
    "content": "/*\n * Copyright (C) 2015-2025  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifndef ls_getenv_r_h_\n#define ls_getenv_r_h_\n\n// Thread-safe /getenv/.\nconst char *ls_getenv_r(const char *name);\n\n#endif\n"
  },
  {
    "path": "libls/ls_io_utils.c",
    "content": "/*\n * Copyright (C) 2015-2025  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#include \"ls_io_utils.h\"\n#include <stddef.h>\n#include <poll.h>\n#include <fcntl.h>\n#include <errno.h>\n#include <signal.h>\n#include <sys/stat.h>\n#include \"ls_time_utils.h\"\n#include \"ls_panic.h\"\n\nstatic int poll_forever(struct pollfd *fds, size_t nfds)\n{\n    int r;\n    while ((r = poll(fds, nfds, -1)) < 0 && errno == EINTR) {\n        // do nothing\n    }\n    return r;\n}\n\nint ls_poll(struct pollfd *fds, size_t nfds, LS_TimeDelta tmo)\n{\n    if (nfds != (nfds_t) nfds) {\n        errno = EOVERFLOW;\n        return -1;\n    }\n\n    int tmo_ms = ls_TD_to_poll_ms_tmo(tmo);\n    if (tmo_ms < 0) {\n        return poll_forever(fds, nfds);\n    }\n\n    sigset_t allsigs;\n    LS_ASSERT(sigfillset(&allsigs) == 0);\n\n    sigset_t origmask;\n    LS_PTH_CHECK(pthread_sigmask(SIG_SETMASK, &allsigs, &origmask));\n\n    int r = poll(fds, nfds, tmo_ms);\n\n    int saved_errno = errno;\n    LS_PTH_CHECK(pthread_sigmask(SIG_SETMASK, &origmask, NULL));\n    errno = saved_errno;\n\n    return r;\n}\n\nint ls_open_fifo(const char *fifo)\n{\n    LS_ASSERT(fifo != NULL);\n\n    int saved_errno;\n\n    int fd = open(fifo, O_RDONLY | O_CLOEXEC | O_NONBLOCK);\n    if (fd < 0) {\n        goto error;\n    }\n\n    struct stat sb;\n    if (fstat(fd, &sb) < 0) {\n        goto error;\n    }\n\n    if (!S_ISFIFO(sb.st_mode)) {\n        errno = -EINVAL;\n        goto error;\n    }\n\n    return fd;\n\nerror:\n    saved_errno = errno;\n    ls_close(fd);\n    errno = saved_errno;\n    return -1;\n}\n"
  },
  {
    "path": "libls/ls_io_utils.h",
    "content": "/*\n * Copyright (C) 2015-2025  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifndef ls_io_utils_h_\n#define ls_io_utils_h_\n\n#include <stddef.h>\n#include <fcntl.h>\n#include <unistd.h>\n#include <errno.h>\n#include <poll.h>\n#include \"ls_compdep.h\"\n#include \"ls_time_utils.h\"\n\n#if EAGAIN == EWOULDBLOCK\n#   define LS_IS_EAGAIN(E_) ((E_) == EAGAIN)\n#else\n#   define LS_IS_EAGAIN(E_) ((E_) == EAGAIN || (E_) == EWOULDBLOCK)\n#endif\n\n// Makes a file descriptor close-on-exec.\n// On success, /fd/ is returned.\n// On failure, /-1/ is returned and /errno/ is set.\nLS_INHEADER int ls_make_cloexec(int fd)\n{\n    int flags = fcntl(fd, F_GETFD);\n    if (flags < 0) {\n        return -1;\n    }\n    flags |= FD_CLOEXEC;\n    if (fcntl(fd, F_SETFD, flags) < 0) {\n        return -1;\n    }\n    return fd;\n}\n\n// Makes a file descriptor non-blocking.\n// On success, /fd/ is returned.\n// On failure, /-1/ is returned and /errno/ is set.\nLS_INHEADER int ls_make_nonblock(int fd)\n{\n    int flags = fcntl(fd, F_GETFL);\n    if (flags < 0) {\n        return -1;\n    }\n    flags |= O_NONBLOCK;\n    if (fcntl(fd, F_SETFL, flags) < 0) {\n        return -1;\n    }\n    return fd;\n}\n\n// Close if non-negative: valgrind doesn't like /close()/ on negative FDs.\nLS_INHEADER int ls_close(int fd)\n{\n    if (fd < 0) {\n        return 0;\n    }\n    return close(fd);\n}\n\n// Poll restarting on /EINTR/ errors.\nint ls_poll(struct pollfd *fds, size_t nfds, LS_TimeDelta tmo);\n\n// Poll a single fd for /POLLIN/ events.\nLS_INHEADER int ls_wait_input_on_fd(int fd, LS_TimeDelta tmo)\n{\n    struct pollfd pfd = {.fd = fd, .events = POLLIN};\n    return ls_poll(&pfd, 1, tmo);\n}\n\n// Open a FIFO for reading, and check that this is really a FIFO.\nint ls_open_fifo(const char *fifo);\n\n#endif\n"
  },
  {
    "path": "libls/ls_lua_compat.h",
    "content": "/*\n * Copyright (C) 2015-2025  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifndef ls_lua_compat_h_\n#define ls_lua_compat_h_\n\n#include <lua.h>\n#include <lauxlib.h>\n#include <stdbool.h>\n#include <stddef.h>\n#include <stdint.h>\n#include <limits.h>\n#include \"ls_compdep.h\"\n\n#if LUA_VERSION_NUM >= 504\n#   define ls_lua_pushfail(L_) luaL_pushfail(L_)\n#else\n#   define ls_lua_pushfail(L_) lua_pushnil(L_)\n#endif\n\nLS_INHEADER bool ls_lua_is_lua51(lua_State *L)\n{\n#if LUA_VERSION_NUM >= 502\n    (void) L;\n    return false;\n#else\n    // LuaJIT, when compiled with -DLUAJIT_ENABLE_LUA52COMPAT, still defines\n    // LUA_VERSION_NUM to 501, but syntax parser, library functions and some\n    // aspect of language work as if it was Lua 5.2.\n\n    // L: ?\n    lua_getglobal(L, \"rawlen\"); // L: ? rawlen\n    bool ret = lua_isnil(L, -1);\n    lua_pop(L, 1); // L: ?\n    return ret;\n#endif\n}\n\nLS_INHEADER size_t ls_lua_array_len(lua_State *L, int pos)\n{\n#if LUA_VERSION_NUM <= 501\n    return lua_objlen(L, pos);\n#else\n    return lua_rawlen(L, pos);\n#endif\n}\n\n#ifdef LUA_MAXINTEGER\n# define LS_LUA_MAXI \\\n    (LUA_MAXINTEGER > (SIZE_MAX - 1) ? (SIZE_MAX - 1) : LUA_MAXINTEGER)\n#else\n# define LS_LUA_MAXI INT_MAX\n#endif\n\nLS_INHEADER int ls_lua_num_prealloc(size_t n)\n{\n    return n < (size_t) INT_MAX ? (int) n : INT_MAX;\n}\n\n#endif\n"
  },
  {
    "path": "libls/ls_osdep.c",
    "content": "/*\n * Copyright (C) 2015-2025  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#define _GNU_SOURCE\n#include \"ls_osdep.h\"\n\n#include <fcntl.h>\n#include <unistd.h>\n#include <sys/socket.h>\n\n#include \"ls_probes.generated.h\"\n\nint ls_cloexec_pipe(int pipefd[2])\n{\n#if LS_HAVE_GNU_PIPE2\n    return pipe2(pipefd, O_CLOEXEC);\n#else\n    if (pipe(pipefd) < 0) {\n        return -1;\n    }\n    ls_make_cloexec(pipefd[0]);\n    ls_make_cloexec(pipefd[1]);\n    return 0;\n#endif\n}\n\nint ls_cloexec_socket(int domain, int type, int protocol)\n{\n#if LS_HAVE_GNU_SOCK_CLOEXEC\n    return socket(domain, type | SOCK_CLOEXEC, protocol);\n#else\n    int fd = socket(domain, type, protocol);\n    if (fd < 0) {\n        return -1;\n    }\n    ls_make_cloexec(fd);\n    return fd;\n#endif\n}\n"
  },
  {
    "path": "libls/ls_osdep.h",
    "content": "/*\n * Copyright (C) 2015-2025  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifndef ls_osdep_h_\n#define ls_osdep_h_\n\n// We can't define /_GNU_SOURCE/ here, nor can we include \"probes.generated.h\", as\n// it may be located outside of the source tree: /libls/ is built with /libls/'s\n// binary directory included, unlike everything that includes its headers.\n// So no in-header functions.\n\n// The behavior is same as calling /pipe(pipefd)/, except that both file descriptors are made\n// close-on-exec. If the latter fails, the pipe is destroyed, /-1/ is returned and /errno/ is set.\nint ls_cloexec_pipe(int pipefd[2]);\n\n// The behavior is same as calling /socket(domain, type, protocol)/, except that the file\n// descriptor is make close-on-exec. If the latter fails, the pipe is destroyed, /-1/ is reurned and\n// /errno/ is set.\nint ls_cloexec_socket(int domain, int type, int protocol);\n\n#endif\n"
  },
  {
    "path": "libls/ls_panic.c",
    "content": "/*\n * Copyright (C) 2015-2025  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#include \"ls_panic.h\"\n#include <stdlib.h>\n#include <stdio.h>\n#include \"ls_cstring_utils.h\"\n\nvoid ls__do_panic_impl__(\n    const char *macro_name,\n    const char *expr,\n    const char *msg,\n    const char *func, const char *file, int line)\n{\n    fprintf(\n        stderr, \"%s(%s) failed in %s at %s:%d: %s\\n\",\n        macro_name, expr,\n        func, file, line,\n        msg);\n    abort();\n}\n\nvoid ls__do_panic_with_errnum_impl__(\n    const char *macro_name,\n    const char *expr,\n    const char *msg,\n    int errnum,\n    const char *func, const char *file, int line)\n{\n    char buf[512];\n    fprintf(\n        stderr, \"%s(%s) failed in %s at %s:%d: %s: %s\\n\",\n        macro_name, expr,\n        func, file, line,\n        msg, ls_strerror_r(errnum, buf, sizeof(buf))\n    );\n    abort();\n}\n"
  },
  {
    "path": "libls/ls_panic.h",
    "content": "/*\n * Copyright (C) 2015-2025  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifndef ls_panic_h_\n#define ls_panic_h_\n\n#include \"ls_compdep.h\"\n\nLS_ATTR_NORETURN void ls__do_panic_impl__(\n    const char *macro_name,\n    const char *expr,\n    const char *msg,\n    const char *func, const char *file, int line);\n\nLS_ATTR_NORETURN void ls__do_panic_with_errnum_impl__(\n    const char *macro_name,\n    const char *expr,\n    const char *msg,\n    int errnum,\n    const char *func, const char *file, int line);\n\n#define LS__DO_PANIC__(MacroName_, Expr_, Msg_) \\\n    ls__do_panic_impl__( \\\n        MacroName_, \\\n        Expr_, \\\n        Msg_, \\\n        __func__, __FILE__, __LINE__)\n\n#define LS__DO_PANIC_WITH_ERRNUM__(MacroName_, Expr_, Msg_, Errnum_) \\\n    ls__do_panic_with_errnum_impl__( \\\n        MacroName_, \\\n        Expr_, \\\n        Msg_, \\\n        Errnum_, \\\n        __func__, __FILE__, __LINE__)\n\n// Logs /Msg_/ and aborts.\n#define LS_PANIC(Msg_) \\\n    LS__DO_PANIC__(\"LS_PANIC\", \"\", Msg_)\n\n// Logs /Msg_/ with errno value /Errnum_/ and aborts.\n#define LS_PANIC_WITH_ERRNUM(Msg_, Errnum_) \\\n    LS__DO_PANIC_WITH_ERRNUM__(\"LS_PANIC_WITH_ERRNUM\", \"\", Msg_, Errnum_)\n\n// Asserts that a /pthread_*/ call was successful.\n#define LS_PTH_CHECK(Expr_) \\\n    do { \\\n        int ls_pth_rc_ = (Expr_); \\\n        if (ls_pth_rc_ != 0) { \\\n            LS__DO_PANIC_WITH_ERRNUM__(\"LS_PTH_CHECK\", #Expr_, \"check failed\", ls_pth_rc_); \\\n        } \\\n    } while (0)\n\n// Asserts that /Expr_/ is true.\n#define LS_ASSERT(Expr_) \\\n    do { \\\n        if (!(Expr_)) { \\\n            LS__DO_PANIC__(\"LS_ASSERT\", #Expr_, \"assertion failed\"); \\\n        } \\\n    } while (0)\n\n// Just use it to tell the compiler some code is unreachable.\n#define LS_MUST_BE_UNREACHABLE() LS_PANIC(\"LS_MUST_BE_UNREACHABLE()\")\n\n#endif\n"
  },
  {
    "path": "libls/ls_parse_int.c",
    "content": "/*\n * Copyright (C) 2015-2025  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#include \"ls_parse_int.h\"\n\n#include <limits.h>\n#include <errno.h>\n\nint ls_strtou_b(const char *s, size_t ns, const char **endptr)\n{\n    int ret = 0;\n    size_t i = 0;\n\n    for (; i != ns; ++i) {\n        int digit = ((int) s[i]) - '0';\n        if (digit < 0 || digit > 9) {\n            break;\n        }\n        if (ret > INT_MAX / 10) {\n            ret = -ERANGE;\n            break;\n        }\n        ret *= 10;\n        if (ret > INT_MAX - digit) {\n            ret = -ERANGE;\n            break;\n        }\n        ret += digit;\n    }\n\n    if (endptr) {\n        *endptr = s + i;\n    }\n    return ret;\n}\n\nint ls_full_strtou_b(const char *s, size_t ns)\n{\n    if (!ns) {\n        return -EINVAL;\n    }\n    const char *endptr;\n    int r = ls_strtou_b(s, ns, &endptr);\n    if (r < 0) {\n        return r;\n    }\n    if (endptr != s + ns) {\n        return -EINVAL;\n    }\n    return r;\n}\n"
  },
  {
    "path": "libls/ls_parse_int.h",
    "content": "/*\n * Copyright (C) 2015-2025  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifndef ls_parse_int_h_\n#define ls_parse_int_h_\n\n#include <stddef.h>\n#include <string.h>\n\n#include \"ls_compdep.h\"\n#include \"ls_panic.h\"\n\n// Parses (locale-independently) a decimal unsigned integer, inspecting no more than first /ns/\n// characters of /s/. Once this limit is reached, or a non-digit character is found, this function\n// stops, writes the current position to /*endptr/, unless it is /NULL/; and returns what has been\n// parsed insofar (if nothing, /0/ is returned).\n//\n// If an overflow happens, /-ERANGE/ is returned.\nint ls_strtou_b(const char *s, size_t ns, const char **endptr);\n\n// Parses (locale-independently) a decimal unsigned integer using first /ns/ characters of /s/.\n//\n// If a non-digit character is found among them, or if /ns/ is /0/, /-EINVAL/ is returned.\n// If an overflow happens, /-ERANGE/ is returned.\nint ls_full_strtou_b(const char *s, size_t ns);\n\n// Parses (locale-independently) a decimal unsigned integer from a zero-terminated string /s/.\n//\n// If a non-digit character is found in /s/, or if /s/ is empty, /-EINVAL/ is returned.\n// If an overflow happens, /-ERANGE/ is returned.\nLS_INHEADER int ls_full_strtou(const char *s)\n{\n    LS_ASSERT(s != NULL);\n\n    return ls_full_strtou_b(s, strlen(s));\n}\n\n#endif\n"
  },
  {
    "path": "libls/ls_probes.in.h",
    "content": "#ifndef ls_probes_h_\n#define ls_probes_h_\n\n#cmakedefine01 LS_HAVE_GNU_PIPE2\n#cmakedefine01 LS_HAVE_GNU_SOCK_CLOEXEC\n\n#endif\n"
  },
  {
    "path": "libls/ls_strarr.h",
    "content": "/*\n * Copyright (C) 2015-2025  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifndef ls_strarr_h_\n#define ls_strarr_h_\n\n#include <stdlib.h>\n#include <string.h>\n\n#include \"ls_string.h\"\n#include \"ls_alloc_utils.h\"\n#include \"ls_compdep.h\"\n#include \"ls_freemem.h\"\n#include \"ls_panic.h\"\n\n// An array of constant strings on a single buffer. Panics on allocation failure.\n\ntypedef struct {\n    size_t *data;\n    size_t size;\n    size_t capacity;\n} LS_StringArray_Offsets;\n\ntypedef struct {\n    LS_String buf;\n    LS_StringArray_Offsets offsets;\n} LS_StringArray;\n\nLS_INHEADER LS_StringArray ls_strarr_new(void)\n{\n    return (LS_StringArray) {\n        .buf = ls_string_new(),\n        .offsets = {NULL, 0, 0},\n    };\n}\n\nLS_INHEADER LS_StringArray ls_strarr_new_reserve(size_t totlen, size_t nelems)\n{\n    size_t *offsets_buf = LS_XNEW(size_t, nelems);\n    return (LS_StringArray) {\n        .buf = ls_string_new_reserve(totlen),\n        .offsets = {offsets_buf, 0, nelems},\n    };\n}\n\nLS_INHEADER void ls_strarr_append(LS_StringArray *sa, const char *buf, size_t nbuf)\n{\n    LS_StringArray_Offsets *o = &sa->offsets;\n    if (o->size == o->capacity) {\n        o->data = LS_M_X2REALLOC(o->data, &o->capacity);\n    }\n    o->data[o->size++] = sa->buf.size;\n\n    ls_string_append_b(&sa->buf, buf, nbuf);\n}\n\nLS_INHEADER void ls_strarr_append_s(LS_StringArray *sa, const char *s)\n{\n    LS_ASSERT(s != NULL);\n\n    ls_strarr_append(sa, s, strlen(s) + 1);\n}\n\nLS_INHEADER size_t ls_strarr_size(LS_StringArray sa)\n{\n    return sa.offsets.size;\n}\n\nLS_INHEADER const char *ls_strarr_at(LS_StringArray sa, size_t i, size_t *n)\n{\n    LS_StringArray_Offsets o = sa.offsets;\n\n    LS_ASSERT(i < o.size);\n\n    size_t begin = o.data[i];\n    size_t end = (i + 1 == o.size) ? sa.buf.size : o.data[i + 1];\n    if (n) {\n        *n = end - begin;\n    }\n    return sa.buf.data + begin;\n}\n\nLS_INHEADER void ls_strarr_clear(LS_StringArray *sa)\n{\n    ls_string_clear(&sa->buf);\n\n    LS_StringArray_Offsets *o = &sa->offsets;\n    o->data = LS_M_FREEMEM(o->data, &o->size, &o->capacity);\n}\n\nLS_INHEADER void ls_strarr_destroy(LS_StringArray sa)\n{\n    ls_string_free(sa.buf);\n    free(sa.offsets.data);\n}\n\n#endif\n"
  },
  {
    "path": "libls/ls_string.c",
    "content": "/*\n * Copyright (C) 2015-2025  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#include \"ls_string.h\"\n\n#include <stdio.h>\n#include <errno.h>\n#include <stdarg.h>\n#include \"ls_panic.h\"\n\nvoid ls_string_append_vf(LS_String *x, const char *fmt, va_list vl)\n{\n    va_list vl2;\n    va_copy(vl2, vl);\n\n    size_t navail = x->capacity - x->size;\n    int r = vsnprintf(x->data + x->size, navail, fmt, vl);\n    if (r < 0) {\n        goto fail;\n    }\n    if (((size_t) r) >= navail) {\n        ls_string_ensure_avail(x, ((size_t) r) + 1);\n        if (vsnprintf(x->data + x->size, ((size_t) r) + 1, fmt, vl2) < 0) {\n            goto fail;\n        }\n    }\n    x->size += r;\n    va_end(vl2);\n    return;\n\nfail:\n    LS_PANIC_WITH_ERRNUM(\"ls_string_append_vf: vsnprintf() failed\", errno);\n}\n"
  },
  {
    "path": "libls/ls_string.h",
    "content": "/*\n * Copyright (C) 2015-2025  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifndef ls_string_h_\n#define ls_string_h_\n\n#include <stdlib.h>\n#include <stdarg.h>\n#include <string.h>\n#include <stdbool.h>\n\n#include \"ls_compdep.h\"\n#include \"ls_alloc_utils.h\"\n#include \"ls_algo.h\"\n#include \"ls_freemem.h\"\n#include \"ls_panic.h\"\n\ntypedef struct {\n    char *data;\n    size_t size;\n    size_t capacity;\n} LS_String;\n\nLS_INHEADER LS_String ls_string_new(void)\n{\n    return (LS_String) {NULL, 0, 0};\n}\n\nLS_INHEADER LS_String ls_string_new_reserve(size_t n)\n{\n    char *buf = LS_XNEW(char, n);\n    return (LS_String) {buf, 0, n};\n}\n\nLS_INHEADER void ls_string_reserve(LS_String *x, size_t n)\n{\n    if (x->capacity < n) {\n        x->capacity = n;\n        x->data = LS_M_XREALLOC(x->data, n);\n    }\n}\n\nLS_INHEADER void ls_string_ensure_avail(LS_String *x, size_t n)\n{\n    while (x->capacity - x->size < n) {\n        x->data = LS_M_X2REALLOC(x->data, &x->capacity);\n    }\n}\n\nLS_INHEADER void ls_string_clear(LS_String *x)\n{\n    x->data = LS_M_FREEMEM(x->data, &x->size, &x->capacity);\n}\n\nLS_INHEADER void ls_string_assign_b(LS_String *x, const char *buf, size_t nbuf)\n{\n    ls_string_reserve(x, nbuf);\n    // see DOCS/c_notes/empty-ranges-and-c-stdlib.md\n    if (nbuf) {\n        memcpy(x->data, buf, nbuf);\n    }\n    x->size = nbuf;\n}\n\nLS_INHEADER void ls_string_assign_s(LS_String *x, const char *s)\n{\n    LS_ASSERT(s != NULL);\n\n    ls_string_assign_b(x, s, strlen(s));\n}\n\nLS_INHEADER void ls_string_append_b(LS_String *x, const char *buf, size_t nbuf)\n{\n    ls_string_ensure_avail(x, nbuf);\n    // see DOCS/c_notes/empty-ranges-and-c-stdlib.md\n    if (nbuf) {\n        memcpy(x->data + x->size, buf, nbuf);\n    }\n    x->size += nbuf;\n}\n\nLS_INHEADER void ls_string_append_s(LS_String *x, const char *s)\n{\n    LS_ASSERT(s != NULL);\n\n    ls_string_append_b(x, s, strlen(s));\n}\n\nLS_INHEADER void ls_string_append_c(LS_String *x, char c)\n{\n    if (x->size == x->capacity) {\n        x->data = LS_M_X2REALLOC(x->data, &x->capacity);\n    }\n    x->data[x->size++] = c;\n}\n\n// Appends a formatted string to /x/.\nLS_ATTR_PRINTF(2, 0)\nvoid ls_string_append_vf(LS_String *x, const char *fmt, va_list vl);\n\n// Appends a formatted string to /x/.\nLS_INHEADER LS_ATTR_PRINTF(2, 3)\nvoid ls_string_append_f(LS_String *x, const char *fmt, ...)\n{\n    va_list vl;\n    va_start(vl, fmt);\n    ls_string_append_vf(x, fmt, vl);\n    va_end(vl);\n}\n\nLS_ATTR_PRINTF(2, 0)\nLS_INHEADER void ls_string_assign_vf(LS_String *x, const char *fmt, va_list vl)\n{\n    ls_string_clear(x);\n    ls_string_append_vf(x, fmt, vl);\n}\n\nLS_INHEADER LS_ATTR_PRINTF(2, 3)\nvoid ls_string_assign_f(LS_String *s, const char *fmt, ...)\n{\n    va_list vl;\n    va_start(vl, fmt);\n    ls_string_assign_vf(s, fmt, vl);\n    va_end(vl);\n}\n\nLS_INHEADER LS_String ls_string_new_from_s(const char *s)\n{\n    LS_String x = ls_string_new();\n    ls_string_assign_s(&x, s);\n    return x;\n}\n\nLS_INHEADER LS_String ls_string_new_from_b(const char *buf, size_t nbuf)\n{\n    LS_String x = ls_string_new();\n    ls_string_assign_b(&x, buf, nbuf);\n    return x;\n}\n\nLS_INHEADER LS_ATTR_PRINTF(1, 0)\nLS_String ls_string_new_from_vf(const char *fmt, va_list vl)\n{\n    LS_String x = ls_string_new();\n    ls_string_append_vf(&x, fmt, vl);\n    return x;\n}\n\nLS_INHEADER LS_ATTR_PRINTF(1, 2)\nLS_String ls_string_new_from_f(const char *fmt, ...)\n{\n    va_list vl;\n    va_start(vl, fmt);\n    LS_String x = ls_string_new_from_vf(fmt, vl);\n    va_end(vl);\n    return x;\n}\n\nLS_INHEADER bool ls_string_eq_b(LS_String x, const char *buf, size_t nbuf)\n{\n    // We have to check that the size is not zero before calling /memcmp/:\n    // see DOCS/c_notes/empty-ranges-and-c-stdlib.md\n    return x.size == nbuf && (nbuf == 0 || memcmp(x.data, buf, nbuf) == 0);\n}\n\nLS_INHEADER bool ls_string_eq(LS_String x, LS_String y)\n{\n    return ls_string_eq_b(x, y.data, y.size);\n}\n\n// Swaps two string efficiently (in O(1) time).\nLS_INHEADER void ls_string_swap(LS_String *x, LS_String *y)\n{\n    LS_SWAP(LS_String, *x, *y);\n}\n\nLS_INHEADER void ls_string_free(LS_String x)\n{\n    free(x.data);\n}\n\n#endif\n"
  },
  {
    "path": "libls/ls_time_utils.h",
    "content": "/*\n * Copyright (C) 2015-2025  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifndef ls_time_utils_h_\n#define ls_time_utils_h_\n\n#include <errno.h>\n#include <math.h>\n#include <time.h>\n#include <sys/time.h>\n#include <stdbool.h>\n#include <limits.h>\n#include <unistd.h>\n\n#include \"ls_compdep.h\"\n#include \"ls_panic.h\"\n\n// Time deltas >= LS_TMO_MAX (in seconds) are treated as \"forever\".\n#define LS_TMO_MAX 2147483647.0\n\n// These are wrapped into structs so that it's hard to do the wrong thing.\n\n// A time stamp. Can be either valid or \"bad\".\ntypedef struct {\n    double ts;\n} LS_TimeStamp;\n\n// A time delta. Can be either valid or \"forever\".\ntypedef struct {\n    double delta;\n} LS_TimeDelta;\n\n#define LS_TD_FOREVER ((LS_TimeDelta) {LS_TMO_MAX})\n\n#define LS_TD_ZERO ((LS_TimeDelta) {0.0})\n\n#define LS_TS_BAD ((LS_TimeStamp) {-1.0})\n\nLS_INHEADER bool ls_double_is_good_time_delta(double x)\n{\n    return isgreaterequal(x, 0.0);\n}\n\nLS_INHEADER bool ls_TD_is_forever(LS_TimeDelta TD)\n{\n    return TD.delta >= LS_TMO_MAX;\n}\n\nLS_INHEADER bool ls_TS_is_bad(LS_TimeStamp TS)\n{\n    return TS.ts < 0;\n}\n\nLS_INHEADER bool ls_double_to_TD_checked(double delta, LS_TimeDelta *out)\n{\n    if (!ls_double_is_good_time_delta(delta)) {\n        return false;\n    }\n    if (delta > LS_TMO_MAX) {\n        delta = LS_TMO_MAX;\n    }\n    *out = (LS_TimeDelta) {delta};\n    return true;\n}\n\nLS_INHEADER LS_TimeDelta ls_double_to_TD(double delta, LS_TimeDelta if_bad)\n{\n    LS_TimeDelta res;\n    if (ls_double_to_TD_checked(delta, &res)) {\n        return res;\n    }\n    return if_bad;\n}\n\nLS_INHEADER LS_TimeDelta ls_double_to_TD_or_die(double delta)\n{\n    LS_TimeDelta res;\n    if (!ls_double_to_TD_checked(delta, &res)) {\n        LS_PANIC(\"ls_double_to_TD_or_die() failed\");\n    }\n    return res;\n}\n\nLS_INHEADER double ls_timespec_to_raw_double(struct timespec ts)\n{\n    double s = ts.tv_sec;\n    double ns = ts.tv_nsec;\n    return s + ns / 1e9;\n}\n\nLS_INHEADER LS_TimeStamp ls_timespec_to_TS(struct timespec ts)\n{\n    return (LS_TimeStamp) {ls_timespec_to_raw_double(ts)};\n}\n\nLS_INHEADER LS_TimeDelta ls_timespec_to_TD(struct timespec ts)\n{\n    return (LS_TimeDelta) {ls_timespec_to_raw_double(ts)};\n}\n\nLS_INHEADER struct timespec ls_now_timespec(void)\n{\n    struct timespec ts;\n    int rc;\n\n#if (!defined(_POSIX_MONOTONIC_CLOCK)) || (_POSIX_MONOTONIC_CLOCK < 0)\n    // CLOCK_MONOTONIC is not supported at compile-time.\n    rc = clock_gettime(CLOCK_REALTIME, &ts);\n\n#elif _POSIX_MONOTONIC_CLOCK > 0\n    // CLOCK_MONOTONIC is supported both at compile-time and at run-time.\n    rc = clock_gettime(CLOCK_MONOTONIC, &ts);\n\n#else\n    // CLOCK_MONOTONIC is supported at compile-time, but might or might not\n    // be supported at run-time.\n    rc = clock_gettime(CLOCK_MONOTONIC, &ts);\n    if (rc < 0) {\n        rc = clock_gettime(CLOCK_REALTIME, &ts);\n    }\n#endif\n\n    if (rc < 0) {\n        LS_PANIC(\"clock_gettime() failed\");\n    }\n    return ts;\n}\n\nLS_INHEADER LS_TimeStamp ls_now(void)\n{\n    return ls_timespec_to_TS(ls_now_timespec());\n}\n\nLS_INHEADER struct timespec ls_TD_to_timespec(LS_TimeDelta TD)\n{\n    LS_ASSERT(!ls_TD_is_forever(TD));\n\n    double delta = TD.delta;\n    return (struct timespec) {\n        .tv_sec = delta,\n        .tv_nsec = (delta - (time_t) delta) * 1e9,\n    };\n}\n\nLS_INHEADER struct timeval ls_TD_to_timeval(LS_TimeDelta TD)\n{\n    LS_ASSERT(!ls_TD_is_forever(TD));\n\n    double delta = TD.delta;\n    return (struct timeval) {\n        .tv_sec = delta,\n        .tv_usec = (delta - (time_t) delta) * 1e6,\n    };\n}\n\nLS_INHEADER int ls_TD_to_poll_ms_tmo(LS_TimeDelta TD)\n{\n    if (ls_TD_is_forever(TD)) {\n        return -1;\n    }\n    double ms = TD.delta * 1000;\n    return ms > INT_MAX ? INT_MAX : ms;\n}\n\nLS_INHEADER LS_TimeDelta ls_TS_minus_TS_nonneg(LS_TimeStamp a, LS_TimeStamp b)\n{\n    LS_ASSERT(!ls_TS_is_bad(a));\n    LS_ASSERT(!ls_TS_is_bad(b));\n\n    double res = a.ts - b.ts;\n    return (LS_TimeDelta) {res < 0 ? 0 : res};\n}\n\nLS_INHEADER LS_TimeStamp ls_TS_plus_TD(LS_TimeStamp a, LS_TimeDelta b)\n{\n    LS_ASSERT(!ls_TS_is_bad(a));\n    LS_ASSERT(!ls_TD_is_forever(b));\n\n    return (LS_TimeStamp) {a.ts + b.delta};\n}\n\nLS_INHEADER bool ls_TD_less(LS_TimeDelta a, LS_TimeDelta b)\n{\n    if (ls_TD_is_forever(a) && ls_TD_is_forever(b)) {\n        return false;\n    }\n    return a.delta < b.delta;\n}\n\nLS_INHEADER void ls_sleep(LS_TimeDelta TD)\n{\n    if (ls_TD_is_forever(TD)) {\n        for (;;) {\n            pause();\n        }\n    }\n\n    struct timespec ts = ls_TD_to_timespec(TD);\n    struct timespec rem;\n    while (nanosleep(&ts, &rem) < 0 && errno == EINTR) {\n        ts = rem;\n    }\n}\n\nLS_INHEADER void ls_sleep_simple(double seconds)\n{\n    ls_sleep(ls_double_to_TD_or_die(seconds));\n}\n\n#endif\n"
  },
  {
    "path": "libls/ls_tls_ebuf.c",
    "content": "/*\n * Copyright (C) 2015-2025  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#include \"ls_tls_ebuf.h\"\n\nstatic __thread char ebuf[LS_TLS_EBUF_N];\n\nchar *ls_tls_ebuf(void)\n{\n    return ebuf;\n}\n"
  },
  {
    "path": "libls/ls_tls_ebuf.h",
    "content": "/*\n * Copyright (C) 2015-2025  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifndef ls_tls_ebuf_h_\n#define ls_tls_ebuf_h_\n\n#include \"ls_compdep.h\"\n#include \"ls_cstring_utils.h\"\n\n// Thread-local storage buffer. Used mostly as a buffer for /ls_strerror_r/.\n\nenum { LS_TLS_EBUF_N = 512 };\n\nchar *ls_tls_ebuf(void);\n\nLS_INHEADER const char *ls_tls_strerror(int errnum)\n{\n    return ls_strerror_r(errnum, ls_tls_ebuf(), LS_TLS_EBUF_N);\n}\n\n#endif\n"
  },
  {
    "path": "libls/ls_xallocf.c",
    "content": "/*\n * Copyright (C) 2025  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#include \"ls_xallocf.h\"\n#include <stdarg.h>\n#include <stddef.h>\n#include <stdio.h>\n#include <errno.h>\n#include \"ls_alloc_utils.h\"\n#include \"ls_panic.h\"\n\nchar *ls_xallocvf(const char *fmt, va_list vl)\n{\n    va_list vl2;\n    va_copy(vl2, vl);\n\n    int n = vsnprintf(NULL, 0, fmt, vl);\n    if (n < 0) {\n        goto fail;\n    }\n    size_t nbuf = ((size_t) n) + 1;\n    char *r = LS_XNEW(char, nbuf);\n    if (vsnprintf(r, nbuf, fmt, vl2) < 0) {\n        goto fail;\n    }\n    va_end(vl2);\n    return r;\n\nfail:\n    LS_PANIC_WITH_ERRNUM(\"ls_xallocvf: vsnprintf() failed\", errno);\n}\n"
  },
  {
    "path": "libls/ls_xallocf.h",
    "content": "/*\n * Copyright (C) 2025  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifndef ls_xallocf_h_\n#define ls_xallocf_h_\n\n#include <stdarg.h>\n#include \"ls_compdep.h\"\n\n// Functions below allocate a formatted C string (as with with 'malloc()').\n// They panic on failure (including out-of-memory condition, bad format\n// string, etc).\n\nLS_ATTR_PRINTF(1, 0)\nchar *ls_xallocvf(const char *fmt, va_list vl);\n\nLS_INHEADER LS_ATTR_PRINTF(1, 2)\nchar *ls_xallocf(const char *fmt, ...)\n{\n    va_list vl;\n    va_start(vl, fmt);\n    char *r = ls_xallocvf(fmt, vl);\n    va_end(vl);\n    return r;\n}\n\n#endif\n"
  },
  {
    "path": "libmoonvisit/CMakeLists.txt",
    "content": "file (GLOB sources \"*.c\")\nadd_library (moonvisit OBJECT ${sources})\n\ntarget_compile_definitions (moonvisit PUBLIC -D_POSIX_C_SOURCE=200809L)\nluastatus_target_compile_with (moonvisit LUA)\n\nfind_library (MATH_LIBRARY m)\nif (MATH_LIBRARY)\n    target_link_libraries (moonvisit PUBLIC ${MATH_LIBRARY})\nendif ()\n\nset_target_properties (moonvisit PROPERTIES POSITION_INDEPENDENT_CODE ON)\n"
  },
  {
    "path": "libmoonvisit/README.txt",
    "content": "Just a tiny library that helps with traversing complex Lua structures.\n"
  },
  {
    "path": "libmoonvisit/moonvisit.c",
    "content": "/*\n * Copyright (C) 2015-2025  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#include \"moonvisit.h\"\n\n#include <lua.h>\n#include <math.h>\n#include <stdlib.h>\n#include <stdint.h>\n#include <stdbool.h>\n#include <stdio.h>\n#include <string.h>\n#include <stdarg.h>\n\nstatic void oom_handler(void)\n{\n    fputs(\"FATAL ERROR: out of memory (libmoonvisit).\\n\", stderr);\n    abort();\n}\n\n// Copies zero-terminated 'src' into buffer 'dst' of size 'ndst', possibly truncating, but always\n// zero-terminating the output (unless 'ndst' is 0, of course).\n//\n// Returns number of bytes written, not including terminating NUL.\nstatic size_t write_str(char *dst, size_t ndst, const char *src)\n{\n    if (!ndst) {\n        return 0;\n    }\n    size_t ncopy = strnlen(src, ndst - 1);\n    memcpy(dst, src, ncopy);\n    dst[ncopy] = '\\0';\n    return ncopy;\n}\n\nbool moon_visit_err(MoonVisit *mv, const char *fmt, ...)\n{\n    size_t off = 0;\n    if (mv->where) {\n        off += write_str(mv->errbuf + off, mv->nerrbuf - off, mv->where);\n        off += write_str(mv->errbuf + off, mv->nerrbuf - off, \": \");\n    }\n\n    va_list vl;\n    va_start(vl, fmt);\n    int r = vsnprintf(mv->errbuf + off, mv->nerrbuf - off, fmt, vl);\n    va_end(vl);\n\n    if (r < 0) {\n        mv->errbuf[0] = '\\0';\n        return false;\n    }\n    return true;\n}\n\nint moon_visit_checktype_at(\n    MoonVisit *mv,\n    const char *what,\n    int pos,\n    int type)\n{\n    int got_type = lua_type(mv->L, pos);\n    if (got_type != type) {\n        if (what) {\n            moon_visit_err(\n                mv, \"%s: expected %s, found %s\",\n                what,\n                lua_typename(mv->L, type),\n                lua_typename(mv->L, got_type));\n        } else {\n            moon_visit_err(\n                mv, \"expected %s, found %s\",\n                lua_typename(mv->L, type),\n                lua_typename(mv->L, got_type));\n        }\n        return -1;\n    }\n    return 0;\n}\n\nint moon_visit_str_f(\n    MoonVisit *mv,\n    int table_pos,\n    const char *key,\n    int (*f)(MoonVisit *mv, void *ud, const char *s, size_t ns),\n    void *ud,\n    bool skip_nil)\n{\n    int top = lua_gettop(mv->L);\n    int r;\n\n    lua_getfield(mv->L, table_pos, key);\n\n    if (skip_nil && lua_isnil(mv->L, -1)) {\n        r = 0;\n        goto done;\n    }\n\n    if (moon_visit_checktype_at(mv, key, -1, LUA_TSTRING) < 0) {\n        r = -1;\n        goto done;\n    }\n\n    size_t ns;\n    const char *s = lua_tolstring(mv->L, -1, &ns);\n\n    const char *old_where = mv->where;\n    r = f(mv, ud, s, ns);\n    mv->where = old_where;\n\ndone:\n    lua_settop(mv->L, top);\n    return r;\n}\n\nint moon_visit_str(\n    MoonVisit *mv,\n    int table_pos,\n    const char *key,\n    char **ps,\n    size_t *pn,\n    bool skip_nil)\n{\n    int r;\n    lua_getfield(mv->L, table_pos, key);\n    if (skip_nil && lua_isnil(mv->L, -1)) {\n        r = 0;\n        goto done;\n    }\n    if (moon_visit_checktype_at(mv, key, -1, LUA_TSTRING) < 0) {\n        r = -1;\n        goto done;\n    }\n\n    size_t ns;\n    const char *s = lua_tolstring(mv->L, -1, &ns);\n\n    char *buf = malloc(ns + 1);\n    if (!buf) {\n        oom_handler();\n    }\n    memcpy(buf, s, ns + 1);\n\n    *ps = buf;\n    if (pn) {\n        *pn = ns;\n    }\n    r = 1;\n\ndone:\n    lua_pop(mv->L, 1);\n    return r;\n}\n\nint moon_visit_num(\n    MoonVisit *mv,\n    int table_pos,\n    const char *key,\n    double *p,\n    bool skip_nil)\n{\n    int r;\n    lua_getfield(mv->L, table_pos, key);\n    if (skip_nil && lua_isnil(mv->L, -1)) {\n        r = 0;\n        goto done;\n    }\n    if (moon_visit_checktype_at(mv, key, -1, LUA_TNUMBER) < 0) {\n        r = -1;\n        goto done;\n    }\n\n    *p = lua_tonumber(mv->L, -1);\n    r = 1;\n\ndone:\n    lua_pop(mv->L, 1);\n    return r;\n}\n\nint moon_visit_bool(\n    MoonVisit *mv,\n    int table_pos,\n    const char *key,\n    bool *p,\n    bool skip_nil)\n{\n    int r;\n    lua_getfield(mv->L, table_pos, key);\n    if (skip_nil && lua_isnil(mv->L, -1)) {\n        r = 0;\n        goto done;\n    }\n    if (moon_visit_checktype_at(mv, key, -1, LUA_TBOOLEAN) < 0) {\n        r = -1;\n        goto done;\n    }\n\n    *p = lua_toboolean(mv->L, -1);\n    r = 1;\n\ndone:\n    lua_pop(mv->L, 1);\n    return r;\n}\n\nint moon_visit_table_f(\n    MoonVisit *mv,\n    int table_pos,\n    const char *key,\n    int (*f)(MoonVisit *mv, void *ud, int kpos, int vpos),\n    void *ud,\n    bool skip_nil)\n{\n    int r;\n\n    lua_getfield(mv->L, table_pos, key);\n\n    if (skip_nil && lua_isnil(mv->L, -1)) {\n        r = 0;\n        goto done;\n    }\n\n    r = moon_visit_table_f_at(mv, key, -1, f, ud);\n\ndone:\n    lua_pop(mv->L, 1);\n    return r;\n}\n\nstatic inline bool aux_is_first_key_numeric(lua_State *L, int pos)\n{\n    // L: ?\n    int adj_pos = pos < 0 ? (pos - 1) : pos;\n    lua_pushnil(L); // L: ? nil\n    if (!lua_next(L, adj_pos)) {\n        // L: ?\n        return false;\n    }\n    // L: ? key value\n    bool res = lua_isnumber(L, -2);\n    lua_pop(L, 2); // L: ?\n    return res;\n}\n\nstatic inline size_t aux_get_array_len(lua_State *L, int pos)\n{\n#if LUA_VERSION_NUM <= 501\n    return lua_objlen(L, pos);\n#else\n    return lua_rawlen(L, pos);\n#endif\n}\n\nint moon_visit_table_f_at(\n    MoonVisit *mv,\n    const char *what,\n    int pos,\n    int (*f)(MoonVisit *mv, void *ud, int kpos, int vpos),\n    void *ud)\n{\n    int top = lua_gettop(mv->L);\n    int r;\n\n    if (moon_visit_checktype_at(mv, what, pos, LUA_TTABLE) < 0) {\n        r = -1;\n        goto done;\n    }\n\n    r = 0;\n    const char *old_where = mv->where;\n\n    if (aux_is_first_key_numeric(mv->L, pos)) {\n        int adj_pos = pos < 0 ? (pos - 1) : pos;\n        size_t len = aux_get_array_len(mv->L, pos);\n        // L: ?\n        for (size_t i = 1; i <= len; ++i) {\n            lua_pushinteger(mv->L, i); // mv->L: ? i\n            lua_rawgeti(mv->L, adj_pos, i); // mv->L: ? i value\n            r = f(mv, ud, -2, -1);\n            mv->where = old_where;\n            if (r < 0) {\n                break;\n            }\n            lua_pop(mv->L, 2); // mv->L: ?\n        }\n    } else {\n        int adj_pos = pos < 0 ? (pos - 1) : pos;\n        // mv->L: ?\n        lua_pushnil(mv->L); // mv->L: ? nil\n        while (lua_next(mv->L, adj_pos)) {\n            // mv->L: ? key value\n            r = f(mv, ud, -2, -1);\n            mv->where = old_where;\n            if (r < 0) {\n                break;\n            }\n            lua_pop(mv->L, 1); // mv->L: ? key\n        }\n    }\n    lua_settop(mv->L, top);\ndone:\n    return r;\n}\n\nint moon_visit_sint(\n    MoonVisit *mv,\n    int table_pos,\n    const char *key,\n    int64_t *p,\n    bool skip_nil)\n{\n    double d;\n    int r = moon_visit_num(mv, table_pos, key, &d, skip_nil);\n    if (r > 0) {\n        if (isnan(d)) {\n            moon_visit_err(mv, \"%s: is NaN\", key);\n            return -1;\n        }\n        if (!(d >= -9223372036854775808.0 && d < 9223372036854775808.0)) {\n            moon_visit_err(mv, \"%s: value out of range INT64_MIN...INT64_MAX\", key);\n            return -1;\n        }\n        *p = d;\n    }\n    return r;\n}\n\nint moon_visit_uint(\n    MoonVisit *mv,\n    int table_pos,\n    const char *key,\n    uint64_t *p,\n    bool skip_nil)\n{\n    double d;\n    int r = moon_visit_num(mv, table_pos, key, &d, skip_nil);\n    if (r > 0) {\n        if (isnan(d)) {\n            moon_visit_err(mv, \"%s: is NaN\", key);\n            return -1;\n        }\n        if (!(d >= 0.0 && d < 18446744073709551616.0)) {\n            moon_visit_err(mv, \"%s: value out of range 0...UINT64_MAX\", key);\n            return -1;\n        }\n        *p = d;\n    }\n    return r;\n}\n\nint moon_visit_scrutinize_table(\n    MoonVisit *mv,\n    int table_pos,\n    const char *key,\n    bool nil_ok)\n{\n    lua_getfield(mv->L, table_pos, key);\n    int type = lua_type(mv->L, -1);\n    if (type == LUA_TNIL && nil_ok) {\n        return 0;\n    }\n    if (moon_visit_checktype_at(mv, key, -1, LUA_TTABLE) < 0) {\n        lua_pop(mv->L, 1);\n        return -1;\n    }\n    return 0;\n}\n\nint moon_visit_scrutinize_str(\n    MoonVisit *mv,\n    int table_pos,\n    const char *key,\n    const char **out,\n    size_t *out_len,\n    bool nil_ok)\n{\n    lua_getfield(mv->L, table_pos, key);\n    int type = lua_type(mv->L, -1);\n    if (type == LUA_TNIL && nil_ok) {\n        *out = NULL;\n        if (out_len) {\n            *out_len = 0;\n        }\n        return 0;\n    }\n\n    if (moon_visit_checktype_at(mv, key, -1, LUA_TSTRING) < 0) {\n        lua_pop(mv->L, 1);\n        return -1;\n    }\n\n    if (out_len) {\n        *out = lua_tolstring(mv->L, -1, out_len);\n    } else {\n        *out = lua_tostring(mv->L, -1);\n    }\n    return 0;\n}\n"
  },
  {
    "path": "libmoonvisit/moonvisit.h",
    "content": "/*\n * Copyright (C) 2015-2025  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifndef moonvisit_h_\n#define moonvisit_h_\n\n#include <lua.h>\n#include <stddef.h>\n#include <stdbool.h>\n#include <stdint.h>\n\n#if ! defined(MOON_VISIT_PRINTF_ATTR)\n# if __GNUC__ >= 2\n#  define MOON_VISIT_PRINTF_ATTR(N, M) __attribute__((format(printf, N, M)))\n# else\n#  define MOON_VISIT_PRINTF_ATTR(N, M) /*nothing*/\n# endif\n#endif\n\ntypedef struct {\n    // Filled by user, never altered (as a pointer) by library.\n    lua_State *L;\n\n    // Filled by user, never altered (as a pointer) by library.\n    //\n    // On error, this buffer is written to, assuming it has the size of 'nerrbuf', and is always\n    // zero-terminated (unless 'nerrbuf' is 0, of course).\n    char *errbuf;\n\n    // Filled by the user, never altered by library.\n    size_t nerrbuf;\n\n    // Filled by user, but might be altered (as a pointer) by the library in the process of\n    // saving/restoring old value of 'where'.\n    const char *where;\n} MoonVisit;\n\n// Prints a formatted string into 'mv->errbuf' (prepended with \"<where>: \" if 'mv->where' is not\n// NULL).\n//\n// Returns true on success, false on \"encoding error\".\nMOON_VISIT_PRINTF_ATTR(2, 3)\nbool moon_visit_err(MoonVisit *mv, const char *fmt, ...);\n\n// Checks that the element at stack position 'pos' in 'mv->L' has type of 'type'.\n// The fact that 'pos' is a valid stack index is *not* checked, simply assumed to be true.\n//\n// 'type' must be one of LUA_T* constants.\n//\n// 'what' must be string description of the value that is being tested.\n//\n// On success, returns 0.\n//\n// On failure, returns -1 and writes the error message into 'mv->errbuf'.\nint moon_visit_checktype_at(\n    MoonVisit *mv,\n    const char *what,\n    int pos,\n    int type);\n\nint moon_visit_str_f(\n    MoonVisit *mv,\n    int table_pos,\n    const char *key,\n    int (*f)(MoonVisit *mv, void *ud, const char *s, size_t ns),\n    void *ud,\n    bool skip_nil);\n\n// Duplicates the string as if with 'malloc()', writing zero-terminated duplicated string into\n// '*ps'; and, if 'pn' is not NULL, the length into '*pn'.\nint moon_visit_str(\n    MoonVisit *mv,\n    int table_pos,\n    const char *key,\n    char **ps,\n    size_t *pn,\n    bool skip_nil);\n\nint moon_visit_num(\n    MoonVisit *mv,\n    int table_pos,\n    const char *key,\n    double *p,\n    bool skip_nil);\n\nint moon_visit_bool(\n    MoonVisit *mv,\n    int table_pos,\n    const char *key,\n    bool *p,\n    bool skip_nil);\n\nint moon_visit_table_f(\n    MoonVisit *mv,\n    int table_pos,\n    const char *key,\n    int (*f)(MoonVisit *mv, void *ud, int kpos, int vpos),\n    void *ud,\n    bool skip_nil);\n\nint moon_visit_table_f_at(\n    MoonVisit *mv,\n    const char *what,\n    int pos,\n    int (*f)(MoonVisit *mv, void *ud, int kpos, int vpos),\n    void *ud);\n\n// Beware: current implementation can only faithfully (without precision loss) fetch integers that\n// fit into 'double' (on virtually every platform it means integers with absolute value <= 2^53).\nint moon_visit_sint(\n    MoonVisit *mv,\n    int table_pos,\n    const char *key,\n    int64_t *p,\n    bool skip_nil);\n\n// Beware: current implementation can only faithfully (without precision loss) fetch integers that\n// fit into 'double' (on virtually every platform it means values that are <= 2^53).\nint moon_visit_uint(\n    MoonVisit *mv,\n    int table_pos,\n    const char *key,\n    uint64_t *p,\n    bool skip_nil);\n\n// Pushes /t[key]/ on the stack, where /t/ is the table residing at stack position /table_pos/.\n// Checks that the result is a table.\n// If /nil_ok/ is true and the result is nil, pushes a nil instead of a table.\nint moon_visit_scrutinize_table(\n    MoonVisit *mv,\n    int table_pos,\n    const char *key,\n    bool nil_ok);\n\n// Pushes /t[key]/ on the stack, where /t/ is the table residing at stack position /table_pos/.\n//\n// Checks that the result is a string. If so, /*out/ is set to the contents of the string,\n// and, if /out_len != NULL/, writes the length of the string into /*out_len/.\n//\n// If /nil_ok/ is true and the result is nil, pushes a nil instead of a table, sets /*out/ to\n// NULL, and, if /out_len != NULL/, sets /*out_len/ to zero.\nint moon_visit_scrutinize_str(\n    MoonVisit *mv,\n    int table_pos,\n    const char *key,\n    const char **out,\n    size_t *out_len,\n    bool nil_ok);\n\n#endif\n"
  },
  {
    "path": "libprocalive/CMakeLists.txt",
    "content": "file (GLOB sources \"*.c\")\nadd_library (procalive OBJECT ${sources})\n\ntarget_compile_definitions (procalive PUBLIC -D_POSIX_C_SOURCE=200809L)\nluastatus_target_compile_with (procalive LUA)\n\nfind_library (MATH_LIBRARY m)\nif (MATH_LIBRARY)\n    target_link_libraries (procalive PUBLIC ${MATH_LIBRARY})\nendif ()\n\nset_target_properties (procalive PROPERTIES POSITION_INDEPENDENT_CODE ON)\n"
  },
  {
    "path": "libprocalive/procalive_lfuncs.c",
    "content": "/*\n * Copyright (C) 2015-2025  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#include \"procalive_lfuncs.h\"\n#include <lua.h>\n#include <lauxlib.h>\n#include <luaconf.h>\n#include <signal.h>\n#include <stdbool.h>\n#include <limits.h>\n#include <stdlib.h>\n#include <stdint.h>\n#include <inttypes.h>\n#include <string.h>\n#include <unistd.h>\n#include <errno.h>\n#include <math.h>\n#include <glob.h>\n#include <sys/types.h>\n#include <sys/stat.h>\n\n#if _POSIX_C_SOURCE < 200112L || defined(_GNU_SOURCE)\n#   error \"Unsupported feature test macros.\"\n#endif\n\nstatic __thread char errbuf[512];\n\nstatic const char *my_strerror(int errnum)\n{\n    // We introduce an /int/ variable in order to get a compilation warning if /strerror_r()/ is\n    // still GNU-specific and returns a pointer to char.\n    int r = strerror_r(errnum, errbuf, sizeof(errbuf));\n    return r == 0 ? errbuf : \"unknown error or truncated error message\";\n}\n\n#ifdef LUA_MAXINTEGER\n# define MAXI \\\n    (LUA_MAXINTEGER > (SIZE_MAX - 1) ? (SIZE_MAX - 1) : LUA_MAXINTEGER)\n#else\n# define MAXI INT_MAX\n#endif\n\nint procalive_lfunc_access(lua_State *L)\n{\n    const char *path = luaL_checkstring(L, 1);\n\n    if (access(path, F_OK) >= 0) {\n        lua_pushboolean(L, 1);\n        // L: true\n        lua_pushnil(L);\n        // L: true nil\n    } else {\n        int saved_errno = errno;\n        lua_pushboolean(L, 0);\n        // L: false\n        if (saved_errno == ENOENT) {\n            lua_pushnil(L);\n            // L: false nil\n        } else {\n            lua_pushstring(L, my_strerror(saved_errno));\n            // L: false str\n        }\n    }\n    return 2;\n}\n\nint procalive_lfunc_stat(lua_State *L)\n{\n    const char *path = luaL_checkstring(L, 1);\n\n    struct stat sb;\n    if (stat(path, &sb) < 0) {\n        int saved_errno = errno;\n        lua_pushnil(L); // L: nil\n        lua_pushstring(L, my_strerror(saved_errno)); // L: nil err\n        return 2;\n    }\n\n    mode_t M = sb.st_mode;\n    const char *M_str;\n\n    if (S_ISREG(M)) {\n        M_str = \"regular\";\n\n    } else if (S_ISDIR(M)) {\n        M_str = \"dir\";\n\n    } else if (S_ISCHR(M)) {\n        M_str = \"chardev\";\n\n    } else if (S_ISBLK(M)) {\n        M_str = \"blockdev\";\n\n    } else if (S_ISFIFO(M)) {\n        M_str = \"fifo\";\n\n    } else if (S_ISLNK(M)) {\n        M_str = \"symlink\";\n\n    } else if (S_ISSOCK(M)) {\n        M_str = \"socket\";\n\n    } else {\n        M_str = \"other\";\n    }\n\n    lua_pushstring(L, M_str); // L: res\n    lua_pushnil(L); // L: res nil\n    return 2;\n}\n\nstatic inline int get_lua_num_prealloc(size_t n)\n{\n    return n < (size_t) INT_MAX ? (int) n : INT_MAX;\n}\n\nstatic bool push_glob_t(lua_State *L, glob_t *g)\n{\n    size_t n = g->gl_pathc;\n    if (n > (size_t) MAXI) {\n        return false;\n    }\n    lua_createtable(L, get_lua_num_prealloc(n), 0); // L: array\n    for (size_t i = 0; i < n; ++i) {\n        lua_pushstring(L, g->gl_pathv[i]); // L: array str\n        lua_rawseti(L, -2, i + 1); // L: array\n    }\n    return true;\n}\n\nint procalive_lfunc_glob(lua_State *L)\n{\n    const char *pattern = luaL_checkstring(L, 1);\n\n    glob_t g = {0};\n    int rc = glob(pattern, GLOB_MARK | GLOB_NOSORT, NULL, &g);\n    int err_num;\n\n    switch (rc) {\n    case 0:\n        if (!push_glob_t(L, &g)) {\n            err_num = EOVERFLOW;\n            goto fail;\n        }\n        // L: res\n        goto ok;\n\n    case GLOB_NOMATCH:\n        lua_newtable(L); // L: res\n        goto ok;\n\n    case GLOB_ABORTED:\n        err_num = EIO;\n        goto fail;\n\n    case GLOB_NOSPACE:\n        err_num = ENOMEM;\n        goto fail;\n\n    default:\n        err_num = EINVAL;\n        goto fail;\n    }\n\nok:\n    globfree(&g);\n    // L: res\n    lua_pushnil(L); // L: res nil\n    return 2;\n\nfail:\n    globfree(&g);\n    lua_pushnil(L); // L: nil\n    lua_pushstring(L, my_strerror(err_num)); // L: nil err\n    return 2;\n}\n\nstatic pid_t parse_pid(const char *s, const char **out_err_msg)\n{\n    char *endptr;\n    errno = 0;\n    intmax_t mres = strtoimax(s, &endptr, 10);\n\n    if (errno != 0 || *s == '\\0' || *endptr != '\\0') {\n        *out_err_msg = \"cannot parse into intmax_t\";\n        return -1;\n    }\n\n    pid_t res = mres;\n    if (res <= 0 || res != mres) {\n        *out_err_msg = \"outside of valid range for pid_t\";\n        return -1;\n    }\n    return res;\n}\n\nint procalive_lfunc_is_process_alive(lua_State *L)\n{\n    luaL_checkany(L, 1);\n\n    pid_t pid;\n\n    if (lua_isstring(L, 1)) {\n        const char *err_msg;\n        pid = parse_pid(lua_tostring(L, 1), &err_msg);\n        if (pid <= 0) {\n            return luaL_argerror(L, 1, err_msg);\n        }\n    } else if (lua_isnumber(L, 1)) {\n        double d = lua_tonumber(L, 1);\n        if (!isgreaterequal(d, 1.0)) {\n            return luaL_argerror(L, 1, \"must be >= 1\");\n        }\n        if (d > INT_MAX) {\n            return luaL_argerror(L, 1, \"too large\");\n        }\n        int i = (int) d;\n        pid = (pid_t) i;\n        if (pid <= 0 || pid != i) {\n            return luaL_argerror(L, 1, \"outside of valid range for pid_t\");\n        }\n    } else {\n        return luaL_argerror(L, 1, \"neither number nor string\");\n    }\n\n    bool res = (kill(pid, 0) >= 0 || errno == EPERM);\n\n    lua_pushboolean(L, res); // L: res\n    return 1;\n}\n\nvoid procalive_lfuncs_register_all(lua_State *L)\n{\n    // L: table\n\n    lua_pushcfunction(L, procalive_lfunc_access); // L: table func\n    lua_setfield(L, -2, \"access\"); // L: table\n\n    lua_pushcfunction(L, procalive_lfunc_stat); // L: table func\n    lua_setfield(L, -2, \"stat\"); // L: table\n\n    lua_pushcfunction(L, procalive_lfunc_glob); // L: table func\n    lua_setfield(L, -2, \"glob\"); // L: table\n\n    lua_pushcfunction(L, procalive_lfunc_is_process_alive); // L: table func\n    lua_setfield(L, -2, \"is_process_alive\"); // L: table\n}\n"
  },
  {
    "path": "libprocalive/procalive_lfuncs.h",
    "content": "/*\n * Copyright (C) 2015-2025  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifndef libprocalive_procalive_lfuncs_h_\n#define libprocalive_procalive_lfuncs_h_\n\n#include <lua.h>\n\n// Lua function access(path):\n//     Checks if a given path exists, as if with 'access(path, F_OK)'. If it does\n//     exist, returns 'true, nil'. If it does not, returns 'false, nil'. If an error\n//     occurs, returns 'false, err_msg'.\nint procalive_lfunc_access(lua_State *L);\n\n// Lua function stat(path):\n//     Tries to get the type of the file at the given path. On success returns either of:\n//     * \"regular\",\n//     * \"dir\" (directory),\n//     * \"chardev\" (character device),\n//     * \"blockdev\" (block device),\n//     * \"fifo\",\n//     * \"symlink\",\n//     * \"socket\",\n//     * \"other\".\n// On failure returns '`'nil, err_msg'.\nint procalive_lfunc_stat(lua_State *L);\n\n// Lua function glob(pattern):\n//     Performs glob expansion of pattern. A glob is a wildcard pattern like /tmp/*.txt\n//     that can be applied as a filter to a list of existing file names. Supported\n//     expansions are *, ? and [...]. Please refer to glob(7) for more information on\n//     wildcard patterns.\n//\n//     Note also that the globbing is performed with GLOB_MARK flag, so that in output,\n//     directories have trailing slash appended to their name.\n//\n//     Returns 'arr, nil' on success, where 'arr' is an array of strings; these are\n//     existing file names that matched the given pattern. The order is arbitrary.\n//\n//     On failure, returns 'nil, err_msg'.\nint procalive_lfunc_glob(lua_State *L);\n\n// Lua function is_process_alive(pid):\n//     Checks if a process with PID 'pid' is currently alive. 'pid' must be either a\n//     or a string.\n//     Returns a boolean that indicates whether the process is alive.\nint procalive_lfunc_is_process_alive(lua_State *L);\n\n// Registers all functions described above into a table on the top of /L/'s stack.\nvoid procalive_lfuncs_register_all(lua_State *L);\n\n#endif\n"
  },
  {
    "path": "librunshell/CMakeLists.txt",
    "content": "file (GLOB sources \"*.c\")\nadd_library (runshell OBJECT ${sources})\n\ntarget_compile_definitions (runshell PUBLIC -D_POSIX_C_SOURCE=200809L)\nluastatus_target_compile_with (runshell LUA)\n\nset_target_properties (runshell PROPERTIES POSITION_INDEPENDENT_CODE ON)\n"
  },
  {
    "path": "librunshell/README.txt",
    "content": "A thread-safe (modulo thread cancellation) version of system().\nIt also does not modify SIGQUIT/SIGINT signal dispositions.\n"
  },
  {
    "path": "librunshell/runshell.c",
    "content": "/*\n * Copyright (C) 2025  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#include \"runshell.h\"\n#include <stdlib.h>\n#include <stdbool.h>\n#include <string.h>\n#include <stdio.h>\n#include <signal.h>\n#include <spawn.h>\n#include <errno.h>\n#include <sys/wait.h>\n#include <sys/types.h>\n#include <lua.h>\n#include <lauxlib.h>\n\n#if LUA_VERSION_NUM >= 504\n#   define my_pushfail(L_) luaL_pushfail(L_)\n#else\n#   define my_pushfail(L_) lua_pushnil(L_)\n#endif\n\n#define CANNOT_FAIL(Expr_) \\\n    do { \\\n        if ((Expr_) < 0) { \\\n            perror(\"librunshell: \" #Expr_ \" failed\"); \\\n            abort(); \\\n        } \\\n    } while (0)\n\n#define CANNOT_FAIL_PTH(Expr_) \\\n    do { \\\n        if ((errno = (Expr_)) != 0) { \\\n            perror(\"librunshell: \" #Expr_ \" failed\"); \\\n            abort(); \\\n        } \\\n    } while (0)\n\nstatic __thread char errbuf[512];\n\n#if _POSIX_C_SOURCE < 200112L || defined(_GNU_SOURCE)\n#   error \"Unsupported feature test macros.\"\n#endif\n\nstatic __thread char errbuf[512];\n\nstatic const char *my_strerror(int errnum)\n{\n    // We introduce an /int/ variable in order to get a compilation warning if /strerror_r()/ is\n    // still GNU-specific and returns a pointer to char.\n    int r = strerror_r(errnum, errbuf, sizeof(errbuf));\n    return r == 0 ? errbuf : \"unknown error or truncated error message\";\n}\n\nextern char **environ;\n\nint runshell(const char *cmd)\n{\n    if (!cmd) {\n        fputs(\"librunshell: passed cmd == NULL (this is not supported)\\n\", stderr);\n        abort();\n    }\n\n    sigset_t ss_new;\n    sigset_t ss_old;\n    CANNOT_FAIL(sigemptyset(&ss_new));\n    CANNOT_FAIL(sigaddset(&ss_new, SIGCHLD));\n    CANNOT_FAIL(sigprocmask(SIG_BLOCK, &ss_new, &ss_old));\n\n    posix_spawnattr_t attr;\n    CANNOT_FAIL_PTH(posix_spawnattr_init(&attr));\n    CANNOT_FAIL_PTH(posix_spawnattr_setsigmask(&attr, &ss_old));\n    CANNOT_FAIL_PTH(posix_spawnattr_setflags(&attr, POSIX_SPAWN_SETSIGMASK));\n\n    char *const argv[] = {\n        (char *) \"sh\",\n        (char *) \"-c\",\n        (char *) cmd,\n        NULL,\n    };\n    pid_t pid;\n    int rc = posix_spawn(\n        /*pid=*/ &pid,\n        /*path=*/ \"/bin/sh\",\n        /*file_actions=*/ NULL,\n        /*attrp=*/ &attr,\n        /*argv=*/ argv,\n        /*envp=*/ environ);\n\n    CANNOT_FAIL_PTH(posix_spawnattr_destroy(&attr));\n\n    int ret;\n    int saved_errno;\n    if (rc == 0) {\n        saved_errno = 0;\n        while (waitpid(pid, &ret, 0) < 0) {\n            if (errno != EINTR) {\n                ret = -1;\n                saved_errno = errno;\n                break;\n            }\n        }\n    } else {\n        ret = -1;\n        saved_errno = rc;\n    }\n\n    CANNOT_FAIL(sigprocmask(SIG_SETMASK, &ss_old, NULL));\n\n    errno = saved_errno;\n    return ret;\n}\n\nint runshell_l_os_execute_lua51ver(lua_State *L)\n{\n    const char *cmd = luaL_optstring(L, 1, NULL);\n    // L: ?\n    if (!cmd) {\n        lua_pushinteger(L, 1); // L: ? 1\n        return 1;\n    }\n    lua_pushinteger(L, runshell(cmd)); // L: ? code\n    return 1;\n}\n\nint runshell_l_os_execute(lua_State *L)\n{\n    const char *cmd = luaL_optstring(L, 1, NULL);\n    // L: ?\n    if (!cmd) {\n        lua_pushboolean(L, 1); // L: ? true\n        return 1;\n    }\n    int rc = runshell(cmd);\n    if (rc < 0) {\n        int saved_errno = errno;\n        my_pushfail(L); // L: ? fail\n        lua_pushstring(L, my_strerror(saved_errno)); // L: ? fail err_msg\n        lua_pushinteger(L, saved_errno); // L: ? fail err_msg errno\n        return 3;\n    }\n    int code;\n    bool normal_exit = true;\n    if (WIFEXITED(rc)) {\n        code = WEXITSTATUS(rc);\n    } else if (WIFSIGNALED(rc)) {\n        code = WTERMSIG(rc);\n        normal_exit = false;\n    } else {\n        code = rc;\n    }\n    if (normal_exit && code == 0) {\n        lua_pushboolean(L, 1); // L: ? true\n    } else {\n        my_pushfail(L); // L: ? fail\n    }\n    lua_pushstring(L, normal_exit ? \"exit\" : \"signal\"); // L: ? is_ok what\n    lua_pushinteger(L, code); // L: ? is_ok what code\n    return 3;\n}\n"
  },
  {
    "path": "librunshell/runshell.h",
    "content": "/*\n * Copyright (C) 2025  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifndef luastatus_runshell_h_\n#define luastatus_runshell_h_\n\n#include <lua.h>\n\n// This function is like /system()/, but:\n//\n//   1. Does not support /cmd == NULL/: on a POSIX system, /system(NULL)/ must\n//      return non-zero anyway;\n//\n//   2. Assumes no thread cancellation takes place, and is not a cancellation\n//      point (nobody is going to cancel luastatus' widget threads);\n//\n//   3. Does not modify signal dispositions for SIGINT and/or SIGQUIT (we are\n//      not going to modify disposition for these signals anyway);\n//\n//   4. Is otherwise thread-safe (POSIX does not guarantee thread-safety of\n//      /system()/, and, on musl, it is not thread-safe).\nint runshell(const char *cmd);\n\nint runshell_l_os_execute(lua_State *L);\n\nint runshell_l_os_execute_lua51ver(lua_State *L);\n\n#endif\n"
  },
  {
    "path": "libsafe/CMakeLists.txt",
    "content": "file (GLOB sources \"*.c\")\nadd_library (safe OBJECT ${sources})\n\ntarget_compile_definitions (safe PUBLIC -D_POSIX_C_SOURCE=200809L)\n\nset_target_properties (safe PROPERTIES POSITION_INDEPENDENT_CODE ON)\n"
  },
  {
    "path": "libsafe/mut_safev.h",
    "content": "/*\n * Copyright (C) 2015-2025  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifndef libsafe_mut_safev_h_\n#define libsafe_mut_safev_h_\n\n#include \"safe_common.h\"\n#include <stddef.h>\n#include \"safev.h\"\n\ntypedef struct {\n    char *mut_s__;\n    size_t mut_n__;\n} MUT_SAFEV;\n\nLIBSAFE_INHEADER MUT_SAFEV MUT_SAFEV_new_empty(void)\n{\n    return (MUT_SAFEV) {.mut_s__ = NULL, .mut_n__ = 0};\n}\n\nLIBSAFE_INHEADER MUT_SAFEV MUT_SAFEV_new_UNSAFE(char *buf, size_t nbuf)\n{\n    return (MUT_SAFEV) {\n        .mut_s__ = buf,\n        .mut_n__ = nbuf,\n    };\n}\n\n#define MUT_SAVEF_STATIC_INIT_UNSAFE(Ptr_, Len_) \\\n    { \\\n        .mut_s__ = (Ptr_), \\\n        .mut_n__ = (Len_), \\\n    }\n\nLIBSAFE_INHEADER void MUT_SAFEV_set_at(MUT_SAFEV Mv, size_t i, char c)\n{\n    LIBSAFE_ASSERT(i < Mv.mut_n__);\n    Mv.mut_s__[i] = c;\n}\n\nLIBSAFE_INHEADER SAFEV MUT_SAFEV_TO_SAFEV(MUT_SAFEV Mv)\n{\n    return SAFEV_new_UNSAFE(Mv.mut_s__, Mv.mut_n__);\n}\n\n#endif\n"
  },
  {
    "path": "libsafe/safe_common.c",
    "content": "/*\n * Copyright (C) 2015-2025  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#include \"safe_common.h\"\n#include <stdio.h>\n#include <stdlib.h>\n\nvoid libsafe_assert_failed__(\n    const char *expr,\n    const char *func,\n    const char *file,\n    int line)\n{\n    fprintf(\n        stderr, \"LIBSAFE_ASSERT(%s) failed in %s at %s:%d.\\n\",\n        expr, func, file, line);\n    abort();\n}\n"
  },
  {
    "path": "libsafe/safe_common.h",
    "content": "/*\n * Copyright (C) 2015-2025  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifndef libsafe_safe_common_h_\n#define libsafe_safe_common_h_\n\n#if __GNUC__ >= 2\n#   define LIBSAFE_ATTR_UNUSED           __attribute__((unused))\n#   define LIBSAFE_ATTR_NORETURN         __attribute__((noreturn))\n#else\n#   define LIBSAFE_ATTR_UNUSED           /*nothing*/\n#   define LIBSAFE_ATTR_NORETURN         /*nothing*/\n#endif\n\nLIBSAFE_ATTR_NORETURN void libsafe_assert_failed__(\n    const char *expr,\n    const char *func,\n    const char *file,\n    int line);\n\n#define LIBSAFE_INHEADER static inline LIBSAFE_ATTR_UNUSED\n\n#define LIBSAFE_ASSERT(Expr_) \\\n    do { \\\n        if (!(Expr_)) { \\\n            libsafe_assert_failed__(#Expr_, __func__, __FILE__, __LINE__); \\\n        } \\\n    } while (0)\n\n#endif\n"
  },
  {
    "path": "libsafe/safev.h",
    "content": "/*\n * Copyright (C) 2015-2025  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifndef libsafe_safev_h_\n#define libsafe_safev_h_\n\n#include \"safe_common.h\"\n#include <stddef.h>\n#include <string.h>\n#include <stdbool.h>\n\ntypedef struct {\n    const char *s__;\n    size_t n__;\n} SAFEV;\n\n// Constructs an empty view.\nLIBSAFE_INHEADER SAFEV SAFEV_new_empty(void)\n{\n    return (SAFEV) {.s__ = NULL, .n__ = 0};\n}\n\n// Constructs a view of a buffer by pointer and length.\nLIBSAFE_INHEADER SAFEV SAFEV_new_UNSAFE(const char *ptr, size_t n)\n{\n    return (SAFEV) {\n        .s__ = ptr,\n        .n__ = n,\n    };\n}\n\n// Constructs a view of a C string.\nLIBSAFE_INHEADER SAFEV SAFEV_new_from_cstr_UNSAFE(const char *cstr)\n{\n    LIBSAFE_ASSERT(cstr != NULL);\n    return (SAFEV) {\n        .s__ = cstr,\n        .n__ = strlen(cstr),\n    };\n}\n\n#define SAFEV_new_from_literal(StrLit_) SAFEV_new_from_cstr_UNSAFE(\"\" StrLit_)\n\n// A static initializer for SAFEV (from a pointer and length).\n#define SAFEV_STATIC_INIT_UNSAFE(Ptr_, Len_) {.s__ = (Ptr_), .n__ = (Len_)}\n\n// A static initializer for SAFEV (from a string literal).\n#define SAFEV_STATIC_INIT_FROM_LITERAL(StrLit_) \\\n    { \\\n        .s__ = \"\" StrLit_, \\\n        .n__ = sizeof(StrLit_) - 1, \\\n    }\n\n// Peeks at a byte of a view.\nLIBSAFE_INHEADER char SAFEV_at(SAFEV v, size_t i)\n{\n    LIBSAFE_ASSERT(i < v.n__);\n    return v.s__[i];\n}\n\n// Peeks at a byte of a view, or, if index is invalid, return 'alt'.\nLIBSAFE_INHEADER char SAFEV_at_or(SAFEV v, size_t i, char alt)\n{\n    if (i >= v.n__) {\n        return alt;\n    }\n    return v.s__[i];\n}\n\n// Searches a view for a first occurence of character 'c'.\n// If found, returns the index.\n// If not found, returns (size_t) -1.\nLIBSAFE_INHEADER size_t SAFEV_index_of(SAFEV v, unsigned char c)\n{\n    const char *pos = v.n__ ? memchr(v.s__, c, v.n__) : NULL;\n    return pos ? (size_t) (pos - v.s__) : (size_t) -1;\n}\n\n// Checks if a view 'v' starts with a view 'prefix'.\nLIBSAFE_INHEADER bool SAFEV_starts_with(SAFEV v, SAFEV prefix)\n{\n    if (prefix.n__ > v.n__) {\n        return false;\n    }\n    if (!prefix.n__) {\n        return true;\n    }\n    return memcmp(prefix.s__, v.s__, prefix.n__) == 0;\n}\n\n// Checks if a view 'v' equals to a view 'v1'.\nLIBSAFE_INHEADER bool SAFEV_equals(SAFEV v, SAFEV v1)\n{\n    if (v.n__ != v1.n__) {\n        return false;\n    }\n    if (!v.n__) {\n        return true;\n    }\n    return memcmp(v.s__, v1.s__, v.n__) == 0;\n}\n\n// Constructs a subview of a view; 'i' is the starting index (inclusive), 'j' is the\n// ending index (exclusive).\nLIBSAFE_INHEADER SAFEV SAFEV_subspan(SAFEV v, size_t i, size_t j)\n{\n    LIBSAFE_ASSERT(i <= j);\n    LIBSAFE_ASSERT(j <= v.n__);\n    return (SAFEV) {\n        .s__ = v.s__ + i,\n        .n__ = j - i,\n    };\n}\n\n// Equivalent to 'SAFEV_subspan(v, from_idx, SAFEV_len(v))'.\nLIBSAFE_INHEADER SAFEV SAFEV_suffix(SAFEV v, size_t from_idx)\n{\n    LIBSAFE_ASSERT(from_idx <= v.n__);\n    return (SAFEV) {\n        .s__ = v.s__ + from_idx,\n        .n__ = v.n__ - from_idx,\n    };\n}\n\n// Returns the pointer of a view.\nLIBSAFE_INHEADER const char *SAFEV_ptr_UNSAFE(SAFEV v)\n{\n    return v.s__;\n}\n\n// Returns the length of a view.\nLIBSAFE_INHEADER size_t SAFEV_len(SAFEV v)\n{\n    return v.n__;\n}\n\n// If 'v' ends with 'c', strips it off; otherwise, returns it unchanged.\nLIBSAFE_INHEADER SAFEV SAFEV_rstrip_once(SAFEV v, char c)\n{\n    if (!v.n__) {\n        return v;\n    }\n    if (SAFEV_at(v, v.n__ - 1) != c) {\n        return v;\n    }\n    return SAFEV_subspan(v, 0, v.n__ - 1);\n}\n\n// Returns min(i, SAFEV_len(v)).\nLIBSAFE_INHEADER size_t SAFEV_trim_to_len(SAFEV v, size_t i)\n{\n    if (i > v.n__) {\n        i = v.n__;\n    }\n    return i;\n}\n\n// This is needed from 'SAFEV_FMT_ARG': returns\n//   min(bound, SAFEV_len(v)),\n// assuming bound >= 0.\nLIBSAFE_INHEADER int SAFEV_bounded_len(SAFEV v, int bound)\n{\n    LIBSAFE_ASSERT(bound >= 0);\n    size_t res = v.n__;\n    if (res > (unsigned) bound) {\n        res = bound;\n    }\n    return res;\n}\n\n// Print a SAFEV as\n//\n//   void print_sv(SAFEV v)\n//   {\n//     printf(\"v = '%.*s'\\n\", SAFEV_FMT_ARG(v, 1024));\n//   }\n#define SAFEV_FMT_ARG(V_, MaxLen_) \\\n    SAFEV_bounded_len((V_), (MaxLen_)), \\\n    (V_).s__\n\n#endif\n"
  },
  {
    "path": "libwidechar/CMakeLists.txt",
    "content": "file (GLOB sources \"*.c\")\nadd_library (widechar OBJECT ${sources})\n\ninclude (CheckSymbolExists)\nset (CMAKE_REQUIRED_DEFINITIONS \"-D_XOPEN_SOURCE=500\")\ncheck_symbol_exists (wcwidth \"wchar.h\" LUASTATUS_HAVE_WCWIDTH)\nconfigure_file (\"config.in.h\" \"config.generated.h\")\n\ntarget_compile_definitions (widechar PUBLIC -D_POSIX_C_SOURCE=200809L)\nluastatus_target_compile_with (widechar LUA)\n\nfind_library (MATH_LIBRARY m)\nif (MATH_LIBRARY)\n    target_link_libraries (widechar PUBLIC ${MATH_LIBRARY})\nendif ()\n\nset_target_properties (widechar PROPERTIES POSITION_INDEPENDENT_CODE ON)\n\ntarget_include_directories (widechar PUBLIC \"${PROJECT_SOURCE_DIR}\" \"${CMAKE_CURRENT_BINARY_DIR}\")\n"
  },
  {
    "path": "libwidechar/README.txt",
    "content": "Implementation of luastatus.libwidechar module.\nIt contains functions working on wchar_t strings to:\n  1. Determine the width of a string;\n  2. Truncate a string to a given width;\n  3. Convert a byte string into a valid string of printable wchar_t's,\n     replacing illegal sequences or unprintable characters with a given\n     placeholder.\n"
  },
  {
    "path": "libwidechar/config.in.h",
    "content": "#ifndef luastatus_libwidechar_config_h_\n#define luastatus_libwidechar_config_h_\n\n#cmakedefine01 LUASTATUS_HAVE_WCWIDTH\n\n#endif\n"
  },
  {
    "path": "libwidechar/libwidechar.c",
    "content": "/*\n * Copyright (C) 2025  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#define _XOPEN_SOURCE 500\n\n#include \"libwidechar.h\"\n\n#include <stddef.h>\n#include <stdint.h>\n#include <stdbool.h>\n#include <math.h>\n#include <wchar.h>\n#include <locale.h>\n\n#include <lua.h>\n#include <lauxlib.h>\n\n#include \"libwidechar_xspan.h\"\n#include \"config.generated.h\"\n\n#if LUASTATUS_HAVE_WCWIDTH\n\nstatic inline int my_wcwidth(wchar_t c) { return wcwidth(c); }\nenum { IS_DUMMY_IMPLEMENTATION = 0 };\n\n#else\n\nstatic inline int my_wcwidth(wchar_t c) { return c == L'\\0' ? 0 : 1; }\nenum { IS_DUMMY_IMPLEMENTATION = 1 };\n\n#endif\n\n#if defined(__NetBSD__)\n#define uselocale(x) ((locale_t) 0)\n#endif\n\nstatic inline bool next(mbstate_t *state, xspan x, xspan *new_x, wchar_t *out)\n{\n    SAFEV unproc_v = xspan_unprocessed_v(x);\n\n    size_t rc = mbrtowc(out, SAFEV_ptr_UNSAFE(unproc_v), SAFEV_len(unproc_v), state);\n    if ((rc == (size_t) -1) || (rc == (size_t) -2)) {\n        return false;\n    }\n    size_t n = rc == 0 ? 1 : rc;\n    *new_x = x;\n    xspan_advance(new_x, n);\n    return true;\n}\n\nint libwidechar_width(SAFEV v, uint64_t *out_width)\n{\n    xspan x = v_to_xspan(v);\n    uint64_t width = 0;\n    mbstate_t state = {0};\n    while (xspan_nonempty(x)) {\n        wchar_t c;\n        if (!next(&state, x, &x, &c)) {\n            return -1;\n        }\n        int w = my_wcwidth(c);\n        if (w < 0 || c == L'\\0') {\n            return -1;\n        }\n        width += w;\n    }\n    *out_width = width;\n    return 0;\n}\n\nsize_t libwidechar_truncate_to_width(\n        SAFEV v,\n        uint64_t max_width,\n        uint64_t *out_resut_width)\n{\n    xspan x = v_to_xspan(v);\n    mbstate_t state = {0};\n    uint64_t width = 0;\n    while (xspan_nonempty(x)) {\n        xspan new_x;\n        wchar_t c;\n        if (!next(&state, x, &new_x, &c)) {\n            return -1;\n        }\n\n        int w = my_wcwidth(c);\n        if (w < 0 || c == L'\\0') {\n            return -1;\n        }\n\n        width += w;\n        if (width > max_width) {\n            width -= w;\n            break;\n        }\n        x = new_x;\n    }\n\n    *out_resut_width = width;\n    return xspan_processed_len(x);\n}\n\nvoid libwidechar_make_valid_and_printable(\n        SAFEV v,\n        SAFEV bad,\n        void (*append)(void *ud, SAFEV segment),\n        void *append_ud)\n{\n    xspan x = v_to_xspan(v);\n    mbstate_t state = {0};\n\n    while (xspan_nonempty(x)) {\n        xspan new_x;\n        wchar_t c;\n        if (next(&state, x, &new_x, &c)) {\n            if (my_wcwidth(c) >= 0 && c != L'\\0') {\n                // this wchar_t is good\n                x = new_x;\n            } else {\n                // valid, but non-printable\n                append(append_ud, xspan_processed_v(x));\n                append(append_ud, bad);\n                x = xspan_skip_processed(new_x);\n            }\n        } else {\n            // invalid\n            append(append_ud, xspan_processed_v(x));\n            append(append_ud, bad);\n            xspan_advance(&x, 1);\n            x = xspan_skip_processed(x);\n        }\n    }\n    append(append_ud, xspan_processed_v(x));\n}\n\ntypedef struct {\n    locale_t native;\n    locale_t old;\n} LocaleSavedData;\n\nstatic inline LocaleSavedData begin_locale(lua_State *L)\n{\n    LocaleSavedData lsd = {0};\n    const char *err_msg;\n\n    lsd.native = newlocale(LC_ALL_MASK, \"\", (locale_t) 0);\n    if (lsd.native == (locale_t) 0) {\n        err_msg = \"begin_locale: newlocale() failed\";\n        goto fail;\n    }\n    lsd.old = uselocale(lsd.native);\n    if (lsd.old == (locale_t) 0) {\n        err_msg = \"begin_locale: uselocale() failed\";\n        goto fail;\n    }\n\n    return lsd;\n\nfail:\n    if (lsd.native != (locale_t) 0) {\n        freelocale(lsd.native);\n    }\n    luaL_error(L, \"%s\", err_msg);\n    // unreachable\n    return (LocaleSavedData) {0};\n}\n\nstatic inline void end_locale(LocaleSavedData lsd, lua_State *L)\n{\n    if (uselocale(lsd.old) == (locale_t) 0) {\n        luaL_error(L, \"end_locale: uselocale() failed\");\n    }\n    freelocale(lsd.native);\n}\n\nstatic inline SAFEV v_from_lua_string(lua_State *L, int pos)\n{\n    size_t ns;\n    const char *s = lua_tolstring(L, pos, &ns);\n    return SAFEV_new_UNSAFE(s, ns);\n}\n\nstatic inline uint64_t nonneg_double_to_u64(double d)\n{\n    if (d >= 18446744073709551616.0) {\n        return -1;\n    }\n    return d;\n}\n\nstatic inline size_t nonneg_double_to_size_t(double d)\n{\n    static const double LIMIT =\n#if SIZE_MAX > UINT32_MAX\n        9223372036854775808.0 // 2^63\n#else\n        SIZE_MAX\n#endif\n    ;\n    if (d > LIMIT) {\n        return -1;\n    }\n    return d;\n}\n\nstatic size_t extract_ij(lua_State *L, int pos, size_t if_absent)\n{\n    double d = luaL_optnumber(L, pos, -1);\n    if (isgreaterequal(d, 0.0)) {\n        return nonneg_double_to_size_t(d);\n    }\n    return if_absent;\n}\n\nstatic SAFEV extract_string_with_ij(lua_State *L, int str_pos, int i_pos)\n{\n    SAFEV v = v_from_lua_string(L, str_pos);\n\n    size_t i = extract_ij(L, i_pos,     1);\n    size_t j = extract_ij(L, i_pos + 1, SIZE_MAX);\n    if (!i) {\n        luaL_argerror(L, i_pos, \"is zero (expected 1-based index)\");\n        // unreachable\n        return (SAFEV) {0};\n    }\n\n    // convert one-based index into zero-based\n    --i;\n    // 'j' doesn't need to be converted because in Lua function, it is *inclusive*\n    // index, but is instead used as an *exclusive* in the code below.\n\n    i = SAFEV_trim_to_len(v, i);\n    j = SAFEV_trim_to_len(v, j);\n\n    if (j < i) {\n        j = i;\n    }\n\n    return SAFEV_subspan(v, i, j);\n}\n\nstatic int lfunc_width(lua_State *L)\n{\n    SAFEV v = extract_string_with_ij(L, 1, 2);\n\n    LocaleSavedData lsd = begin_locale(L);\n    uint64_t width;\n    int rc = libwidechar_width(v, &width);\n    end_locale(lsd, L);\n\n    // L: ?\n    if (rc < 0) {\n        lua_pushnil(L); // L: ? nil\n    } else {\n        lua_pushnumber(L, width); // ? L: width\n    }\n    return 1;\n}\n\nstatic int lfunc_truncate_to_width(lua_State *L)\n{\n    SAFEV v = extract_string_with_ij(L, 1, 3);\n\n    double d_max_width = luaL_checknumber(L, 2);\n    if (!isgreaterequal(d_max_width, 0.0)) {\n        return luaL_argerror(L, 2, \"negative or NaN\");\n    }\n    uint64_t max_width = nonneg_double_to_u64(d_max_width);\n\n    LocaleSavedData lsd = begin_locale(L);\n    uint64_t res_width;\n    size_t res_len = libwidechar_truncate_to_width(v, max_width, &res_width);\n    end_locale(lsd, L);\n\n    if (res_len == (size_t) -1) {\n        lua_pushnil(L); // L: nil\n        lua_pushnil(L); // L: nil nil\n    } else {\n        SAFEV res = SAFEV_subspan(v, 0, res_len);\n        lua_pushlstring(L, SAFEV_ptr_UNSAFE(res), SAFEV_len(res)); // L: result\n        lua_pushnumber(L, res_width); // L: result result_width\n    }\n\n    return 2;\n}\n\nstatic void append_to_lua_buf_callback(void *ud, SAFEV segment)\n{\n    luaL_Buffer *b = ud;\n    luaL_addlstring(b, SAFEV_ptr_UNSAFE(segment), SAFEV_len(segment));\n}\n\nstatic int lfunc_make_valid_and_printable(lua_State *L)\n{\n    SAFEV v = extract_string_with_ij(L, 1, 3);\n\n    SAFEV bad = v_from_lua_string(L, 2);\n\n    luaL_Buffer b;\n    luaL_buffinit(L, &b);\n\n    LocaleSavedData lsd = begin_locale(L);\n    libwidechar_make_valid_and_printable(v, bad, append_to_lua_buf_callback, &b);\n    end_locale(lsd, L);\n\n    luaL_pushresult(&b); // L: result\n\n    return 1;\n}\n\nstatic int lfunc_is_dummy_implementation(lua_State *L)\n{\n    lua_pushboolean(L, IS_DUMMY_IMPLEMENTATION); // L: bool\n    return 1;\n}\n\nvoid libwidechar_register_lua_funcs(lua_State *L)\n{\n    (void) L;\n\n#if ! defined(__NetBSD__)\n    // L: table\n    lua_pushcfunction(L, lfunc_width); // L: table func\n    lua_setfield(L, -2, \"width\"); // L: table\n\n    lua_pushcfunction(L, lfunc_truncate_to_width); // L: table func\n    lua_setfield(L, -2, \"truncate_to_width\"); // L: table\n\n    lua_pushcfunction(L, lfunc_make_valid_and_printable); // L: table func\n    lua_setfield(L, -2, \"make_valid_and_printable\"); // L: table\n\n    lua_pushcfunction(L, lfunc_is_dummy_implementation); // L: table func\n    lua_setfield(L, -2, \"is_dummy_implementation\"); // L: table\n#endif\n}\n"
  },
  {
    "path": "libwidechar/libwidechar.h",
    "content": "/*\n * Copyright (C) 2025  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifndef libwidechar_h_\n#define libwidechar_h_\n\n#include <stddef.h>\n#include <stdint.h>\n#include <lua.h>\n#include \"libsafe/safev.h\"\n\nint libwidechar_width(SAFEV v, uint64_t *out_width);\n\nsize_t libwidechar_truncate_to_width(\n        SAFEV v,\n        uint64_t max_width,\n        uint64_t *out_resut_width);\n\nvoid libwidechar_make_valid_and_printable(\n        SAFEV v,\n        SAFEV bad,\n        void (*append)(void *ud, SAFEV segment),\n        void *append_ud);\n\nvoid libwidechar_register_lua_funcs(lua_State *L);\n\n#endif\n"
  },
  {
    "path": "libwidechar/libwidechar_compdep.h",
    "content": "/*\n * Copyright (C) 2025  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifndef libwidechar_compdep_h_\n#define libwidechar_compdep_h_\n\n#if __GNUC__ >= 2\n#   define LIBWIDECHAR_UNUSED __attribute__((unused))\n#else\n#   define LIBWIDECHAR_UNUSED /*nothing*/\n#endif\n\n#define LIBWIDECHAR_INHEADER static inline LIBWIDECHAR_UNUSED\n\n#endif\n"
  },
  {
    "path": "libwidechar/libwidechar_xspan.h",
    "content": "/*\n * Copyright (C) 2025  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifndef libwidechar_xspan_h_\n#define libwidechar_xspan_h_\n\n#include <stddef.h>\n#include <stdbool.h>\n#include \"libsafe/safev.h\"\n#include \"libwidechar_compdep.h\"\n\ntypedef struct {\n    SAFEV v;\n    size_t cur;\n} xspan;\n\nLIBWIDECHAR_INHEADER xspan v_to_xspan(SAFEV v)\n{\n    return (xspan) {\n        .v  = v,\n        .cur = 0,\n    };\n}\n\nLIBWIDECHAR_INHEADER bool xspan_nonempty(xspan x)\n{\n    return x.cur != SAFEV_len(x.v);\n}\n\nLIBWIDECHAR_INHEADER void xspan_advance(xspan *x, size_t n)\n{\n    x->cur += n;\n}\n\nLIBWIDECHAR_INHEADER size_t xspan_processed_len(xspan x)\n{\n    return x.cur;\n}\n\nLIBWIDECHAR_INHEADER SAFEV xspan_processed_v(xspan x)\n{\n    return SAFEV_subspan(x.v, 0, x.cur);\n}\n\nLIBWIDECHAR_INHEADER SAFEV xspan_unprocessed_v(xspan x)\n{\n    return SAFEV_suffix(x.v, x.cur);\n}\n\nLIBWIDECHAR_INHEADER xspan xspan_skip_processed(xspan x)\n{\n    return (xspan) {\n        .v = xspan_unprocessed_v(x),\n        .cur = 0,\n    };\n}\n\n#endif\n"
  },
  {
    "path": "plugins/alsa/CMakeLists.txt",
    "content": "file (GLOB sources \"*.c\")\nluastatus_add_plugin (plugin-alsa $<TARGET_OBJECTS:ls> $<TARGET_OBJECTS:moonvisit> ${sources})\n\ntarget_compile_definitions (plugin-alsa PUBLIC -D_POSIX_C_SOURCE=200809L)\nluastatus_target_compile_with (plugin-alsa LUA)\ntarget_include_directories (plugin-alsa PUBLIC \"${PROJECT_SOURCE_DIR}\")\n\nfind_package (PkgConfig REQUIRED)\npkg_check_modules (ALSA REQUIRED alsa)\nluastatus_target_build_with (plugin-alsa ALSA)\n\nluastatus_add_man_page (README.rst luastatus-plugin-alsa 7)\n"
  },
  {
    "path": "plugins/alsa/README.rst",
    "content": ".. :X-man-page-only: luastatus-plugin-alsa\n.. :X-man-page-only: #####################\n.. :X-man-page-only:\n.. :X-man-page-only: #########################\n.. :X-man-page-only: ALSA plugin for luastatus\n.. :X-man-page-only: #########################\n.. :X-man-page-only:\n.. :X-man-page-only: :Copyright: LGPLv3\n.. :X-man-page-only: :Manual section: 7\n\nOverview\n========\nThis plugin monitors volume and mute state of an ALSA channel.\n\nOptions\n========\nThe following options are supported:\n\n* ``card``: string\n\n  Card name, defaults to ``\"default\"``.\n\n* ``channel``: string\n\n  Channel name, defaults to ``\"Master\"``.\n\n* ``in_db``: boolean\n\n  Whether or not to report normalized volume (in dBs).\n\n* ``capture``: boolean\n\n  Whether or not this is a capture stream, as opposed to a playback one. Defaults to false.\n\n* ``timeout``: number\n\n  If specified and not negative, this plugin will call ``cb`` with ``nil`` argument whenever the\n  channel does not change its state in ``timeout`` seconds since the previous call to ``cb``.\n\n* ``make_self_pipe``: boolean\n\n  If true, the ``wake_up()`` (see the `Functions`_ section) function will be available. Defaults to\n  false.\n\n``cb`` argument\n===============\nOn timeout, ``nil`` (if the ``timeout`` option has been specified).\n\nOtherwise, the argument is a table with the following entries:\n\n* ``mute``: boolean\n\n  Whether or not the playback switch is turned off.\n\n  (Only provided if the channel has the playback switch capability.)\n\n* ``vol``: table with the following entries:\n\n  * ``cur``, ``min``, ``max``: numbers\n\n    Current, minimal and maximal volume levels, correspondingly.\n\n    (Only provided if the channel has the volume control capability.)\n\nFunctions\n=========\nThe following functions are provided:\n\n* ``luastatus.plugin.wake_up()``\n\n  Forces a call to ``cb``.\n\n  Only available if the ``make_self_pipe`` option was set to ``true``; otherwise, it throws an\n  error.\n"
  },
  {
    "path": "plugins/alsa/alsa.c",
    "content": "/*\n * Copyright (C) 2015-2025  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#include <lua.h>\n#include <alsa/asoundlib.h>\n#include <errno.h>\n#include <stdbool.h>\n#include <stdlib.h>\n#include <string.h>\n#include <unistd.h>\n#include <stdio.h>\n#include <sys/types.h>\n#include <poll.h>\n\n#include \"include/plugin_v1.h\"\n#include \"include/sayf_macros.h\"\n\n#include \"libmoonvisit/moonvisit.h\"\n\n#include \"libls/ls_alloc_utils.h\"\n#include \"libls/ls_tls_ebuf.h\"\n#include \"libls/ls_evloop_lfuncs.h\"\n#include \"libls/ls_time_utils.h\"\n#include \"libls/ls_io_utils.h\"\n\ntypedef struct {\n    char *card;\n    char *channel;\n    bool capture;\n    bool in_db;\n    double tmo;\n    int pipefds[2];\n} Priv;\n\nstatic void destroy(LuastatusPluginData *pd)\n{\n    Priv *p = pd->priv;\n    free(p->card);\n    free(p->channel);\n    ls_close(p->pipefds[0]);\n    ls_close(p->pipefds[1]);\n    free(p);\n}\n\nstatic int init(LuastatusPluginData *pd, lua_State *L)\n{\n    Priv *p = pd->priv = LS_XNEW(Priv, 1);\n    *p = (Priv) {\n        .card = NULL,\n        .channel = NULL,\n        .capture = false,\n        .in_db = false,\n        .tmo = -1,\n        .pipefds = {-1, -1},\n    };\n    char errbuf[256];\n    MoonVisit mv = {.L = L, .errbuf = errbuf, .nerrbuf = sizeof(errbuf)};\n\n    // Parse card\n    if (moon_visit_str(&mv, -1, \"card\", &p->card, NULL, true) < 0)\n        goto mverror;\n    if (!p->card)\n        p->card = ls_xstrdup(\"default\");\n\n    // Parse channel\n    if (moon_visit_str(&mv, -1, \"channel\", &p->channel, NULL, true) < 0)\n        goto mverror;\n    if (!p->channel)\n        p->channel = ls_xstrdup(\"Master\");\n\n    // Parse capture\n    if (moon_visit_bool(&mv, -1, \"capture\", &p->capture, true) < 0)\n        goto mverror;\n\n    // Parse in_db\n    if (moon_visit_bool(&mv, -1, \"in_db\", &p->in_db, true) < 0)\n        goto mverror;\n\n    // Parse timeout\n    if (moon_visit_num(&mv, -1, \"timeout\", &p->tmo, true) < 0)\n        goto mverror;\n\n    // Parse make_self_pipe\n    bool mkpipe = false;\n    if (moon_visit_bool(&mv, -1, \"make_self_pipe\", &mkpipe, true) < 0)\n        goto mverror;\n    if (mkpipe) {\n        if (ls_self_pipe_open(p->pipefds) < 0) {\n            LS_FATALF(pd, \"ls_self_pipe_open: %s\", ls_tls_strerror(errno));\n            goto error;\n        }\n    }\n\n    return LUASTATUS_OK;\n\nmverror:\n    LS_FATALF(pd, \"%s\", errbuf);\nerror:\n    destroy(pd);\n    return LUASTATUS_ERR;\n}\n\nstatic void register_funcs(LuastatusPluginData *pd, lua_State *L)\n{\n    Priv *p = pd->priv;\n    // L: table\n    ls_self_pipe_push_luafunc(p->pipefds, L); // L: table func\n    lua_setfield(L, -2, \"wake_up\"); // L: table\n}\n\nstatic bool card_has_nicename(\n        const char *realname,\n        snd_ctl_card_info_t *info,\n        const char *nicename)\n{\n    snd_ctl_t *ctl;\n    if (snd_ctl_open(&ctl, realname, 0) < 0)\n        return false;\n\n    bool r = false;\n    if (snd_ctl_card_info(ctl, info) >= 0) {\n        const char *name = snd_ctl_card_info_get_name(info);\n        r = (strcmp(name, nicename) == 0);\n    }\n\n    snd_ctl_close(ctl);\n    return r;\n}\n\nstatic char *xalloc_card_realname(const char *nicename)\n{\n    snd_ctl_card_info_t *info;\n    if (snd_ctl_card_info_malloc(&info) < 0)\n        ls_oom();\n\n    enum { NBUF = 32 };\n    char *buf = LS_XNEW(char, NBUF);\n\n    int rcard = -1;\n    while (snd_card_next(&rcard) >= 0 && rcard >= 0) {\n        snprintf(buf, NBUF, \"hw:%d\", rcard);\n        if (card_has_nicename(buf, info, nicename))\n            goto cleanup;\n    }\n\n    free(buf);\n    buf = NULL;\n\ncleanup:\n    snd_ctl_card_info_free(info);\n    return buf;\n}\n\ntypedef struct {\n    int (*get_range)(snd_mixer_elem_t *, long *, long *);\n    int (*get_cur)(snd_mixer_elem_t *, snd_mixer_selem_channel_id_t, long *);\n    int (*get_switch)(snd_mixer_elem_t *, snd_mixer_selem_channel_id_t, int *);\n} GetVolFuncs;\n\nstatic GetVolFuncs select_gv_funcs(bool capture, bool in_db)\n{\n    return (GetVolFuncs) {\n        .get_range =\n            capture ? (in_db ? snd_mixer_selem_get_capture_dB_range\n                             : snd_mixer_selem_get_capture_volume_range)\n                    : (in_db ? snd_mixer_selem_get_playback_dB_range\n                             : snd_mixer_selem_get_playback_volume_range),\n        .get_cur =\n            capture ? (in_db ? snd_mixer_selem_get_capture_dB\n                             : snd_mixer_selem_get_capture_volume)\n                    : (in_db ? snd_mixer_selem_get_playback_dB\n                             : snd_mixer_selem_get_playback_volume),\n        .get_switch =\n            capture ? snd_mixer_selem_get_capture_switch\n                    : snd_mixer_selem_get_playback_switch,\n    };\n}\n\nstatic void push_vol_info(lua_State *L, snd_mixer_elem_t *elem, GetVolFuncs gv_funcs)\n{\n    lua_createtable(L, 0, 2); // L: table\n\n    lua_createtable(L, 0, 3); // L: table table\n    long pmin, pmax;\n    if (gv_funcs.get_range(elem, &pmin, &pmax) >= 0) {\n        lua_pushnumber(L, pmin); // L: table table pmin\n        lua_setfield(L, -2, \"min\"); // L: table table\n        lua_pushnumber(L, pmax); // L: table table pmax\n        lua_setfield(L, -2, \"max\"); // L: table table\n    }\n    long pcur;\n    if (gv_funcs.get_cur(elem, 0, &pcur) >= 0) {\n        lua_pushnumber(L, pcur); // L: table table pcur\n        lua_setfield(L, -2, \"cur\"); // L: table table\n    }\n    lua_setfield(L, -2, \"vol\"); // L: table\n\n    int notmute;\n    if (gv_funcs.get_switch(elem, 0, &notmute) >= 0) {\n        lua_pushboolean(L, !notmute); // L: table !notmute\n        lua_setfield(L, -2, \"mute\"); // L: table\n    }\n    // L: table\n}\n\ntypedef struct {\n    struct pollfd *data;\n    size_t size;\n    size_t nprefix;\n} PollFdSet;\n\nstatic inline PollFdSet pollfd_set_new(struct pollfd prefix)\n{\n    if (prefix.fd >= 0) {\n        return (PollFdSet) {\n            .data = LS_M_XMEMDUP(&prefix, 1),\n            .size = 1,\n            .nprefix = 1,\n        };\n    } else {\n        return (PollFdSet) {\n            .data = NULL,\n            .size = 0,\n            .nprefix = 0,\n        };\n    }\n}\n\nstatic inline void pollfd_set_resize(PollFdSet *s, size_t n)\n{\n    if (s->size != n) {\n        s->data = LS_M_XREALLOC(s->data, n);\n        s->size = n;\n    }\n}\n\nstatic inline void pollfd_set_free(PollFdSet s)\n{\n    free(s.data);\n}\n\nstatic bool iteration(LuastatusPluginData *pd, LuastatusPluginRunFuncs funcs)\n{\n    Priv *p = pd->priv;\n    bool ret = false;\n\n    snd_mixer_t *mixer = NULL;\n    snd_mixer_selem_id_t *sid = NULL;\n    char *realname = NULL;\n    PollFdSet pollfds = pollfd_set_new((struct pollfd) {\n        .fd = p->pipefds[0],\n        .events = POLLIN,\n    });\n\n    if (!(realname = xalloc_card_realname(p->card))) {\n        realname = ls_xstrdup(p->card);\n    }\n\n    int r;\n    if ((r = snd_mixer_open(&mixer, 0)) < 0) {\n        LS_FATALF(pd, \"snd_mixer_open: %s\", snd_strerror(r));\n        goto error;\n    }\n    if ((r = snd_mixer_attach(mixer, realname)) < 0) {\n        LS_FATALF(pd, \"snd_mixer_attach: %s\", snd_strerror(r));\n        goto error;\n    }\n    if ((r = snd_mixer_selem_register(mixer, NULL, NULL)) < 0) {\n        LS_FATALF(pd, \"snd_mixer_selem_register: %s\", snd_strerror(r));\n        goto error;\n    }\n    if ((r = snd_mixer_load(mixer)) < 0) {\n        LS_FATALF(pd, \"snd_mixer_load: %s\", snd_strerror(r));\n        goto error;\n    }\n    if ((r = snd_mixer_selem_id_malloc(&sid)) < 0) {\n        LS_FATALF(pd, \"snd_mixer_selem_id_malloc: %s\", snd_strerror(r));\n        goto error;\n    }\n    snd_mixer_selem_id_set_name(sid, p->channel);\n    snd_mixer_elem_t *elem = snd_mixer_find_selem(mixer, sid);\n    if (!elem) {\n        LS_FATALF(pd, \"cannot find channel '%s'\", p->channel);\n        goto error;\n    }\n\n    ret = true;\n\n    LS_TimeDelta tmo = ls_double_to_TD(p->tmo, LS_TD_FOREVER);\n    GetVolFuncs gv_funcs = select_gv_funcs(p->capture, p->in_db);\n    bool is_timeout = false;\n    while (1) {\n        lua_State *L = funcs.call_begin(pd->userdata);\n        if (is_timeout) {\n            lua_pushnil(L); // L: nil\n        } else {\n            push_vol_info(L, elem, gv_funcs); // L: table\n        }\n        funcs.call_end(pd->userdata);\n\n        int nextrafds = snd_mixer_poll_descriptors_count(mixer);\n        pollfd_set_resize(&pollfds, pollfds.nprefix + nextrafds);\n\n        if ((r = snd_mixer_poll_descriptors(\n                mixer,\n                pollfds.data + pollfds.nprefix,\n                pollfds.size - pollfds.nprefix)) < 0)\n        {\n            LS_FATALF(pd, \"snd_mixer_poll_descriptors: %s\", snd_strerror(r));\n            goto error;\n        }\n\n        int nfds = ls_poll(pollfds.data, pollfds.size, tmo);\n        if (nfds < 0) {\n            LS_FATALF(pd, \"poll: %s\", ls_tls_strerror(errno));\n            goto error;\n\n        } else if (nfds == 0) {\n            is_timeout = true;\n\n        } else {\n            is_timeout = false;\n\n            if (pollfds.nprefix && (pollfds.data[0].revents & POLLIN)) {\n                char c;\n                ssize_t unused = read(p->pipefds[0], &c, 1);\n                (void) unused;\n            }\n        }\n\n        unsigned short revents;\n        if ((r = snd_mixer_poll_descriptors_revents(\n                mixer,\n                pollfds.data + pollfds.nprefix,\n                pollfds.size - pollfds.nprefix,\n                &revents)) < 0)\n        {\n            LS_FATALF(pd, \"snd_mixer_poll_descriptors_revents: %s\", snd_strerror(r));\n            goto error;\n        }\n\n        if (revents & (POLLERR | POLLNVAL)) {\n            LS_FATALF(pd, \"snd_mixer_poll_descriptors_revents() reported error condition\");\n            goto error;\n        }\n        if (revents & POLLIN) {\n            if ((r = snd_mixer_handle_events(mixer)) < 0) {\n                LS_FATALF(pd, \"snd_mixer_handle_events: %s\", snd_strerror(r));\n                goto error;\n            }\n        }\n    }\nerror:\n    if (sid) {\n        snd_mixer_selem_id_free(sid);\n    }\n    if (mixer) {\n        snd_mixer_close(mixer);\n    }\n    free(realname);\n    pollfd_set_free(pollfds);\n    return ret;\n}\n\nstatic void run(LuastatusPluginData *pd, LuastatusPluginRunFuncs funcs)\n{\n    for (;;) {\n        if (!iteration(pd, funcs)) {\n            ls_sleep_simple(5.0);\n        }\n    }\n}\n\nLuastatusPluginIface luastatus_plugin_iface_v1 = {\n    .init = init,\n    .register_funcs = register_funcs,\n    .run = run,\n    .destroy = destroy,\n};\n"
  },
  {
    "path": "plugins/backlight-linux/CMakeLists.txt",
    "content": "install (FILES backlight-linux.lua DESTINATION ${LUA_PLUGINS_DIR})\n\nluastatus_add_man_page (README.rst luastatus-plugin-backlight-linux 7)\n"
  },
  {
    "path": "plugins/backlight-linux/README.rst",
    "content": ".. :X-man-page-only: luastatus-plugin-backlight-linux\n.. :X-man-page-only: ################################\n.. :X-man-page-only:\n.. :X-man-page-only: #############################################\n.. :X-man-page-only: Linux-specific backlight plugin for luastatus\n.. :X-man-page-only: #############################################\n.. :X-man-page-only:\n.. :X-man-page-only: :Copyright: LGPLv3\n.. :X-man-page-only: :Manual section: 7\n\nOverview\n========\nThis derived plugin shows the display backlight level when it changes.\n\nFunctions\n=========\nThe following functions are provided:\n\n* ``widget(tbl)``\n\n  Constructs a ``widget`` table required by luastatus. ``tbl`` is a table with\n  the following fields:\n\n  **(required)**\n\n  - ``cb``: function\n\n    The callback that will be called with a display backlight level (a number from 0 to 1), or\n    with ``nil``.\n\n  **(optional)**\n\n  - ``syspath``: string\n\n    Path to the device directory, e.g.::\n\n        /sys/devices/pci0000:00/0000:00:02.0/drm/card0/card0-eDP-1/intel_backlight\n\n  - ``timeout``: number\n\n    Backlight information is hidden after the timeout in seconds (default is 2).\n\n  - ``event``\n\n    The ``event`` entry of the resulting table (see ``luastatus`` documentation for the\n    description of ``widget.event`` field).\n"
  },
  {
    "path": "plugins/backlight-linux/backlight-linux.lua",
    "content": "--[[\n  Copyright (C) 2015-2025  luastatus developers\n\n  This file is part of luastatus.\n\n  luastatus is free software: you can redistribute it and/or modify\n  it under the terms of the GNU Lesser General Public License as published by\n  the Free Software Foundation, either version 3 of the License, or\n  (at your option) any later version.\n\n  luastatus is distributed in the hope that it will be useful,\n  but WITHOUT ANY WARRANTY; without even the implied warranty of\n  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n  GNU Lesser General Public License for more details.\n\n  You should have received a copy of the GNU Lesser General Public License\n  along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n--]]\n\nlocal P = {}\n\nlocal function read_num(path)\n    local f = assert(io.open(path, 'r'))\n    local r = f:read('*number')\n    f:close()\n    return r\nend\n\nfunction P.widget(tbl)\n    local timeout = tbl.timeout or 2\n    return {\n        plugin = 'udev',\n        opts = {\n            subsystem = 'backlight',\n        },\n        cb = function(t)\n            if t.what ~= 'event' then\n                return tbl.cb(nil)\n            end\n            local s = t.syspath\n            if tbl.syspath and tbl.syspath ~= s then\n                return tbl.cb(nil)\n            end\n\n            luastatus.plugin.push_timeout(timeout)\n\n            local b = read_num(s .. '/brightness')\n            local mb = read_num(s .. '/max_brightness')\n            if (not b) or (not mb) then\n                return tbl.cb(nil)\n            end\n            return tbl.cb(b / mb)\n        end,\n        event = tbl.event,\n    }\nend\n\nreturn P\n"
  },
  {
    "path": "plugins/battery-linux/CMakeLists.txt",
    "content": "install (FILES battery-linux.lua DESTINATION ${LUA_PLUGINS_DIR})\n\nluastatus_add_man_page (README.rst luastatus-plugin-battery-linux 7)\n"
  },
  {
    "path": "plugins/battery-linux/README.rst",
    "content": ".. :X-man-page-only: luastatus-plugin-battery-linux\n.. :X-man-page-only: ##############################\n.. :X-man-page-only:\n.. :X-man-page-only: ###########################################\n.. :X-man-page-only: Linux-specific battery plugin for luastatus\n.. :X-man-page-only: ###########################################\n.. :X-man-page-only:\n.. :X-man-page-only: :Copyright: LGPLv3\n.. :X-man-page-only: :Manual section: 7\n\nOverview\n========\nThis derived plugin periodically polls Linux ``sysfs`` for the state of a battery.\n\nIt is also able to estimate the time remaining to full charge/discharge and\nthe current battery consumption rate.\n\nFunctions\n=========\nThe following functions are provided:\n\n* ``widget(tbl)``\n\n  Constructs a ``widget`` table required by luastatus. ``tbl`` is a table with the following\n  fields:\n\n  **(required)**\n\n  - ``cb``: function\n\n    The callback that will be called with a table with the following keys:\n\n    + ``status``: string\n\n      A string with battery status text, e.g. ``\"Full\"``, ``\"Unknown\"``, ``\"Charging\"``, ``\"Discharging\"``.\n\n      Not present if the status cannot be read (for example, when the battery is missing).\n\n    + ``capacity``: number\n\n      A percentage representing battery capacity.\n\n      Not present if the capacity cannot be read.\n\n    + ``rem_time``: number\n\n      Time (in hours) remaining to full charge/discharge.\n\n      Usually only present on battery charge/discharge.\n\n    + ``consumption``: number\n\n      The current battery consumption/charge rate in watts.\n\n      Usually only present on battery charge/discharge.\n\n    **(optional)**\n\n    - ``dev``: string\n\n      Device directory name under ``/sys/class/power_supply``; default is ``\"BAT0\"``.\n\n    - ``period``: number\n\n      The period in seconds for polling ``sysfs``; default is 2 seconds.\n\n      Because this plugin utilizes ``udev`` under the hood, it is able to respond to\n      battery state changes (such as cable (un-)plugging) immediately, irrespective of\n      the value of the period.\n\n    - ``use_energy_full_design``: boolean\n\n      If ``true``, the ``energy_full_design`` property (not ``energy_full``) will be used for\n      capacity calculation.\n\n    - ``event``\n\n      The ``event`` entry of the resulting table (see ``luastatus`` documentation for the\n      description of ``widget.event`` field).\n"
  },
  {
    "path": "plugins/battery-linux/battery-linux.lua",
    "content": "--[[\n  Copyright (C) 2015-2025  luastatus developers\n\n  This file is part of luastatus.\n\n  luastatus is free software: you can redistribute it and/or modify\n  it under the terms of the GNU Lesser General Public License as published by\n  the Free Software Foundation, either version 3 of the License, or\n  (at your option) any later version.\n\n  luastatus is distributed in the hope that it will be useful,\n  but WITHOUT ANY WARRANTY; without even the implied warranty of\n  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n  GNU Lesser General Public License for more details.\n\n  You should have received a copy of the GNU Lesser General Public License\n  along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n--]]\n\nlocal P = {}\n\nlocal function read_uevent(devpath)\n    local f = io.open(devpath .. '/uevent', 'r')\n    if not f then\n        return nil\n    end\n    local r = {}\n    for line in f:lines() do\n        local key, value = line:match('POWER_SUPPLY_(.-)=(.*)')\n        if key then\n            r[key:lower()] = value\n        end\n    end\n    f:close()\n    return r\nend\n\nlocal function get_battery_info(devpath, use_energy_full_design)\n    local p = read_uevent(devpath)\n    if not p then\n        return {}\n    end\n\n    -- Convert amperes to watts.\n    if p.charge_full then\n        p.energy_full = p.charge_full * p.voltage_now / 1e6\n        p.energy_now = p.charge_now * p.voltage_now / 1e6\n        -- Some drivers don't report power_now\n        if p.current_now ~= nil then\n            p.power_now = p.current_now * p.voltage_now / 1e6\n        end\n    end\n\n    local ef = use_energy_full_design and p.energy_full_design or p.energy_full\n    -- A buggy driver can report energy_now as energy_full_design, which\n    -- will lead to an overshoot in capacity.\n    local capacity = math.min(math.floor(p.energy_now / ef * 100 + 0.5), 100)\n\n    local r = {status = p.status, capacity = capacity}\n\n    local pn = tonumber(p.power_now)\n    if pn ~= nil and pn ~= 0 then\n        pn = math.abs(pn)\n        r.consumption = pn / 1e6\n        if p.status == 'Charging' then\n            r.rem_time = (p.energy_full - p.energy_now) / pn\n        elseif p.status == 'Discharging' or p.status == 'Not charging' then\n            r.rem_time = p.energy_now / pn\n        end\n    end\n\n    return r\nend\n\nfunction P.widget(tbl)\n    local devpath\n    if tbl._devpath then\n        devpath = tbl._devpath\n    else\n        local dev = tbl.dev or 'BAT0'\n        devpath = '/sys/class/power_supply/' .. dev\n    end\n    local period = tbl.period or 2\n    return {\n        plugin = 'udev',\n        opts = {\n            subsystem = 'power_supply',\n            timeout = period,\n            greet = true\n        },\n        cb = function()\n            return tbl.cb(get_battery_info(devpath, tbl.use_energy_full_design))\n        end,\n        event = tbl.event,\n    }\nend\n\nreturn P\n"
  },
  {
    "path": "plugins/cpu-freq-linux/CMakeLists.txt",
    "content": "install (FILES cpu-freq-linux.lua DESTINATION ${LUA_PLUGINS_DIR})\n\nluastatus_add_man_page (README.rst luastatus-plugin-cpu-freq-linux 7)\n"
  },
  {
    "path": "plugins/cpu-freq-linux/README.rst",
    "content": ".. :X-man-page-only: luastatus-plugin-cpu-freq-linux\n.. :X-man-page-only: ###############################\n.. :X-man-page-only:\n.. :X-man-page-only: #################################################\n.. :X-man-page-only: Linux-specific CPU frequency plugin for luastatus\n.. :X-man-page-only: #################################################\n.. :X-man-page-only:\n.. :X-man-page-only: :Copyright: LGPLv3\n.. :X-man-page-only: :Manual section: 7\n\nOverview\n========\nThis derived plugin periodically polls Linux ``sysfs`` for the current\nfrequency of each of the CPUs.\n\nFunctions\n=========\nThe following functions are provided:\n\n* ``get_freqs(data)``\n\n  Returns either an array or nil.\n  If the result is nil, no data is currently available (most likely reason for this\n  is that the set of plugged-in CPUs has changed; if so, the subsequent calls will\n  return non-nil).\n\n  Each array entry corresponds to a subsequent CPU; it is a table with the following fields:\n\n  * ``min`` (number): minimal frequency of this CPU;\n  * ``max`` (number): maximal frequency of this CPU;\n  * ``cur`` (number): current frequency of this CPU.\n\n  All values are in KHz.\n\n  In order to use this function, you are expected to maintain a table ``data``, initially empty,\n  and pass it to ``get_freqs`` each time. The only thing the caller is allowed to do with this\n  table is to set ``please_reload`` field to ``true``. If ``please_reload`` field is true when\n  this function is called, the function will forcefully reload all information and reset\n  ``please_reload`` field to ``nil``.\n\n  The array is sorted by CPU index.\n\n* ``widget(tbl[, data])``\n\n  Constructs a ``widget`` table required by luastatus.\n\n  If ``data`` is specified, it must be an empty table; you can set ``please_reload`` field\n  in this table to force a full reload. On each reported event, before a call to ``tbl.cb``,\n  this field is reset to ``nil``.\n\n  ``tbl`` is a table with the following fields:\n\n  **(required)**\n\n  - ``cb``: a function\n\n    The callback function that will be called with current CPU frequencies, or with ``nil``.\n    The argument is the same as the return value of ``get_freqs`` (see above for description\n    of ``get_freqs`` function for more information).\n\n  **(optional)**\n\n  - ``timer_opts``: table\n\n    Options for the underlying ``timer`` plugin.\n\n  - ``event``\n\n    The ``event`` entry of the resulting table (see ``luastatus`` documentation for the\n    description of ``widget.event`` field).\n"
  },
  {
    "path": "plugins/cpu-freq-linux/cpu-freq-linux.lua",
    "content": "--[[\n  Copyright (C) 2025  luastatus developers\n\n  This file is part of luastatus.\n\n  luastatus is free software: you can redistribute it and/or modify\n  it under the terms of the GNU Lesser General Public License as published by\n  the Free Software Foundation, either version 3 of the License, or\n  (at your option) any later version.\n\n  luastatus is distributed in the hope that it will be useful,\n  but WITHOUT ANY WARRANTY; without even the implied warranty of\n  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n  GNU Lesser General Public License for more details.\n\n  You should have received a copy of the GNU Lesser General Public License\n  along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n--]]\n\n-- Sorts an array of strings by the first digit group.\nlocal function numeric_sort(tbl)\n    local function extract_first_number(s)\n        return tonumber(s:match('[0-9]+') or '-1')\n    end\n    table.sort(tbl, function(a, b)\n        return extract_first_number(a) < extract_first_number(b)\n    end)\nend\n\n-- Escapes given cpu_dir for POSIX shell.\nlocal function escape_cpu_dir(cpu_dir)\n    if cpu_dir:find('\\0') then\n        error('_cpu_dir contains NUL character')\n    end\n    local res, _ = cpu_dir:gsub([[']], [['\\'']])\n    return string.format([['%s']], res)\nend\n\n-- Fetches list of all CPU paths and assigns to 'data.paths'.\nlocal function data_fetch_paths(data)\n    local paths = {}\n    local cpu_dir\n    local cpu_dir_escaped\n    if data._cpu_dir then\n        cpu_dir = data._cpu_dir\n        cpu_dir_escaped = escape_cpu_dir(cpu_dir)\n    else\n        cpu_dir = '/sys/devices/system/cpu'\n        cpu_dir_escaped = cpu_dir\n    end\n    local f = assert(io.popen([[\ncd ]] .. cpu_dir_escaped .. [[ || exit $?\nfor x in cpu[0-9]*/; do\n    [ -d \"$x\" ] || break\n    printf '%s\\n' \"$x\"\ndone\n    ]]))\n    for p in f:lines() do\n        table.insert(paths, string.format('%s/%s', cpu_dir, p))\n    end\n    f:close()\n\n    numeric_sort(paths)\n\n    data.paths = paths\nend\n\n-- Reads a number from \"${path}/${suffix}\" for each CPU path ${path} in\n-- array 'data.paths'.\n-- Returns an array of numbers on success, nil on failure.\nlocal function data_read_from_all_paths(data, suffix)\n    assert(data.paths)\n    local r = {}\n    for _, path in ipairs(data.paths) do\n        local f = io.open(path .. suffix, 'r')\n        if not f then\n            return nil\n        end\n        local val = f:read('*number')\n        f:close()\n        if not val then\n            return nil\n        end\n        assert(val > 0, 'reported frequency is zero or negative')\n        r[#r + 1] = val\n    end\n    return r\nend\n\n-- Returns a boolean indicating whether all required fields ('data.paths',\n-- 'data.max_freqs', 'data.min_freqs') are available.\nlocal function data_is_ready(data)\n    return (data.paths and data.max_freqs and data.min_freqs) ~= nil\nend\n\n-- Tries to load information and set all required fields ('data.paths',\n-- 'data.max_freqs', 'data.min_freqs').\n-- Returns true on success, false on failure.\nlocal function data_reload_all(data)\n    data.paths = nil\n    data.max_freqs = nil\n    data.min_freqs = nil\n\n    data_fetch_paths(data)\n\n    local my_max_freqs = data_read_from_all_paths(data, '/cpufreq/cpuinfo_max_freq')\n    local my_min_freqs = data_read_from_all_paths(data, '/cpufreq/cpuinfo_min_freq')\n    if (not my_max_freqs) or (not my_min_freqs) then\n        return false\n    end\n    for i = 1, #data.paths do\n        if my_max_freqs[i] < my_min_freqs[i] then\n            return false\n        end\n    end\n\n    data.max_freqs, data.min_freqs = my_max_freqs, my_min_freqs\n    return true\nend\n\nlocal P = {}\n\nfunction P.get_freqs(data)\n    if (data.please_reload) or (not data_is_ready(data)) then\n        if not data_reload_all(data) then\n            return nil\n        end\n        data.please_reload = nil\n    end\n    local cur_freqs = data_read_from_all_paths(data, '/cpufreq/scaling_cur_freq')\n    if not cur_freqs then\n        data_reload_all(data)\n        return nil\n    end\n    local r = {}\n    for i, f_cur_raw in ipairs(cur_freqs) do\n        local f_max = data.max_freqs[i]\n        local f_min = data.min_freqs[i]\n        local f_cur = math.max(math.min(f_cur_raw, f_max), f_min)\n        r[#r + 1] = {max = f_max, min = f_min, cur = f_cur}\n    end\n    return r\nend\n\nfunction P.widget(tbl, data)\n    data = data or {}\n    return {\n        plugin = 'timer',\n        opts = tbl.timer_opts,\n        cb = function(_)\n            return tbl.cb(P.get_freqs(data))\n        end,\n        event = tbl.event,\n    }\nend\n\nreturn P\n"
  },
  {
    "path": "plugins/cpu-usage-linux/CMakeLists.txt",
    "content": "install (FILES cpu-usage-linux.lua DESTINATION ${LUA_PLUGINS_DIR})\n\nluastatus_add_man_page (README.rst luastatus-plugin-cpu-usage-linux 7)\n"
  },
  {
    "path": "plugins/cpu-usage-linux/README.rst",
    "content": ".. :X-man-page-only: luastatus-plugin-cpu-usage-linux\n.. :X-man-page-only: ################################\n.. :X-man-page-only:\n.. :X-man-page-only: #############################################\n.. :X-man-page-only: Linux-specific CPU usage plugin for luastatus\n.. :X-man-page-only: #############################################\n.. :X-man-page-only:\n.. :X-man-page-only: :Copyright: LGPLv3\n.. :X-man-page-only: :Manual section: 7\n\nOverview\n========\nThis derived plugin periodically polls Linux ``sysfs`` for the rate of CPU usage.\n\nFunctions\n=========\nThe following functions are provided:\n\n* ``get_usage(cur, prev[, cpu])``\n\n  Returns current CPU usage rate, or ``nil`` if the amount of data collected is insufficient yet.\n\n  ``cpu``, if passed and not ``nil``, specifies the CPU number, starting with 1. Otherwise,\n  average rate will be calculated.\n\n  In order to use this function, you are expected to maintain two tables, ``cur`` and ``prev``,\n  both initially empty. After calling the function, you need to swap them, i.e. run\n  ``cur, prev = prev, cur``.\n\n  This function must be invoked each second.\n\n* ``widget(tbl)``\n\n  Constructs a ``widget`` table required by luastatus. ``tbl`` is a table with the following\n  fields:\n\n  **(required)**\n\n  - ``cb``: a function\n\n    The callback function that will be called with current CPU usage rate, or with ``nil``\n    if the usage rate can not yet be calculated or if the given ``cpu`` is not plugged in.\n\n  **(optional)**\n\n  - ``cpu``: a number\n\n    CPU number, starting with 1.\n\n  - ``event``\n\n    The ``event`` entry of the resulting table (see ``luastatus`` documentation for the\n    description of ``widget.event`` field).\n"
  },
  {
    "path": "plugins/cpu-usage-linux/cpu-usage-linux.lua",
    "content": "--[[\n  Copyright (C) 2015-2025  luastatus developers\n\n  This file is part of luastatus.\n\n  luastatus is free software: you can redistribute it and/or modify\n  it under the terms of the GNU Lesser General Public License as published by\n  the Free Software Foundation, either version 3 of the License, or\n  (at your option) any later version.\n\n  luastatus is distributed in the hope that it will be useful,\n  but WITHOUT ANY WARRANTY; without even the implied warranty of\n  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n  GNU Lesser General Public License for more details.\n\n  You should have received a copy of the GNU Lesser General Public License\n  along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n--]]\n\nlocal P = {}\n\nlocal DEFAULT_PROCPATH = '/proc'\n\nlocal function wrap0(x)\n    return x < 0 and 0 or x\nend\n\nlocal function clear_table(t)\n    for k, _ in pairs(t) do\n        t[k] = nil\n    end\nend\n\nlocal function get_usage_impl(procpath, cur, prev, cpu)\n    local f = assert(io.open(procpath .. '/stat', 'r'))\n    for _ = 1, (cpu or 0) do\n        f:read('*line')\n    end\n    local line = f:read('*line')\n    f:close()\n    if not line then\n        clear_table(cur)\n        clear_table(prev)\n        return nil\n    end\n    cur.user, cur.nice, cur.system, cur.idle, cur.iowait, cur.irq, cur.softirq, cur.steal,\n        cur.guest, cur.guest_nice = string.match(line,\n            'cpu%d*%s+(%S+)%s+(%S+)%s+(%S+)%s+(%S+)%s+(%S+)%s+(%S+)%s+(%S+)%s+(%S+)%s+(%S+)%s+(%S+)')\n    assert(cur.user ~= nil, 'Line has unexpected format')\n\n    cur.user = cur.user - cur.guest\n    cur.nice = cur.nice - cur.guest_nice\n\n    cur.IdleAll = cur.idle + cur.iowait\n    cur.SysAll = cur.system + cur.irq + cur.softirq\n    cur.VirtAll = cur.guest + cur.guest_nice\n    cur.Total = cur.user + cur.nice + cur.SysAll + cur.IdleAll + cur.steal + cur.VirtAll\n\n    if prev.user == nil then\n        return nil\n    end\n    return (wrap0(cur.user - prev.user)     +\n            wrap0(cur.nice - prev.nice)     +\n            wrap0(cur.SysAll - prev.SysAll) +\n            wrap0(cur.steal - prev.steal)   +\n            wrap0(cur.guest - prev.guest)\n           ) / wrap0(cur.Total - prev.Total)\nend\n\nfunction P.get_usage(cur, prev, cpu)\n    return get_usage_impl(DEFAULT_PROCPATH, cur, prev, cpu)\nend\n\nfunction P.widget(tbl)\n    local procpath = tbl._procpath or DEFAULT_PROCPATH\n    local cur, prev = {}, {}\n    return {\n        plugin = 'timer',\n        opts = {period = 1},\n        cb = function()\n            prev, cur = cur, prev\n            return tbl.cb(get_usage_impl(procpath, cur, prev, tbl.cpu))\n        end,\n        event = tbl.event,\n    }\nend\n\nreturn P\n"
  },
  {
    "path": "plugins/dbus/.gitignore",
    "content": "lualib.generated.inc\n"
  },
  {
    "path": "plugins/dbus/CMakeLists.txt",
    "content": "file (GLOB sources \"*.c\" \"zoo/*.c\")\nluastatus_add_plugin (plugin-dbus $<TARGET_OBJECTS:ls> $<TARGET_OBJECTS:moonvisit> ${sources})\n\ntarget_compile_definitions (plugin-dbus PUBLIC -D_POSIX_C_SOURCE=200809L)\nluastatus_target_compile_with (plugin-dbus LUA)\ntarget_include_directories (plugin-dbus PUBLIC \"${PROJECT_SOURCE_DIR}\")\n\nfind_package (PkgConfig REQUIRED)\npkg_check_modules (GLIB_STUFF REQUIRED glib-2.0 gio-2.0)\nluastatus_target_build_with (plugin-dbus GLIB_STUFF)\n\n# find pthreads\nset (CMAKE_THREAD_PREFER_PTHREAD TRUE)\nset (THREADS_PREFER_PTHREAD_FLAG TRUE)\nfind_package (Threads REQUIRED)\n# link against pthread\ntarget_link_libraries (plugin-dbus PUBLIC Threads::Threads)\n\nluastatus_add_man_page (README.rst                  luastatus-plugin-dbus 7)\nluastatus_add_man_page (README_FN_CALL_METHOD.rst   luastatus-plugin-dbus-fn-call-method 7)\nluastatus_add_man_page (README_FN_PROP.rst          luastatus-plugin-dbus-fn-prop 7)\nluastatus_add_man_page (README_FN_MKVAL.rst         luastatus-plugin-dbus-fn-mkval 7)\n\n# === lualib stuff below ===\n\nfunction (add_stringified_file dest src target_name)\n    add_custom_command (\n        OUTPUT \"${dest}\"\n        COMMAND \"${CMAKE_CURRENT_SOURCE_DIR}/cstringify.sh\" ARGS \"${src}\" \"${dest}\"\n        MAIN_DEPENDENCY \"${src}\"\n        VERBATIM)\n    add_custom_target (\"${target_name}\" DEPENDS \"${dest}\")\nendfunction ()\n\nadd_stringified_file (\n    \"${CMAKE_CURRENT_BINARY_DIR}/lualib.generated.inc\"\n    \"${CMAKE_CURRENT_SOURCE_DIR}/lualib.lua\"\n    \"stringify-lualib\"\n)\n\nadd_dependencies (plugin-dbus \"stringify-lualib\")\n\ntarget_include_directories (plugin-dbus PRIVATE \"${CMAKE_CURRENT_BINARY_DIR}\")\n"
  },
  {
    "path": "plugins/dbus/README.rst",
    "content": ".. :X-man-page-only: luastatus-plugin-dbus\n.. :X-man-page-only: #####################\n.. :X-man-page-only:\n.. :X-man-page-only: ##########################\n.. :X-man-page-only: D-Bus plugin for luastatus\n.. :X-man-page-only: ##########################\n.. :X-man-page-only:\n.. :X-man-page-only: :Copyright: LGPLv3\n.. :X-man-page-only: :Manual section: 7\n\nOverview\n========\nThis plugin subscribes to D-Bus signals.\n\nOptions\n=======\nThe following options are supported:\n\n* ``greet``: boolean\n\n  Whether or not a first call to ``cb`` with ``what=\"hello\"`` should be made as soon as the\n  widget starts. Defaults to false.\n\n* ``report_when_ready``: boolean\n\n  Whether or not to call to ``cb`` with ``what=\"ready\"`` once the plugin has subscribed to\n  all events successfully.\n  Note that there is no guarantee this will be done before any other call, including calls\n  with ``what=\"signal\"``, because glib has something called \"priorities\". You can simply\n  ignore any calls before one with ``what=\"ready\"``.\n\n  Defaults to false.\n\n* ``timeout``: number\n\n  If specified and not negative, this plugin calls ``cb`` with ``what=\"timeout\"`` if no D-Bus\n  signal has been received in ``timeout`` seconds.\n\n* ``signals``: array of tables\n\n  Array of tables with the following entries (all are optional):\n\n  - ``sender``: string\n\n    Sender name to match on (unique or well-known name).\n\n  - ``interface``: string\n\n    D-Bus interface name to match on.\n\n  - ``signal``: string\n\n    D-Bus signal name to match on.\n\n  - ``object_path``: string\n\n    Object path to match on.\n\n  - ``arg0``: string\n\n    Contents of the first string argument to match on.\n\n  - ``flags``: array of strings\n\n    The following flags are recognized:\n\n    + ``\"match_arg0_namespace\"``\n\n      Match first arguments that contain a bus or interface name with the given namespace.\n\n    + ``\"match_arg0_path\"``\n\n      Match first arguments that contain an object path that is either equivalent to the\n      given path, or one of the paths is a subpath of the other.\n\n  - ``bus``: string\n\n    Specify the bus to subscribe to the signal on: either ``\"system\"`` or ``\"session\"``;\n    default is ``\"session\"``.\n\n``cb`` argument\n===============\nA table with a ``what`` entry.\n\n* If ``what`` is ``\"hello\"``, this is the first call to ``cb`` (only if the ``greet`` option was\n  set to ``true``).\n\n* If ``what`` is ``\"ready\"``, the plugin has subscribed to all the events successfully (only if\n  the ``report_when_ready`` option was set to ``true``).\n\n* It ``what`` is ``\"timeout\"``, a signal has not been received for the number of seconds specified\n  as the ``timeout`` option.\n\n* If ``what`` is ``\"signal\"``, a signal has been received. In this case, the table has the\n  following additional entries:\n\n  - ``bus``: string\n\n    Either ``\"session\"`` or ``\"system\"``.\n\n  - ``sender``: string\n\n    Unique sender name.\n\n  - ``object_path``: string\n\n    Object path.\n\n  - ``interface``: string\n\n    D-Bus interface name.\n\n  - ``signal``: string\n\n    Signal name.\n\n  - ``parameters``: *D-Bus object*\n\n    Signal arguments.\n\nD-Bus objects\n=============\nD-Bus objects are marshalled as follows:\n\n.. rst2man does not support tables with headers, so let's just use bold.\n\n+-----------------------+------------------------+\n| **D-Bus object type** | **Lua representation** |\n+-----------------------+------------------------+\n| boolean               | boolean                |\n+-----------------------+------------------------+\n| byte, int16, uint16,  | string                 |\n| int32, uint32, int64, |                        |\n| uint64                |                        |\n+-----------------------+------------------------+\n| double                | number                 |\n+-----------------------+------------------------+\n| string, object path,  | string                 |\n| signature             |                        |\n+-----------------------+------------------------+\n| handle                | special object with    |\n|                       | value ``\"handle\"``     |\n+-----------------------+------------------------+\n| array, tuple, dict    | array                  |\n| entry                 |                        |\n+-----------------------+------------------------+\n\nIf an object cannot be marshalled, a special object with an error is generated instead.\n\nSpecial objects\n---------------\nSpecial objects represent D-Bus objects that cannot be marshalled to Lua.\n\nA special object is a function that, when called, returns either of:\n\n* value;\n* ``nil``, error.\n\nFunctions\n=========\n\nThis plugin provides functions to:\n\n1. interact with other programs over D-Bus (calling methods and getting/setting properties);\n2. construct D-Bus values and types from within Lua; this is needed for calling methods and setting properties.\n\nThe full descriptions of all those functions would be a bit too long for a single man page.\nSo the detailed descriptions are provided in other man pages (or .rst files):\n\n* **luastatus-plugin-dbus-fn-call-method(7)** (or ``README_FN_CALL_METHOD.rst`` file): functions to\n  call methods of D-Bus objects:\n\n  - ``luastatus.plugin.call_method``: calls a method of a D-Bus object.\n\n  - ``luastatus.plugin.call_method_str``: same as above, but can only call methods accepting\n    a single string argument or no arguments. It exists because it's easier to use it compared to\n    the above, and also for backward compatibility.\n\n* **luastatus-plugin-dbus-fn-prop(7)** (or ``README_FN_PROP.rst`` file): functions to get/set\n  properties of D-Bus objects:\n\n  - ``luastatus.plugin.get_property``: get a property of a D-Bus object.\n\n  - ``luastatus.plugin.get_all_properties``: get all properties of a D-Bus object.\n\n  - ``luastatus.plugin.set_property``: set a property of a D-Bus object.\n\n  - ``luastatus.plugin.set_property_str``: same as above, but can only set string properties. It exists\n    because it's easier to use it compared to the above, and also for backward compatibility.\n\n* **luastatus-plugin-dbus-fn-mkval(7)** (or ``README_FN_MKVAL.rst`` file): functions to construct D-Bus\n  values and types from within Lua:\n\n  - module ``dbustypes``:\n\n    + ``luastatus.plugin.dbustypes.mkval_from_fmt``: make a D-Bus value object from a type format string and a Lua value.\n\n    + ``luastatus.plugin.dbustypes.parse_dtype``: parse a type format string into a D-Bus type object.\n\n    + ``luastatus.plugin.dbustypes.mkval_of_dtype``: make a D-Bus value object from a D-Bus type object and a Lua value.\n\nThere is also a module named ``dbustypes_lowlevel``.\nIt is not documented, because this module is probably of little interest to the user.\nIt is used by implementation of \"high-level\" ``dbustypes`` module.\n"
  },
  {
    "path": "plugins/dbus/README_FN_CALL_METHOD.rst",
    "content": ".. :X-man-page-only: luastatus-plugin-dbus-fn-call-method\n.. :X-man-page-only: ####################################\n.. :X-man-page-only:\n.. :X-man-page-only: #####################################################\n.. :X-man-page-only: D-Bus plugin for luastatus: functions to call methods\n.. :X-man-page-only: #####################################################\n.. :X-man-page-only:\n.. :X-man-page-only: :Copyright: LGPLv3\n.. :X-man-page-only: :Manual section: 7\n\nOverview\n========\nThe **dbus** plugin for **luastatus** provides functions to:\n\n1. interact with other programs over D-Bus (calling methods and getting/setting properties);\n2. construct D-Bus values and types from within Lua; this is needed for calling methods and setting properties.\n\nFor more information on **luastatus**, see **luastatus(1)**.\nFor more information on the **dbus** plugin for luastatus, see **luastatus-plugin-dbus(7)**.\n\nThis man page gives full descriptions of the functions to call methods of D-Bus objects belonging to other programs.\n\nAll the functions take exactly one argument, which must be a table with string keys (we are going to refer to\nthese keys as *fields*).\n\nCommon fields\n=============\n\nThere are a number of fields that are accepted by all the functions listed here:\n\n* ``bus`` (string, **required**): the bus to use: either ``\"system\"`` or ``\"session\"``.\n\n* ``flag_no_autostart`` (boolean, optional): whether to pass\n  ``G_DBUS_CALL_FLAGS_NO_AUTO_START`` flag (which means don't launch an\n  owner for the destination name in response to this method invocation).\n\n  Defaults to false.\n\n* ``timeout`` (number, optional): timeout to wait for the reply for, in seconds.\n  Default is to use the proxy default timeout.\n  Pass ``math.huge`` to wait forever.\n\nFunctions\n=========\nThe following functions for calling methods of D-Bus objects are provided:\n\n* ``luastatus.plugin.call_method(params)``\n\n  Call a D-Bus method with the specified arguments.\n\n  ``params`` must be a table with the following fields, in addition to the \"common\" fields:\n\n  - ``dest`` (string): a unique or well-known name of the owner.\n\n  - ``object_path`` (string): the path to the object.\n\n  - ``interface`` (string): the name of the interface.\n\n  - ``method`` (string): the name of the method.\n\n  - ``args`` (D-Bus value object): the arguments for the call, as a tuple. The tuple can be\n    constructed with ``luastatus.plugin.dbustypes.mkval_from_fmt``\n    or ``luastatus.plugin.dbustypes.mkval_of_dtype``\n    functions (see **luastatus-plugin-dbus-fn-mkval(7)** for more information).\n\n  On success, returns ``true, result``, where ``result`` is the result of\n  the call call, unpacked as described in section `Marshalling`_.\n\n  On failure, returns ``false, err_msg, err_code``.\n\n* ``luastatus.plugin.call_method_str(params)``\n\n  Call a D-Bus method, either with no arguments or a single string argument.\n\n  This function exists because the more generic function, presented above, is harder to\n  use for simple use cases, and also for backward compatibility.\n\n  ``params`` must be a table with the following fields, in addition to the \"common\" fields:\n\n  - ``dest`` (string): a unique or well-known name of the owner.\n\n  - ``object_path`` (string): the path to the object.\n\n  - ``interface`` (string): the name of the interface.\n\n  - ``method`` (string): the name of the method.\n\n  - ``arg_str`` (optional, string): the argument for the call. If specified, the method\n    will be called with a single string argument. If not specified, the method will be\n    called with no arguments.\n\n  On success, returns ``true, result``, where ``result`` is the result of\n  the call call, unpacked as described in section `Marshalling`_.\n\n  On failure, returns ``false, err_msg, err_code``.\n\nMarshalling\n===========\nPlease see the main man page (**luastatus-plugin-dbus**) or the main help file (``README.rst``),\nsection \"D-Bus objects\", for information about how values are converted from D-Bus objects\ninto Lua stuff.\n"
  },
  {
    "path": "plugins/dbus/README_FN_MKVAL.rst",
    "content": ".. :X-man-page-only: luastatus-plugin-dbus-fn-mkval\n.. :X-man-page-only: ##############################\n.. :X-man-page-only:\n.. :X-man-page-only: ############################################################\n.. :X-man-page-only: D-Bus plugin for luastatus: functions to create D-Bus values\n.. :X-man-page-only: ############################################################\n.. :X-man-page-only:\n.. :X-man-page-only: :Copyright: LGPLv3\n.. :X-man-page-only: :Manual section: 7\n\nOverview\n========\nThe **dbus** plugin for **luastatus** provides functions to:\n\n1. interact with other programs over D-Bus (calling methods and getting/setting properties);\n2. construct D-Bus values and types from within Lua; this is needed for calling methods and setting properties.\n\nFor more information on **luastatus**, see **luastatus(1)**.\nFor more information on the **dbus** plugin for luastatus, see **luastatus-plugin-dbus(7)**.\n\nThis man page gives full descriptions of the functions to construct D-Bus values and types\nfrom within Lua. All the functions described here are in the ``dbustypes`` module;\nso please note that you should call them as ``luastatus.plugin.dbustypes.some_func()``.\n\nFunctions\n=========\nThe following functions for construction of D-Bus values and types from within Lua are provided:\n\n* ``luastatus.plugin.dbustypes.mkval_from_fmt(fmt, lua_val)``\n\n  Make a D-Bus value object from the type string ``fmt`` and Lua representation\n  ``lua_val``. For information on type strings, see `DBus type system and type strings`_.\n  For information on the required structure of ``lua_val``, see `Value conversion`_.\n\n* ``luastatus.plugin.dbustypes.mkval_of_dtype(dtype, lua_val)``\n\n  Make a D-Bus value object of type ``dtype`` from Lua representation\n  ``lua_val``. A ``dtype`` value can be obtained via a call to ``luastatus.plugin.dbustypes.parse_dtype``.\n  For information on the required structure of ``lua_val``, see `Value conversion`_.\n\n* ``luastatus.plugin.dbustypes.parse_dtype(fmt)``\n\n  Make a D-Bus type object from the type string ``fmt``.\n  For information on type strings, see `DBus type system and type strings`_.\n\nDBus type system and type strings\n=================================\nThis section discusses the D-Bus type system and type strings.\n\nIn D-Bus, each type can be expressed as a type string, which is a printable\nstring, e.g. ``d`` or ``(susssasa{sv}i)``.\nWe will refer to the type string of a type as the *spelling* of that type.\n\nFor a more digestible description, the reader is advised to visit\n<https://docs.gtk.org/glib/gvariant-format-strings.html> and/or\n<https://dbus.freedesktop.org/doc/dbus-specification.html>.\n\nBasic types\n-----------\nThere are a number of *basic types*. The spelling of a basic type\nis always a single letter:\n\n* ``b``: boolean type.\n\n* ``y``: byte type.\n\n* ``n``, ``i``, ``x``: signed integer types, 16-bit, 32-bit and 64-bit, correspondingly.\n\n* ``q``, ``u``, ``t``: unsigned integer types, 16-bit, 32-bit and 64-bit, correspondingly.\n\n* ``d``: floating-point type (the same as Lua number).\n\n* ``s``: string type; note that the string must be valid UTF-8, and must not contain NUL characters (``\\0``).\n\n* ``o``: D-Bus object path type. This is just like a string, but with additional requirements for the content.\n\n* ``g``: D-Bus signature type. This is just like a string, but with additional requirements for the content.\n\n* ``h``: \"handle\" type; it represents a file descriptor passed along with the call. There is no support for handles\n  in this module; you can create the handle type, but you can't create a handle value object.\n\nThe variant type\n----------------\nThere is a \"variant\" type; its spelling is ``v``.\nIt acts as a black box: you can put value of any type into a variant value,\nbut the type system only sees the variant type.\nYou can put a variant into a variant, too, so there may be many levels of variants.\n\nIt is frequently used to hide implementation-related details.\n\nFor example, the type ``a{sv}`` (array of dictionary entries from string to variant),\nmore conveniently \"dictionary from strings to anything\", is used in Desktop Notifications Specification\n`for hints to notification daemon <https://specifications.freedesktop.org/notification/latest/protocol.html#id-1.10.3.3.4>`_.\n\nHints keys are always strings, but the value might be anything (see `the list of standard hints <https://specifications.freedesktop.org/notification/latest/hints.html>`_).\n``suppress-sound`` is boolean, ``sound-file`` is string, ``x`` and ``y`` are int32. And also ``image-data`` is ``(iiibiiay)``.\n\nArray types\n-----------\nThere is a type that represents an array of something.\nEach element in the array must be of the same type.\nIf the spelling of the element type is ``X``, then ``aX`` is the spelling of the type \"array of values of type ``X``\".\n\nFor example, ``as`` means array of strings, ``aad`` means an array of arrays of floating-point values.\n\nTuple types\n-----------\nWhile a value of type \"array of string\" might have any length,\nthe number of items in the tuple is fixed in the type signature.\nAlso, unlike arrays, tuple can contain values of different types.\n\nSo, a tuple can represent things like \"three strings\", \"an int32 and a string\" or \"nothing at all\" (empty tuple).\n\nTuple items don't have names; instead, they are referred to by their index.\n\nThe spelling of a tuple type consists of:\n1. symbol ``(``, and\n2. spellings of the types of all the items concatenated (without any delimiter), and\n3. symbol ``)``.\n\nSo, \"three strings\" is spelled as ``(sss)``, \"an int32 and a string\" is spelled as \"(is)\",\nand \"nothing at all\" is spelled as ``()``. The latter is called an empty tuple.\n\nDict entry types\n----------------\nThe D-Bus type system doesn't have a dictionary type; instead, it emulates dictionaries\nwith arrays of *dict entries*. Basically, a dict entry is a pair of key and value,\nwith the requirement that the key must be of basic type.\n\nSuppose we have two types, one with spelling ``K`` and another with spelling ``V``.\nWe want to use the type with spelling ``K`` as the \"key\" type in a \"dictionary\",\nand use the type with spelling ``V`` as the \"value\" type.\n(Note that this can only be done if the \"key\" type is basic.)\nThen the spelling of dict entry for this key-value pair of types is ``{KV}``.\n\nFor example, ``a{sv}`` means, literally, \"array of dictionary entries from string to variant\",\nbut in more human terms means \"dictionary from strings to anything\".\n\nValue conversion\n================\nDuring the conversion from D-Bus object to Lua value, the conversion functions\nrequire the following correspondence between D-Bus types and Lua values:\n\n+------------------------+-------------------------+\n| **D-Bus type string**  | **Lua representation**  |\n+------------------------+-------------------------+\n| ``b``                  | boolean                 |\n+------------------------+-------------------------+\n| ``y``                  | either string of length |\n|                        | 1, or a number (numeric |\n|                        | value of a byte)        |\n+------------------------+-------------------------+\n| ``n``, ``i``, ``x``,   | either a string or a    |\n| ``q``, ``u``, ``t``    | number                  |\n+------------------------+-------------------------+\n| double                 | number                  |\n+------------------------+-------------------------+\n| string, object path,   | string                  |\n| signature              |                         |\n+------------------------+-------------------------+\n| handle                 | special object with     |\n|                        | value ``\"handle\"``      |\n+------------------------+-------------------------+\n| array, tuple           | array (a table with     |\n|                        | numeric keys)           |\n+------------------------+-------------------------+\n| dict entry             | array of length 2: key  |\n|                        | and value               |\n+------------------------+-------------------------+\n\nCreation of handles is not supported; it is possible to create a handle *type*,\nbut trying to create a handle *value* leads to an error being thrown.\n\nExample\n=======\nThis example shows a notification with text in red color::\n\n    function show_notification()\n        local summary = 'Hello'\n        local body = 'World'\n\n        local mkval = luastatus.plugin.dbustypes.mkval_from_fmt\n        local args = mkval('(susssasa{sv}i)', {\n            'luastatus',    -- appname\n            0,              -- replaces_id\n            \"\",             -- icon\n            summary,        -- summary\n            body,           -- body\n            {},             -- actions\n            {               -- hints\n                {'fgcolor', mkval('s', '#ff0000')},\n            },\n            -1,             -- timeout\n        })\n\n        assert(luastatus.plugin.call_method({\n            bus = 'session',\n            dest = 'org.freedesktop.Notifications',\n            object_path = '/org/freedesktop/Notifications',\n            interface = 'org.freedesktop.Notifications',\n            method = 'Notify',\n            args = args,\n        }))\n    end\n\nNotes\n=====\nThere is also a module named ``dbustypes_lowlevel``.\nIt is not documented, because this module is probably of little interest to the user.\nIt is used by implementation of \"high-level\" ``dbustypes`` module.\n"
  },
  {
    "path": "plugins/dbus/README_FN_PROP.rst",
    "content": ".. :X-man-page-only: luastatus-plugin-dbus-fn-prop\n.. :X-man-page-only: #############################\n.. :X-man-page-only:\n.. :X-man-page-only: ###########################################################\n.. :X-man-page-only: D-Bus plugin for luastatus: functions to get/set properties\n.. :X-man-page-only: ###########################################################\n.. :X-man-page-only:\n.. :X-man-page-only: :Copyright: LGPLv3\n.. :X-man-page-only: :Manual section: 7\n\nOverview\n========\nThe **dbus** plugin for **luastatus** provides functions to:\n\n1. interact with other programs over D-Bus (calling methods and getting/setting properties);\n2. construct D-Bus values and types from within Lua; this is needed for calling methods and setting properties.\n\nFor more information on **luastatus**, see **luastatus(1)**.\nFor more information on the **dbus** plugin for luastatus, see **luastatus-plugin-dbus(7)**.\n\nThis man page gives full descriptions of the functions to get/set properties of D-Bus objects belonging to\nother programs.\n\nAll the functions take exactly one argument, which must be a table with string keys (we are going to refer to\nthese keys as *fields*).\n\nCommon fields\n=============\n\nThere are a number of fields that are accepted by all the functions listed here:\n\n* ``bus`` (string, **required**): the bus to use: either ``\"system\"`` or ``\"session\"``.\n\n* ``flag_no_autostart`` (boolean, optional): whether to pass\n  ``G_DBUS_CALL_FLAGS_NO_AUTO_START`` flag (which means don't launch an\n  owner for the destination name in response to this method invocation).\n\n  Defaults to false.\n\n* ``timeout`` (number, optional): timeout to wait for the reply for, in seconds.\n  Default is to use the proxy default timeout.\n  Pass ``math.huge`` to wait forever.\n\nFunctions\n=========\nThe following functions for getting/setting properties of D-Bus objects are provided:\n\n* ``luastatus.plugin.get_property(params)``\n\n  Get a D-Bus property associated with an interface. ``params`` must be a table\n  with the following fields, in addition to the \"common\" fields (all are **required**):\n\n  - ``dest`` (string): a unique or well-known name of the owner of the property.\n\n  - ``object_path`` (string): the path to the object to get a property of.\n\n  - ``interface`` (string): the name of the interface.\n\n  - ``property_name`` (string): the name of the property.\n\n  On success, returns ``true, result``, where ``result`` is unmarshalled\n  as described in section `Marshalling`_.\n\n  On failure, returns ``false, err_msg, err_code``.\n\n* ``luastatus.plugin.get_all_propertes(params)``\n\n  Get all D-Bus properties associated with an interface. ``params`` must be the\n  same as to ``get_property``, except that ``property_name`` should not be set.\n\n  On success, returns ``true, result``, where ``result`` is the result of\n  the ``GetAll`` call, unmarshalled as described in section `Marshalling`_.\n  It looks like this::\n\n    {{{\"Property1\", \"Value1\"}, {\"Property2\", \"Value2\"}}}\n\n  On failure, returns ``false, err_msg, err_code``.\n\n* ``luastatus.plugin.set_property(params)``\n\n  Set a D-Bus property associated with an interface. ``params`` must be\n  the same as to ``get_property``, except that a new field ``value`` must be set.\n  ``value`` must be a D-Bus value object; it can be constructed with\n  ``luastatus.plugin.dbustypes.mkval_from_fmt`` or\n  ``luastatus.plugin.dbustypes.mkval_of_dtype``\n  functions (see **luastatus-plugin-dbus-fn-mkval(7)** for more information).\n\n  On success, returns ``true, result``, where ``result`` is an empty table (the ``Set``\n  method does not return anything).\n\n  On failure, returns ``false, err_msg, err_code``.\n\n* ``luastatus.plugin.set_property_str(params)``\n\n  Set D-Bus property associated with an interface, to a string. ``params`` must be\n  the same as to ``get_property``, except that a new string field ``value_str``\n  must be set.\n\n  This function exists because the more generic function, presented above, is harder to\n  use for simple use cases, and also for backward compatibility.\n\n  On success, returns ``true, result``, where ``result`` is an empty table (the ``Set``\n  method does not return anything).\n\n  On failure, returns ``false, err_msg, err_code``.\n\nMarshalling\n===========\nPlease see the main man page (**luastatus-plugin-dbus**) or the main help file (``README.rst``),\nsection \"D-Bus objects\", for information about how values are converted from D-Bus objects\ninto Lua stuff.\n"
  },
  {
    "path": "plugins/dbus/bustype2idx.h",
    "content": "/*\n * Copyright (C) 2026  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#pragma once\n\n#include <stddef.h>\n#include <gio/gio.h>\n#include \"libls/ls_compdep.h\"\n#include \"libls/ls_panic.h\"\n\nLS_INHEADER size_t bustype2idx(GBusType bus_type)\n{\n    switch (bus_type) {\n    case G_BUS_TYPE_SYSTEM:\n        return 0;\n    case G_BUS_TYPE_SESSION:\n        return 1;\n    default:\n        LS_PANIC(\"bustype2idx: got unexpected GBusType value\");\n    }\n}\n"
  },
  {
    "path": "plugins/dbus/cstringify.sh",
    "content": "#!/bin/sh\n\nif [ \"$#\" -ne 2 ]; then\n    echo >&2 \"USAGE: $0 INPUT OUTPUT\"\n    exit 2\nfi\n\n# 1. Escape backslashes (\\ -> \\\\) and double quotes (\" -> \\\").\n# 2. Replace tabs with \\t.\n# 3. Try to handle C trigraphs.\n# 4. Insert \\n at the end, enclose the whole line in double quotes.\nsed -r 's/[\\\\\"]/\\\\\\0/g; s/\\t/\\\\t/g; s/\\?/\"\"\\0\"\"/g; s/.*/\"\\0\\\\n\"/' -- \"$1\" > \"$2\"\n"
  },
  {
    "path": "plugins/dbus/cvt.c",
    "content": "/*\n * Copyright (C) 2015-2026  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#include \"cvt.h\"\n\n#include <stdio.h>\n#include <stdarg.h>\n#include <stdint.h>\n#include <inttypes.h>\n#include <limits.h>\n#include <string.h>\n#include <stdbool.h>\n#include <glib/gtypes.h>\n#include <lua.h>\n#include \"libls/ls_compdep.h\"\n#include \"libls/ls_panic.h\"\n#include \"libls/ls_lua_compat.h\"\n\nstatic int l_special_object(lua_State *L)\n{\n    lua_pushvalue(L, lua_upvalueindex(1)); // L: upvalue1\n    if (lua_isnil(L, -1)) {\n        lua_pushvalue(L, lua_upvalueindex(2)); // L: upvalue1 upvalue2\n        return 2;\n    } else {\n        // L: upvalue1\n        return 1;\n    }\n}\n\nstatic void push_special_object(lua_State *L, const char *s, size_t ns, bool is_error)\n{\n    if (ns == (size_t) -1) {\n        ns = strlen(s);\n    }\n\n    int num_upvalues = 1;\n    if (is_error) {\n        lua_pushnil(L);\n        num_upvalues = 2;\n    }\n    lua_pushlstring(L, s, ns);\n    lua_pushcclosure(L, l_special_object, num_upvalues);\n}\n\n// forward declaration\nstatic void push_gvariant(lua_State *L, GVariant *var, unsigned recurlim);\n\nstatic void on_recur_lim(lua_State *L)\n{\n    push_special_object(L, \"depth limit exceeded\", -1, true);\n}\n\nstatic inline void push_gvariant_strlike(lua_State *L, GVariant *var)\n{\n    gsize ns;\n    const gchar *s = g_variant_get_string(var, &ns);\n    lua_pushlstring(L, s, ns);\n}\n\nstatic void push_gvariant_iterable(lua_State *L, GVariant *var, unsigned recurlim)\n{\n    if (!recurlim--) {\n        on_recur_lim(L);\n        return;\n    }\n\n    GVariantIter iter;\n    g_variant_iter_init(&iter, var);\n\n    size_t n = g_variant_iter_n_children(&iter);\n    if (n > (size_t) LS_LUA_MAXI) {\n        push_special_object(L, \"array would be too big\", -1, true);\n        return;\n    }\n    lua_createtable(L, ls_lua_num_prealloc(n), 0); // L: table\n\n    GVariant *elem;\n    for (size_t i = 1; (elem = g_variant_iter_next_value(&iter)); ++i) {\n        push_gvariant(L, elem, recurlim); // L: table value\n        g_variant_unref(elem);\n        lua_rawseti(L, -2, i); // L: table\n    }\n}\n\nstatic inline LS_ATTR_PRINTF(2, 3)\nvoid push_small_fstr(lua_State *L, const char *fmt, ...)\n{\n    char buf[32];\n    va_list vl;\n    va_start(vl, fmt);\n    vsnprintf(buf, sizeof(buf), fmt, vl);\n    va_end(vl);\n    lua_pushstring(L, buf);\n}\n\nstatic void push_gvariant(lua_State *L, GVariant *var, unsigned recurlim)\n{\n    LS_ASSERT(var != NULL);\n\n    if (!recurlim--) {\n        on_recur_lim(L);\n        return;\n    }\n\n    switch (g_variant_classify(var)) {\n    case G_VARIANT_CLASS_BOOLEAN:\n        lua_pushboolean(L, !!g_variant_get_boolean(var));\n        break;\n\n    case G_VARIANT_CLASS_BYTE:\n        push_small_fstr(L, \"%\" PRIu8, (uint8_t) g_variant_get_byte(var));\n        break;\n\n    case G_VARIANT_CLASS_INT16:\n        push_small_fstr(L, \"%\" PRIi16, (int16_t) g_variant_get_int16(var));\n        break;\n\n    case G_VARIANT_CLASS_UINT16:\n        push_small_fstr(L, \"%\" PRIu16, (uint16_t) g_variant_get_uint16(var));\n        break;\n\n    case G_VARIANT_CLASS_INT32:\n        push_small_fstr(L, \"%\" PRIi32, (int32_t) g_variant_get_int32(var));\n        break;\n\n    case G_VARIANT_CLASS_UINT32:\n        push_small_fstr(L, \"%\" PRIu32, (uint32_t) g_variant_get_uint32(var));\n        break;\n\n    case G_VARIANT_CLASS_INT64:\n        push_small_fstr(L, \"%\" PRIi64, (int64_t) g_variant_get_int64(var));\n        break;\n\n    case G_VARIANT_CLASS_UINT64:\n        push_small_fstr(L, \"%\" PRIu64, (uint64_t) g_variant_get_uint64(var));\n        break;\n\n    case G_VARIANT_CLASS_DOUBLE:\n        lua_pushnumber(L, g_variant_get_double(var));\n        break;\n\n    case G_VARIANT_CLASS_STRING:\n    case G_VARIANT_CLASS_OBJECT_PATH:\n    case G_VARIANT_CLASS_SIGNATURE:\n        push_gvariant_strlike(L, var);\n        break;\n\n    case G_VARIANT_CLASS_VARIANT:\n        {\n            GVariant *boxed = g_variant_get_variant(var);\n            push_gvariant(L, boxed, recurlim);\n            g_variant_unref(boxed);\n        }\n        break;\n\n    case G_VARIANT_CLASS_ARRAY:\n    case G_VARIANT_CLASS_TUPLE:\n    case G_VARIANT_CLASS_DICT_ENTRY:\n        push_gvariant_iterable(L, var, recurlim);\n        break;\n\n    case G_VARIANT_CLASS_HANDLE:\n    default:\n        {\n            const GVariantType *type = g_variant_get_type(var);\n            const gchar *s = g_variant_type_peek_string(type);\n            gsize ns = g_variant_type_get_string_length(type);\n            push_special_object(L, s, ns, false);\n        }\n        break;\n    }\n}\n\nvoid cvt(lua_State *L, GVariant *var)\n{\n    if (!var) {\n        lua_pushnil(L);\n        return;\n    }\n\n    if (lua_checkstack(L, 210)) {\n        push_gvariant(L, var, 200);\n    } else {\n        push_special_object(L, \"out of memory\", -1, true);\n    }\n}\n"
  },
  {
    "path": "plugins/dbus/cvt.h",
    "content": "/*\n * Copyright (C) 2026  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#pragma once\n\n#include <lua.h>\n#include <glib.h>\n\n// \"cvt\" means \"convert\", as in \"convert GVariant to Lua value\".\n\nvoid cvt(lua_State *L, GVariant *var);\n"
  },
  {
    "path": "plugins/dbus/dbus.c",
    "content": "/*\n * Copyright (C) 2015-2026  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#include <lua.h>\n#include <glib.h>\n#include <gio/gio.h>\n#include <stdbool.h>\n#include <stdlib.h>\n#include <string.h>\n#include <pthread.h>\n#include <glib-object.h>\n\n#include \"libls/ls_alloc_utils.h\"\n#include \"libls/ls_time_utils.h\"\n#include \"libls/ls_panic.h\"\n\n#include \"libmoonvisit/moonvisit.h\"\n\n#include \"include/plugin_v1.h\"\n#include \"include/sayf_macros.h\"\n\n#include \"bustype2idx.h\"\n#include \"cvt.h\"\n#include \"load_lualib.h\"\n#include \"zoo/zoo.h\"\n\ntypedef struct {\n    char *sender;\n    char *interface;\n    char *signal;\n    char *object_path;\n    char *arg0;\n    GDBusSignalFlags flags;\n} Signal;\n\nstatic void signal_free(Signal s)\n{\n    free(s.sender);\n    free(s.interface);\n    free(s.signal);\n    free(s.object_path);\n    free(s.arg0);\n}\n\ntypedef struct {\n    Signal *data;\n    size_t size;\n    size_t capacity;\n} SignalList;\n\nstatic inline SignalList signal_list_new(void)\n{\n    return (SignalList) {NULL, 0, 0};\n}\n\nstatic inline void signal_list_add(SignalList *x, Signal s)\n{\n    if (x->size == x->capacity) {\n        x->data = LS_M_X2REALLOC(x->data, &x->capacity);\n    }\n    x->data[x->size++] = s;\n}\n\nstatic inline void signal_list_destroy(SignalList *x)\n{\n    for (size_t i = 0; i < x->size; ++i) {\n        signal_free(x->data[i]);\n    }\n    free(x->data);\n}\n\ntypedef struct {\n    SignalList subs[2];\n    double tmo;\n    bool greet;\n    bool report_when_ready;\n    Zoo *zoo;\n} Priv;\n\nstatic void destroy(LuastatusPluginData *pd)\n{\n    Priv *p = pd->priv;\n    for (int i = 0; i < 2; ++i) {\n        signal_list_destroy(&p->subs[i]);\n    }\n    if (p->zoo) {\n        zoo_destroy(p->zoo);\n    }\n    free(p);\n}\n\nstatic inline SignalList *get_sub(Priv *p, GBusType bus_type)\n{\n    size_t idx = bustype2idx(bus_type);\n    return &p->subs[idx];\n}\n\nstatic int parse_bus_str(MoonVisit *mv, void *ud, const char *s, size_t ns)\n{\n    (void) ns;\n    GBusType *out = ud;\n    if (strcmp(s, \"session\") == 0) {\n        *out = G_BUS_TYPE_SESSION;\n        return 1;\n    }\n    if (strcmp(s, \"system\") == 0) {\n        *out = G_BUS_TYPE_SYSTEM;\n        return 1;\n    }\n    moon_visit_err(mv, \"unknown bus: '%s'\", s);\n    return -1;\n}\n\nstatic int parse_flags_elem(MoonVisit *mv, void *ud, int kpos, int vpos)\n{\n    mv->where = \"'signals' element, 'flags' element\";\n    (void) kpos;\n\n    GDBusSignalFlags *out = ud;\n\n    if (moon_visit_checktype_at(mv, NULL, vpos, LUA_TSTRING) < 0)\n        goto error;\n\n    const char *s = lua_tostring(mv->L, vpos);\n    if (strcmp(s, \"match_arg0_namespace\") == 0) {\n        *out |= G_DBUS_SIGNAL_FLAGS_MATCH_ARG0_NAMESPACE;\n        return 1;\n    }\n    if (strcmp(s, \"match_arg0_path\") == 0) {\n        *out |= G_DBUS_SIGNAL_FLAGS_MATCH_ARG0_PATH;\n        return 1;\n    }\n    moon_visit_err(mv, \"unknown flag: '%s'\", s);\nerror:\n    return -1;\n}\n\nstatic int parse_signals_elem(MoonVisit *mv, void *ud, int kpos, int vpos)\n{\n    mv->where = \"'signals' element\";\n    (void) kpos;\n\n    Priv *p = ud;\n    Signal s = {0};\n\n    if (moon_visit_checktype_at(mv, NULL, vpos, LUA_TTABLE) < 0)\n        goto error;\n\n    GBusType bus_type = G_BUS_TYPE_SESSION;\n    if (moon_visit_str_f(mv, vpos, \"bus\", parse_bus_str, &bus_type, true) < 0)\n        goto error;\n\n    if (moon_visit_str(mv, vpos, \"sender\", &s.sender, NULL, true) < 0)\n        goto error;\n\n    if (moon_visit_str(mv, vpos, \"interface\", &s.interface, NULL, true) < 0)\n        goto error;\n\n    if (moon_visit_str(mv, vpos, \"signal\", &s.signal, NULL, true) < 0)\n        goto error;\n\n    if (moon_visit_str(mv, vpos, \"object_path\", &s.object_path, NULL, true) < 0)\n        goto error;\n\n    if (moon_visit_str(mv, vpos, \"arg0\", &s.arg0, NULL, true) < 0)\n        goto error;\n\n    if (moon_visit_table_f(mv, vpos, \"flags\", parse_flags_elem, &s.flags, true) < 0)\n        goto error;\n\n    signal_list_add(get_sub(p, bus_type), s);\n    return 1;\n\nerror:\n    signal_free(s);\n    return -1;\n}\n\nstatic int init(LuastatusPluginData *pd, lua_State *L)\n{\n    Priv *p = pd->priv = LS_XNEW(Priv, 1);\n    *p = (Priv) {\n        .subs = {signal_list_new(), signal_list_new()},\n        .tmo = -1,\n        .greet = false,\n        .report_when_ready = false,\n        .zoo = zoo_new(),\n    };\n    char errbuf[256];\n    MoonVisit mv = {.L = L, .errbuf = errbuf, .nerrbuf = sizeof(errbuf)};\n\n    // Parse greet\n    if (moon_visit_bool(&mv, -1, \"greet\", &p->greet, true) < 0)\n        goto mverror;\n\n    // Parse report_when_ready\n    if (moon_visit_bool(&mv, -1, \"report_when_ready\", &p->report_when_ready, true) < 0)\n        goto mverror;\n\n    // Parse timeout\n    if (moon_visit_num(&mv, -1, \"timeout\", &p->tmo, true) < 0)\n        goto mverror;\n\n    // Parse signals\n    if (moon_visit_table_f(&mv, -1, \"signals\", parse_signals_elem, p, false) < 0)\n        goto mverror;\n\n    return LUASTATUS_OK;\n\nmverror:\n    LS_FATALF(pd, \"%s\", errbuf);\n//error:\n    destroy(pd);\n    return LUASTATUS_ERR;\n}\n\nstatic void register_funcs(LuastatusPluginData *pd, lua_State *L)\n{\n    Priv *p = pd->priv;\n\n    zoo_register_funcs(p->zoo, L);\n\n    (void) load_lualib(pd, L);\n}\n\ntypedef struct {\n    LuastatusPluginData *pd;\n    LuastatusPluginRunFuncs funcs;\n    GDBusConnection *cnx_session;\n    GDBusConnection *cnx_system;\n    pthread_mutex_t mtx;\n} PluginRunArgs;\n\nstatic inline void set_str(lua_State *L, const char *s, const char *key)\n{\n    if (s) {\n        lua_pushstring(L, s);\n        lua_setfield(L, -2, key);\n    }\n}\n\nstatic void callback_signal(\n    GDBusConnection *cnx,\n    const gchar *sender_name,\n    const gchar *object_path,\n    const gchar *interface_name,\n    const gchar *signal_name,\n    GVariant *parameters,\n    gpointer user_data)\n{\n    PluginRunArgs *args = (PluginRunArgs *) user_data;\n\n    LS_PTH_CHECK(pthread_mutex_lock(&args->mtx));\n\n    const char *bus_name = \"\";\n    if (cnx == args->cnx_session) {\n        bus_name = \"session\";\n    } else if (cnx == args->cnx_system) {\n        bus_name = \"system\";\n    }\n\n    lua_State *L = args->funcs.call_begin(args->pd->userdata);\n\n    lua_createtable(L, 0, 7); // L: table\n\n    set_str(L, \"signal\", \"what\");\n    set_str(L, bus_name, \"bus\");\n    set_str(L, sender_name, \"sender\");\n    set_str(L, object_path, \"object_path\");\n    set_str(L, interface_name, \"interface\");\n    set_str(L, signal_name, \"signal\");\n\n    cvt(L, parameters); // L: table value\n    lua_setfield(L, -2, \"parameters\"); // L: table\n\n    args->funcs.call_end(args->pd->userdata);\n\n    LS_PTH_CHECK(pthread_mutex_unlock(&args->mtx));\n}\n\nstatic void report_simple(\n        LuastatusPluginData *pd,\n        LuastatusPluginRunFuncs funcs,\n        const char *what)\n{\n    lua_State *L = funcs.call_begin(pd->userdata);\n\n    lua_createtable(L, 0, 1); // L: table\n    lua_pushstring(L, what); // L: table string\n    lua_setfield(L, -2, \"what\"); // L: table\n\n    funcs.call_end(pd->userdata);\n}\n\nstatic gboolean callback_timeout(gpointer user_data)\n{\n    PluginRunArgs *args = (PluginRunArgs *) user_data;\n\n    LS_PTH_CHECK(pthread_mutex_lock(&args->mtx));\n    report_simple(args->pd, args->funcs, \"timeout\");\n    LS_PTH_CHECK(pthread_mutex_unlock(&args->mtx));\n\n    return G_SOURCE_CONTINUE;\n}\n\nstatic gboolean callback_idle(gpointer user_data)\n{\n    PluginRunArgs *args = (PluginRunArgs *) user_data;\n\n    LS_PTH_CHECK(pthread_mutex_lock(&args->mtx));\n    report_simple(args->pd, args->funcs, \"ready\");\n    LS_PTH_CHECK(pthread_mutex_unlock(&args->mtx));\n\n    return G_SOURCE_REMOVE;\n}\n\nstatic GDBusConnection *maybe_connect_and_subscribe(\n        Priv *p,\n        GBusType bus_type,\n        gpointer userdata,\n        GError **err)\n{\n    SignalList *SL = get_sub(p, bus_type);\n    if (!SL->size) {\n        return NULL;\n    }\n\n    GDBusConnection *cnx = g_bus_get_sync(bus_type, NULL, err);\n    if (*err) {\n        return NULL;\n    }\n\n    LS_ASSERT(cnx != NULL);\n\n    for (size_t i = 0; i < SL->size; ++i) {\n        Signal s = SL->data[i];\n        g_dbus_connection_signal_subscribe(\n            cnx,\n            s.sender,\n            s.interface,\n            s.signal,\n            s.object_path,\n            s.arg0,\n            s.flags,\n            callback_signal,\n            userdata,\n            NULL);\n    }\n    return cnx;\n}\n\nstatic void run(LuastatusPluginData *pd, LuastatusPluginRunFuncs funcs)\n{\n    Priv *p = pd->priv;\n\n    GError *err = NULL;\n    GDBusConnection *session_bus = NULL;\n    GDBusConnection *system_bus = NULL;\n    GMainLoop *mainloop = NULL;\n    GMainContext *context = g_main_context_ref_thread_default();\n    GSource *source_tmo = NULL;\n    GSource *source_idle = NULL;\n\n    PluginRunArgs args = {.pd = pd, .funcs = funcs};\n    LS_PTH_CHECK(pthread_mutex_init(&args.mtx, NULL));\n\n    session_bus = maybe_connect_and_subscribe(p, G_BUS_TYPE_SESSION, &args, &err);\n    if (err) {\n        LS_FATALF(pd, \"cannot connect to the session bus: %s\", err->message);\n        goto error;\n    }\n\n    system_bus = maybe_connect_and_subscribe(p, G_BUS_TYPE_SYSTEM, &args, &err);\n    if (err) {\n        LS_FATALF(pd, \"cannot connect to the system bus: %s\", err->message);\n        goto error;\n    }\n\n    args.cnx_session = session_bus;\n    args.cnx_system = system_bus;\n\n    LS_TimeDelta TD = ls_double_to_TD(p->tmo, LS_TD_FOREVER);\n    if (!ls_TD_is_forever(TD)) {\n        int tmo_ms = ls_TD_to_poll_ms_tmo(TD);\n        source_tmo = g_timeout_source_new(tmo_ms);\n        g_source_set_callback(source_tmo, callback_timeout, &args, NULL);\n        if (g_source_attach(source_tmo, context) == 0) {\n            LS_FATALF(pd, \"g_source_attach() failed (timeout source)\");\n            goto error;\n        }\n    }\n    if (p->greet) {\n        report_simple(pd, funcs, \"hello\");\n    }\n    if (p->report_when_ready) {\n        source_idle = g_idle_source_new();\n        g_source_set_callback(source_idle, callback_idle, &args, NULL);\n        if (g_source_attach(source_idle, context) == 0) {\n            LS_FATALF(pd, \"g_source_attach() failed (idle source)\");\n            goto error;\n        }\n    }\n\n    mainloop = g_main_loop_new(context, FALSE);\n    g_main_loop_run(mainloop);\n\nerror:\n    if (source_tmo) {\n        g_source_unref(source_tmo);\n    }\n    if (source_idle) {\n        g_source_unref(source_idle);\n    }\n    if (context) {\n        g_main_context_unref(context);\n    }\n    if (mainloop) {\n        g_main_loop_unref(mainloop);\n    }\n    if (session_bus) {\n        g_dbus_connection_close_sync(session_bus, NULL, NULL);\n        g_object_unref(session_bus);\n    }\n    if (system_bus) {\n        g_dbus_connection_close_sync(system_bus, NULL, NULL);\n        g_object_unref(system_bus);\n    }\n    if (err) {\n        g_error_free(err);\n    }\n\n    LS_PTH_CHECK(pthread_mutex_destroy(&args.mtx));\n}\n\nLuastatusPluginIface luastatus_plugin_iface_v1 = {\n    .init = init,\n    .register_funcs = register_funcs,\n    .run = run,\n    .destroy = destroy,\n};\n"
  },
  {
    "path": "plugins/dbus/load_lualib.c",
    "content": "/*\n * Copyright (C) 2026  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#include \"load_lualib.h\"\n#include <lua.h>\n#include <lauxlib.h>\n#include <string.h>\n#include \"include/sayf_macros.h\"\n#include \"include/plugin_data_v1.h\"\n\nstatic void report_and_pop_error(LuastatusPluginData *pd, lua_State *L, int rc)\n{\n    const char *msg = lua_tostring(L, -1);\n    if (!msg) {\n        msg = \"(error object cannot be converted to string)\";\n    }\n    LS_ERRF(pd, \"cannot load lualib.lua: [%d] %s\", rc, msg);\n\n    lua_pop(L, 1); // L: ?\n}\n\nbool load_lualib(LuastatusPluginData *pd, lua_State *L)\n{\n    static const char *LUA_SOURCE =\n#include \"lualib.generated.inc\"\n    ;\n    int rc;\n\n    // L: ? table\n    rc = luaL_loadbuffer(L, LUA_SOURCE, strlen(LUA_SOURCE), \"<bundled lualib.lua>\");\n    if (rc != 0) {\n        // L: ? table error\n        report_and_pop_error(pd, L, rc);\n        // L: ? table\n        return false;\n    }\n    // L: ? table chunk\n    lua_pushvalue(L, -2); // L: ? table chunk table\n\n    rc = lua_pcall(L, 1, 0, 0);\n    if (rc != 0) {\n        // L: ? table error\n        report_and_pop_error(pd, L, rc);\n        // L: ? table\n        return false;\n    }\n    // L: ? table\n\n    return true;\n}\n"
  },
  {
    "path": "plugins/dbus/load_lualib.h",
    "content": "/*\n * Copyright (C) 2026  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#pragma once\n\n#include <stdbool.h>\n#include <lua.h>\n#include \"include/plugin_data_v1.h\"\n\nbool load_lualib(LuastatusPluginData *pd, lua_State *L);\n"
  },
  {
    "path": "plugins/dbus/lualib.lua",
    "content": "local PLUGIN_TABLE = ...\nlocal DT_LL = PLUGIN_TABLE.dbustypes_lowlevel\n\n-- Map with index: returns a new array 'ys' such that #ys == #xs and\n--   ys[i] = f(xs[i], i).\nlocal function imap(xs, f)\n\tlocal res = {}\n\tfor i = 1, #xs do\n\t\tres[i] = f(xs[i], i)\n\tend\n\treturn res\nend\n\n-- This table contains true for all type specifiers that specify a simple type.\nlocal IS_TYPE_SIMPLE = {\n\tb = true,\n\ty = true,\n\tn = true,\n\tq = true,\n\ti = true,\n\tu = true,\n\tx = true,\n\tt = true,\n\th = true,\n\td = true,\n\ts = true,\n\to = true,\n\tg = true,\n\tv = true,\n}\n\n-- Parses fmt string 'fmt' starting with (1-based) position 'i'.\n-- Returns: 'dtype, j', where:\n--  * 'dtype' is the parsed D-Bus type object;\n--  * 'j' is the (1-based) position of the \"leftover\" (the part after the parsed prefix).\nlocal function parse_dtype_impl(fmt, i)\n\tassert(i <= #fmt, 'fmt string terminated too early')\n\tlocal c = fmt:sub(i, i)\n\tif IS_TYPE_SIMPLE[c] then\n\t\treturn DT_LL.mktype_simple(c), i + 1\n\tend\n\tif c == 'a' then\n\t\tlocal elem_dtype\n\t\telem_dtype, i = parse_dtype_impl(fmt, i + 1)\n\t\treturn DT_LL.mktype_array(elem_dtype), i\n\tend\n\tif c == '(' then\n\t\ti = i + 1\n\t\tlocal items_dtypes = {}\n\t\twhile fmt:sub(i, i) ~= ')' do\n\t\t\tlocal item_dtype\n\t\t\titem_dtype, i = parse_dtype_impl(fmt, i)\n\t\t\titems_dtypes[#items_dtypes + 1] = item_dtype\n\t\tend\n\t\treturn DT_LL.mktype_tuple(items_dtypes), i + 1\n\tend\n\tif c == '{' then\n\t\ti = i + 1\n\t\tlocal k_dtype, v_dtype\n\t\tk_dtype, i = parse_dtype_impl(fmt, i)\n\t\tv_dtype, i = parse_dtype_impl(fmt, i)\n\t\tassert(fmt:sub(i, i) == '}', 'invalid dict entry type fmt')\n\t\treturn DT_LL.mktype_dict_entry(k_dtype, v_dtype), i + 1\n\tend\n\terror(string.format('invalid type specifier \"%s\"', c))\nend\n\n-- Parses the whole fmt string. Returns the parsed D-Bus type object.\nlocal function parse_dtype(fmt)\n\tassert(type(fmt) == 'string', 'fmt string must be of string type')\n\n\tlocal dtype, i = parse_dtype_impl(fmt, 1)\n\tassert(i == #fmt + 1, 'extra data after fmt string')\n\treturn dtype\nend\n\n-- Converts a Lua value 'x' into a D-Bus value object of type 'dtype'.\nlocal function mkval_of_dtype(dtype, x)\n\tlocal category = dtype:get_category()\n\tif category == 'simple' then\n\t\treturn DT_LL.mkval_simple(dtype, x)\n\tend\n\tif category == 'array' then\n\t\tassert(type(x) == 'table', 'array type requires a table argument')\n\t\tlocal elem_dtype = dtype:get_elem_type()\n\t\tlocal elems = imap(x, function(y, _) return mkval_of_dtype(elem_dtype, y) end)\n\t\treturn DT_LL.mkval_array(elem_dtype, elems)\n\tend\n\tif category == 'tuple' then\n\t\tassert(type(x) == 'table', 'tuple type requires a table argument')\n\t\tlocal item_dtypes = dtype:get_item_types()\n\t\tassert(#x == #item_dtypes, 'length of given array does not match the # of elements in tuple')\n\t\tlocal items = imap(x, function(y, i) return mkval_of_dtype(item_dtypes[i], y) end)\n\t\treturn DT_LL.mkval_tuple(items)\n\tend\n\tif category == 'dict_entry' then\n\t\tassert(type(x) == 'table', 'dict entry type requires a table argument')\n\t\tlocal dtype_k, dtype_v = dtype:get_kv_types()\n\t\tassert(#x == 2, 'dict entry type requires array of size 2')\n\t\tlocal k = mkval_of_dtype(dtype_k, x[1])\n\t\tlocal v = mkval_of_dtype(dtype_v, x[2])\n\t\treturn DT_LL.mkval_dict_entry(k, v)\n\tend\n\terror('unexpected type') -- this should never happen\nend\n\n-- Converts a Lua value 'x' into a D-Bus value object of type given by fmt string 'fmt'.\nlocal function mkval_from_fmt(fmt, x)\n\treturn mkval_of_dtype(parse_dtype(fmt), x)\nend\n\nPLUGIN_TABLE.dbustypes = {\n\tmkval_from_fmt = mkval_from_fmt,\n\tmkval_of_dtype = mkval_of_dtype,\n\tparse_dtype = parse_dtype,\n}\n"
  },
  {
    "path": "plugins/dbus/zoo/README.txt",
    "content": "This stuff is called zoo because it's a kind of interactive zoo.\n\n---\n\n\"uncvt\" means \"unconvert\": files\n\n    zoo_uncvt_{type,val}.{c,h}\n\ndo the inverse of what files\n\n    ../cvt.{c,h}\n\ndo: the latter can convert from GVariant objects to Lua values, the former\nprovide a mechanism to create GVariant objects from within Lua.\n"
  },
  {
    "path": "plugins/dbus/zoo/zoo.c",
    "content": "/*\n * Copyright (C) 2026  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#include \"zoo.h\"\n#include <stdbool.h>\n#include <stdio.h>\n#include <string.h>\n#include <stdlib.h>\n\n#include <lua.h>\n\n#include <glib.h>\n#include <glib-object.h>\n#include <gio/gio.h>\n\n#include \"libls/ls_alloc_utils.h\"\n#include \"libls/ls_panic.h\"\n\n#include \"zoo_call_params.h\"\n#include \"zoo_uncvt_type.h\"\n#include \"zoo_uncvt_val.h\"\n#include \"../cvt.h\"\n#include \"../bustype2idx.h\"\n\nstatic void failure(lua_State *L, GError *err)\n{\n    lua_pushboolean(L, 0);\n    lua_pushstring(L, err->message);\n}\n\nstatic void success(lua_State *L, GVariant *res)\n{\n    lua_pushboolean(L, 1);\n    cvt(L, res);\n}\n\nstatic const char *lookupf(Zoo_CallParams *p, const char *key)\n{\n    for (Zoo_StrField *f = p->str_fields; f->key; ++f) {\n        if (strcmp(key, f->key) == 0) {\n            return f->value;\n        }\n    }\n    char buf[128];\n    snprintf(buf, sizeof(buf), \"lookupf: cannot find string field '%s'\", key);\n    LS_PANIC(buf);\n}\n\nstruct Zoo {\n    GDBusConnection *conns[2];\n};\n\nstatic int do_the_bloody_thing(\n    lua_State *L,\n    Zoo *z,\n    Zoo_CallParams *p,\n    const char *dest,\n    const char *object_path,\n    const char *interface_name,\n    const char *method_name,\n    GVariant *args)\n{\n    GDBusConnection *conn = z->conns[bustype2idx(p->bus_type)];\n    if (!conn) {\n        GError *err = NULL;\n        conn = g_bus_get_sync(p->bus_type, NULL, &err);\n        if (!conn) {\n            LS_ASSERT(err != NULL);\n            failure(L, err);\n            g_error_free(err);\n\n            // Dispose of /args/.\n            if (g_variant_is_floating(args)) {\n                g_variant_unref(args);\n            }\n\n            return 2;\n        }\n        LS_ASSERT(err == NULL);\n        z->conns[bustype2idx(p->bus_type)] = conn;\n    }\n\n    GError *err = NULL;\n    GVariant *res = g_dbus_connection_call_sync(\n        conn,\n        /*bus_name=*/ dest,\n        /*object_path=*/ object_path,\n        /*interface_name=*/ interface_name,\n        /*method_name=*/ method_name,\n        /*parameters=*/ args,\n        /*reply_type=*/ NULL,\n        /*flags=*/ p->flags,\n        /*timeout_ms=*/ p->tmo_ms,\n        /*cancellable=*/ NULL,\n        /*error=*/ &err\n    );\n    if (res) {\n        LS_ASSERT(err == NULL);\n        success(L, res);\n        g_variant_unref(res);\n    } else {\n        LS_ASSERT(err != NULL);\n        failure(L, err);\n        g_error_free(err);\n    }\n    return 2;\n}\n\nstatic int l_call_method(lua_State *L)\n{\n    Zoo *z = lua_touserdata(L, lua_upvalueindex(1));\n\n    Zoo_StrField str_fields[] = {\n        {.key = \"dest\"},\n        {.key = \"object_path\"},\n        {.key = \"interface\"},\n        {.key = \"method\"},\n        {0},\n    };\n    Zoo_CallParams p = {\n        .str_fields = str_fields,\n        .gvalue_field_name = \"args\",\n        .gvalue_field_must_be_tuple = true,\n    };\n    zoo_call_params_parse(L, &p, 1);\n\n    int nret = do_the_bloody_thing(\n        L, z, &p,\n        /*dest=*/ lookupf(&p, \"dest\"),\n        /*object_path=*/ lookupf(&p, \"object_path\"),\n        /*interface_name=*/ lookupf(&p, \"interface\"),\n        /*method_name=*/ lookupf(&p, \"method\"),\n        /*args=*/ p.gvalue\n    );\n    zoo_call_params_free(&p);\n    return nret;\n}\n\nstatic int l_call_method_str(lua_State *L)\n{\n    Zoo *z = lua_touserdata(L, lua_upvalueindex(1));\n\n    Zoo_StrField str_fields[] = {\n        {.key = \"dest\"},\n        {.key = \"object_path\"},\n        {.key = \"interface\"},\n        {.key = \"method\"},\n        {.key = \"arg_str\", .nullable = true},\n        {0},\n    };\n    Zoo_CallParams p = {\n        .str_fields = str_fields,\n    };\n    zoo_call_params_parse(L, &p, 1);\n\n    const char *arg_str = lookupf(&p, \"arg_str\");\n    GVariant *args = arg_str ?\n        g_variant_new(\"(s)\", arg_str) :\n        g_variant_new(\"()\") ;\n\n    int nret = do_the_bloody_thing(\n        L, z, &p,\n        /*dest=*/ lookupf(&p, \"dest\"),\n        /*object_path=*/ lookupf(&p, \"object_path\"),\n        /*interface_name=*/ lookupf(&p, \"interface\"),\n        /*method_name=*/ lookupf(&p, \"method\"),\n        /*args=*/ args\n    );\n    zoo_call_params_free(&p);\n    return nret;\n}\n\nstatic int l_get_property(lua_State *L)\n{\n    Zoo *z = lua_touserdata(L, lua_upvalueindex(1));\n\n    Zoo_StrField str_fields[] = {\n        {.key = \"dest\"},\n        {.key = \"object_path\"},\n        {.key = \"interface\"},\n        {.key = \"property_name\"},\n        {0},\n    };\n    Zoo_CallParams p = {\n        .str_fields = str_fields,\n    };\n    zoo_call_params_parse(L, &p, 1);\n\n    int nret = do_the_bloody_thing(\n        L, z, &p,\n        /*dest=*/ lookupf(&p, \"dest\"),\n        /*object_path=*/ lookupf(&p, \"object_path\"),\n        /*interface_name=*/ \"org.freedesktop.DBus.Properties\",\n        /*method_name=*/ \"Get\",\n        /*args=*/ g_variant_new(\n            \"(ss)\",\n            lookupf(&p, \"interface\"),\n            lookupf(&p, \"property_name\")\n        )\n    );\n    zoo_call_params_free(&p);\n    return nret;\n}\n\nstatic int l_get_all_properties(lua_State *L)\n{\n    Zoo *z = lua_touserdata(L, lua_upvalueindex(1));\n\n    Zoo_StrField str_fields[] = {\n        {.key = \"dest\"},\n        {.key = \"object_path\"},\n        {.key = \"interface\"},\n        {0},\n    };\n    Zoo_CallParams p = {\n        .str_fields = str_fields,\n    };\n    zoo_call_params_parse(L, &p, 1);\n\n    int nret = do_the_bloody_thing(\n        L, z, &p,\n        /*dest=*/ lookupf(&p, \"dest\"),\n        /*object_path=*/ lookupf(&p, \"object_path\"),\n        /*interface_name=*/ \"org.freedesktop.DBus.Properties\",\n        /*method_name=*/ \"GetAll\",\n        /*args=*/ g_variant_new(\n            \"(s)\",\n            lookupf(&p, \"interface\")\n        )\n    );\n    zoo_call_params_free(&p);\n    return nret;\n}\n\nstatic int l_set_property_str(lua_State *L)\n{\n    Zoo *z = lua_touserdata(L, lua_upvalueindex(1));\n\n    Zoo_StrField str_fields[] = {\n        {.key = \"dest\"},\n        {.key = \"object_path\"},\n        {.key = \"interface\"},\n        {.key = \"property_name\"},\n        {.key = \"value_str\"},\n        {0},\n    };\n    Zoo_CallParams p = {\n        .str_fields = str_fields,\n    };\n    zoo_call_params_parse(L, &p, 1);\n\n    int nret = do_the_bloody_thing(\n        L, z, &p,\n        /*dest=*/ lookupf(&p, \"dest\"),\n        /*object_path=*/ lookupf(&p, \"object_path\"),\n        /*interface_name=*/ \"org.freedesktop.DBus.Properties\",\n        /*method_name=*/ \"Set\",\n        /*args=*/ g_variant_new(\n            \"(ssv)\",\n            lookupf(&p, \"interface\"),\n            lookupf(&p, \"property_name\"),\n            g_variant_new(\"s\", lookupf(&p, \"value_str\"))\n        )\n    );\n    zoo_call_params_free(&p);\n    return nret;\n}\n\nstatic int l_set_property(lua_State *L)\n{\n    Zoo *z = lua_touserdata(L, lua_upvalueindex(1));\n\n    Zoo_StrField str_fields[] = {\n        {.key = \"dest\"},\n        {.key = \"object_path\"},\n        {.key = \"interface\"},\n        {.key = \"property_name\"},\n        {0},\n    };\n    Zoo_CallParams p = {\n        .str_fields = str_fields,\n        .gvalue_field_name = \"value\",\n    };\n    zoo_call_params_parse(L, &p, 1);\n\n    int nret = do_the_bloody_thing(\n        L, z, &p,\n        /*dest=*/ lookupf(&p, \"dest\"),\n        /*object_path=*/ lookupf(&p, \"object_path\"),\n        /*interface_name=*/ \"org.freedesktop.DBus.Properties\",\n        /*method_name=*/ \"Set\",\n        /*args=*/ g_variant_new(\n            \"(ssv)\",\n            lookupf(&p, \"interface\"),\n            lookupf(&p, \"property_name\"),\n            g_variant_new_variant(p.gvalue)\n        )\n    );\n    zoo_call_params_free(&p);\n    return nret;\n}\n\nZoo *zoo_new(void)\n{\n    Zoo *z = LS_XNEW(Zoo, 1);\n    *z = (Zoo) {0};\n    return z;\n}\n\nvoid zoo_register_funcs(Zoo *z, lua_State *L)\n{\n    // L: ? table\n    lua_newtable(L); // L: ? table table\n    zoo_uncvt_type_register_mt_and_funcs(L); // L: ? table table\n    zoo_uncvt_val_register_mt_and_funcs(L); // L: ? table table\n    lua_setfield(L, -2, \"dbustypes_lowlevel\"); // L: ? table\n\n#define REG(Name_, F_) \\\n    (lua_pushlightuserdata(L, z), \\\n    lua_pushcclosure(L, (F_), 1), \\\n    lua_setfield(L, -2, (Name_)))\n\n    REG(\"call_method\", l_call_method);\n    REG(\"call_method_str\", l_call_method_str);\n    REG(\"get_property\", l_get_property);\n    REG(\"get_all_properties\", l_get_all_properties);\n    REG(\"set_property\", l_set_property);\n    REG(\"set_property_str\", l_set_property_str);\n\n#undef REG\n}\n\nvoid zoo_destroy(Zoo *z)\n{\n    for (size_t i = 0; i < 2; ++i) {\n        GDBusConnection *conn = z->conns[i];\n        if (conn) {\n            g_dbus_connection_close_sync(conn, NULL, NULL);\n            g_object_unref(conn);\n        }\n    }\n    free(z);\n}\n"
  },
  {
    "path": "plugins/dbus/zoo/zoo.h",
    "content": "/*\n * Copyright (C) 2026  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#pragma once\n\n#include <lua.h>\n\nstruct Zoo;\ntypedef struct Zoo Zoo;\n\nZoo *zoo_new(void);\n\nvoid zoo_register_funcs(Zoo *z, lua_State *L);\n\nvoid zoo_destroy(Zoo *z);\n"
  },
  {
    "path": "plugins/dbus/zoo/zoo_call_params.c",
    "content": "/*\n * Copyright (C) 2026  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#include \"zoo_call_params.h\"\n#include <stdlib.h>\n#include <string.h>\n\n#include <lua.h>\n#include <lauxlib.h>\n\n#include \"libls/ls_alloc_utils.h\"\n#include \"libls/ls_panic.h\"\n#include \"libls/ls_time_utils.h\"\n\n#include \"zoo_uncvt_val.h\"\n#include \"zoo_call_prot.h\"\n\nstatic void check_type(lua_State *L, int pos, const char *key, int expected_type, bool nullable)\n{\n    int t = lua_type(L, pos);\n    if (t != expected_type) {\n        (void) luaL_error(\n            L, \"%s: expected %s%s, found %s\",\n            key,\n            lua_typename(L, expected_type),\n            nullable ? \" or nil\" : \"\",\n            lua_typename(L, t));\n        LS_MUST_BE_UNREACHABLE();\n    }\n}\n\nstatic void do_str(lua_State *L, const char *key, char **dst, bool nullable)\n{\n    // L: ? table\n    lua_getfield(L, 1, key); // L: ? table value\n    if (nullable && lua_isnil(L, -1)) {\n        goto done;\n    }\n\n    check_type(L, -1, key, LUA_TSTRING, nullable);\n\n    size_t ns;\n    const char *s = lua_tolstring(L, -1, &ns);\n    if (!g_utf8_validate_len(s, ns, NULL)) {\n        (void) luaL_error(L, \"%s: value contains invalid UTF-8\", key);\n        LS_MUST_BE_UNREACHABLE();\n    }\n\n    *dst = ls_xstrdup(s);\ndone:\n    lua_pop(L, 1); // L: ? table\n}\n\nstatic void do_gvalue(lua_State *L, const char *key, GVariant **dst, bool must_be_tuple)\n{\n    // L: ? table\n    lua_getfield(L, 1, key); // L: ? table value\n\n    GVariant *v = zoo_uncvt_val_fetch_newref(L, -1, key);\n    *dst = v;\n    if (must_be_tuple) {\n        if (!g_variant_type_is_tuple(g_variant_get_type(v))) {\n            (void) luaL_error(L, \"%s: is not of tuple type\", key);\n            LS_MUST_BE_UNREACHABLE();\n        }\n    }\n\n    lua_pop(L, 1); // L: ? table\n}\n\nstatic void maybe_do_bool(lua_State *L, const char *key, bool *dst)\n{\n    // L: ? table\n    lua_getfield(L, 1, key); // L: ? table value\n    if (lua_isnil(L, -1)) {\n        goto done;\n    }\n\n    check_type(L, -1, key, LUA_TBOOLEAN, true);\n\n    *dst = lua_toboolean(L, -1);\ndone:\n    lua_pop(L, 1); // L: ? table\n}\n\nstatic void maybe_do_timeout(lua_State *L, const char *key, int *dst)\n{\n    // L: ? table\n    lua_getfield(L, 1, key); // L: ? table value\n    if (lua_isnil(L, -1)) {\n        goto done;\n    }\n\n    check_type(L, -1, key, LUA_TNUMBER, true);\n\n    double d = lua_tonumber(L, -1);\n\n    LS_TimeDelta TD;\n    if (ls_double_to_TD_checked(d, &TD)) {\n        *dst = ls_TD_to_poll_ms_tmo(TD);\n    } else {\n        (void) luaL_error(L, \"%s: invalid timeout value\", key);\n        LS_MUST_BE_UNREACHABLE();\n    }\n\ndone:\n    lua_pop(L, 1); // L: ? table\n}\n\nstatic void do_bus_type(lua_State *L, const char *key, GBusType *dst)\n{\n    // L: ? table\n    lua_getfield(L, 1, key); // L: ? table value\n\n    check_type(L, -1, key, LUA_TSTRING, false);\n\n    const char *s = lua_tostring(L, -1);\n    if (strcmp(s, \"system\") == 0) {\n        *dst = G_BUS_TYPE_SYSTEM;\n    } else if (strcmp(s, \"session\") == 0) {\n        *dst = G_BUS_TYPE_SESSION;\n    } else {\n        (void) luaL_error(L, \"%s: expected either 'system' or 'session'\", key);\n        LS_MUST_BE_UNREACHABLE();\n    }\n\n    lua_pop(L, 1); // L: ? table\n}\n\nstatic int do_parse_throwable(lua_State *L)\n{\n    Zoo_CallParams *p = lua_touserdata(L, lua_upvalueindex(1));\n\n    LS_ASSERT(lua_gettop(L) == 1);\n\n    do_bus_type(L, \"bus\", &p->bus_type);\n\n    for (Zoo_StrField *f = p->str_fields; f->key; ++f) {\n        do_str(L, f->key, &f->value, f->nullable);\n    }\n\n    if (p->gvalue_field_name) {\n        do_gvalue(L, p->gvalue_field_name, &p->gvalue, p->gvalue_field_must_be_tuple);\n    }\n\n    bool no_autostart = false;\n    maybe_do_bool(L, \"flag_no_autostart\", &no_autostart);\n    p->flags = no_autostart ? G_DBUS_CALL_FLAGS_NO_AUTO_START : 0;\n\n    p->tmo_ms = -1;\n    maybe_do_timeout(L, \"timeout\", &p->tmo_ms);\n\n    return 0;\n}\n\nvoid zoo_call_params_free(Zoo_CallParams *p)\n{\n    for (Zoo_StrField *f = p->str_fields; f->key; ++f) {\n        free(f->value);\n    }\n    if (p->gvalue) {\n        g_variant_unref(p->gvalue);\n    }\n}\n\nvoid zoo_call_params_parse(lua_State *L, Zoo_CallParams *p, int arg)\n{\n    luaL_checktype(L, arg, LUA_TTABLE);\n\n    lua_pushvalue(L, arg);\n    if (!zoo_call_prot(L, 1, 0, do_parse_throwable, p)) {\n        zoo_call_params_free(p);\n        lua_error(L);\n    }\n}\n"
  },
  {
    "path": "plugins/dbus/zoo/zoo_call_params.h",
    "content": "/*\n * Copyright (C) 2026  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#pragma once\n\n#include <lua.h>\n#include <glib.h>\n#include <gio/gio.h>\n#include <stdbool.h>\n\ntypedef struct {\n    const char *key;\n    char *value;\n    bool nullable;\n} Zoo_StrField;\n\ntypedef struct {\n    Zoo_StrField *str_fields;\n\n    const char *gvalue_field_name;\n    bool gvalue_field_must_be_tuple;\n    GVariant *gvalue;\n\n    GBusType bus_type;\n    GDBusCallFlags flags;\n    int tmo_ms;\n} Zoo_CallParams;\n\nvoid zoo_call_params_parse(lua_State *L, Zoo_CallParams *p, int arg);\n\nvoid zoo_call_params_free(Zoo_CallParams *p);\n"
  },
  {
    "path": "plugins/dbus/zoo/zoo_call_prot.c",
    "content": "/*\n * Copyright (C) 2026  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#include \"zoo_call_prot.h\"\n#include <stdbool.h>\n#include <lua.h>\n\nbool zoo_call_prot(lua_State *L, int nargs, int nresults, lua_CFunction f, void *f_ud)\n{\n    // L: ? args\n    lua_pushlightuserdata(L, f_ud); // L: ? args ud\n    lua_pushcclosure(L, f, 1); // L: ? args func\n    lua_insert(L, -(nargs + 1)); // L: ? func args\n    return lua_pcall(L, nargs, nresults, 0) == 0;\n}\n"
  },
  {
    "path": "plugins/dbus/zoo/zoo_call_prot.h",
    "content": "/*\n * Copyright (C) 2026  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#pragma once\n\n#include <lua.h>\n#include <stdbool.h>\n\nbool zoo_call_prot(lua_State *L, int args, int nresults, lua_CFunction f, void *f_ud);\n"
  },
  {
    "path": "plugins/dbus/zoo/zoo_checkudata.c",
    "content": "/*\n * Copyright (C) 2026  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#include \"zoo_checkudata.h\"\n#include <lua.h>\n#include <lauxlib.h>\n#include \"libls/ls_panic.h\"\n\nvoid *zoo_checkudata(lua_State *L, int pos, const char *tname, const char *what)\n{\n    // L: ?\n    void *ud = lua_touserdata(L, pos);\n    if (!ud) {\n        goto error;\n    }\n    if (!lua_getmetatable(L, pos)) {\n        goto error;\n    }\n    // L: ? actual_mt\n    lua_getfield(L, LUA_REGISTRYINDEX, tname);\n    // L: ? actual_mt expected_mt\n    if (!lua_rawequal(L, -1, -2)) {\n        goto error;\n    }\n    lua_pop(L, 2); // L: ?\n    return ud;\nerror:\n    (void) luaL_error(L, \"%s: is not a '%s' userdata value\", what, tname);\n    LS_MUST_BE_UNREACHABLE();\n}\n"
  },
  {
    "path": "plugins/dbus/zoo/zoo_checkudata.h",
    "content": "/*\n * Copyright (C) 2026  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#pragma once\n\n#include <lua.h>\n\nvoid *zoo_checkudata(lua_State *L, int pos, const char *tname, const char *what);\n"
  },
  {
    "path": "plugins/dbus/zoo/zoo_mt.c",
    "content": "/*\n * Copyright (C) 2026  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#include \"zoo_mt.h\"\n\n#include <lua.h>\n#include <lauxlib.h>\n\nvoid zoo_mt_begin(lua_State *L, const char *mt_name)\n{\n    // L: ?\n    luaL_newmetatable(L, mt_name); // L: ? mt\n\n    lua_pushvalue(L, -1); // L: ? mt mt\n    lua_setfield(L, -2, \"__index\"); // L: ? mt\n}\n\nvoid zoo_mt_add_method(lua_State *L, const char *name, lua_CFunction f)\n{\n    lua_pushcfunction(L, f); // L: ? mt f\n    lua_setfield(L, -2, name); // L: ? mt\n}\n\nvoid zoo_mt_end(lua_State *L)\n{\n    // L: ? mt\n    lua_pop(L, 1); // L: ?\n}\n"
  },
  {
    "path": "plugins/dbus/zoo/zoo_mt.h",
    "content": "/*\n * Copyright (C) 2026  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#pragma once\n\n#include <lua.h>\n\nvoid zoo_mt_begin(lua_State *L, const char *mt_name);\n\nvoid zoo_mt_add_method(lua_State *L, const char *name, lua_CFunction f);\n\nvoid zoo_mt_end(lua_State *L);\n"
  },
  {
    "path": "plugins/dbus/zoo/zoo_uncvt_type.c",
    "content": "/*\n * Copyright (C) 2026  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#include \"zoo_uncvt_type.h\"\n#include <stdlib.h>\n#include <string.h>\n#include <stdbool.h>\n#include <lua.h>\n#include <lauxlib.h>\n#include <glib.h>\n#include \"libls/ls_alloc_utils.h\"\n#include \"libls/ls_panic.h\"\n#include \"libls/ls_lua_compat.h\"\n#include \"zoo_call_prot.h\"\n#include \"zoo_checkudata.h\"\n#include \"zoo_mt.h\"\n\ntypedef struct {\n    // This is an owning handle, not a borrow.\n    // May be NULL, which means it's already been \"finalized\" (garbage-collected).\n    GVariantType *t;\n} Tobj;\n\nstatic const char *MT_NAME = \"io.shdown.luastatus.plugin.dbus.official.Type\";\n\nstatic Tobj *fetch_tobj(lua_State *L, int pos, const char *what)\n{\n    return zoo_checkudata(L, pos, MT_NAME, what);\n}\n\n// Returns a borrow.\nstatic const GVariantType *fetch_gtype_borrow(lua_State *L, int pos, const char *what)\n{\n    Tobj *tobj = fetch_tobj(L, pos, what);\n    if (!tobj->t) {\n        (void) luaL_error(L, \"this type object has already been finalized\");\n        LS_MUST_BE_UNREACHABLE();\n    }\n    return tobj->t;\n}\n\n// /t/ must be an owning handle, which then gets \"stolen\".\nstatic void make_tobj_steal(lua_State *L, GVariantType *t)\n{\n    LS_ASSERT(t != NULL);\n\n    // L: ?\n    Tobj *tobj = lua_newuserdata(L, sizeof(Tobj)); // L: ? ud\n    tobj->t = t;\n\n    luaL_getmetatable(L, MT_NAME); // L: ? ud mt\n    lua_setmetatable(L, -2); // L: ? ud\n}\n\n// /t/ is a borrow, which gets copied.\nstatic void make_tobj_copy(lua_State *L, const GVariantType *t)\n{\n    LS_ASSERT(t != NULL);\n    make_tobj_steal(L, g_variant_type_copy(t));\n}\n\nstatic int tobj_gc(lua_State *L)\n{\n    Tobj *tobj = fetch_tobj(L, 1, \"argument #1\");\n    if (tobj->t) {\n        g_variant_type_free(tobj->t);\n        tobj->t = NULL;\n    }\n    return 0;\n}\n\nstatic int l_equals_to(lua_State *L)\n{\n    // Both /t1/ and /t2/ are borrowed (STACK).\n    const GVariantType *t1 = fetch_gtype_borrow(L, 1, \"argument #1\");\n    const GVariantType *t2 = fetch_gtype_borrow(L, 2, \"argument #2\");\n\n    lua_pushboolean(L, g_variant_type_equal(t1, t2));\n    return 1;\n}\n\nstatic int l_get_type_string(lua_State *L)\n{\n    // /t/ is borrowed (STACK).\n    const GVariantType *t = fetch_gtype_borrow(L, 1, \"argument #1\");\n\n    size_t ns = g_variant_type_get_string_length(t);\n    const char *s = g_variant_type_peek_string(t);\n\n    lua_pushlstring(L, s, ns);\n    return 1;\n}\n\nstatic int l_get_category(lua_State *L)\n{\n    // /t/ is borrowed (STACK).\n    const GVariantType *t = fetch_gtype_borrow(L, 1, \"argument #1\");\n\n    if (g_variant_type_is_basic(t) || g_variant_type_is_variant(t)) {\n        lua_pushstring(L, \"simple\");\n    } else if (g_variant_type_is_array(t)) {\n        lua_pushstring(L, \"array\");\n    } else if (g_variant_type_is_dict_entry(t)) {\n        lua_pushstring(L, \"dict_entry\");\n    } else if (g_variant_type_is_tuple(t)) {\n        lua_pushstring(L, \"tuple\");\n    } else {\n        return luaL_argerror(L, 1, \"unknown type\");\n    }\n    return 1;\n}\n\nstatic int l_is_basic(lua_State *L)\n{\n    // /t/ is borrowed (STACK).\n    const GVariantType *t = fetch_gtype_borrow(L, 1, \"argument #1\");\n\n    lua_pushboolean(L, !!g_variant_type_is_basic(t));\n    return 1;\n}\n\nstatic int l_get_item_types(lua_State *L)\n{\n    // /t/ is borrowed (STACK).\n    const GVariantType *t = fetch_gtype_borrow(L, 1, \"argument #1\");\n\n    if (!g_variant_type_is_tuple(t)) {\n        return luaL_argerror(L, 1, \"is not a tuple type\");\n    }\n\n    // L: ?\n    size_t n = g_variant_type_n_items(t);\n    if (n > (size_t) LS_LUA_MAXI) {\n        return luaL_error(L, \"tuple type has too many elements\");\n    }\n    lua_createtable(L, ls_lua_num_prealloc(n), 0); // L: ? arr\n\n    size_t i = 1;\n    for (\n        const GVariantType *item_t = g_variant_type_first(t);\n        item_t;\n        item_t = g_variant_type_next(item_t))\n    {\n        // L: ? arr\n        make_tobj_copy(L, item_t); // L: ? arr type\n        lua_rawseti(L, -2, i); // L: ? arr\n        ++i;\n    }\n    return 1;\n}\n\nstatic int l_get_elem_type(lua_State *L)\n{\n    // /t/ is borrowed (STACK).\n    const GVariantType *t = fetch_gtype_borrow(L, 1, \"argument #1\");\n\n    if (!g_variant_type_is_array(t)) {\n        return luaL_argerror(L, 1, \"is not an array type\");\n    }\n\n    make_tobj_copy(L, g_variant_type_element(t));\n    return 1;\n}\n\nstatic int l_get_kv_types(lua_State *L)\n{\n    // /t/ is borrowed (STACK).\n    const GVariantType *t = fetch_gtype_borrow(L, 1, \"argument #1\");\n\n    if (!g_variant_type_is_dict_entry(t)) {\n        return luaL_argerror(L, 1, \"is not a dict entry type\");\n    }\n\n    // L: ?\n    make_tobj_copy(L, g_variant_type_key(t)); // L: ? ktype\n    make_tobj_copy(L, g_variant_type_value(t)); // L: ? ktype vtype\n    return 2;\n}\n\nstatic int l_mktype_simple(lua_State *L)\n{\n    static const char VALID[] = {\n        'b', // boolean\n        'y', // byte\n        'n', // int16\n        'q', // uint16\n        'i', // int32\n        'u', // uint32\n        'x', // int64\n        't', // uint64\n        'h', // handle\n        'd', // double\n        's', // string\n        'o', // object_path\n        'g', // signature\n        'v', // variant\n    };\n\n    size_t ns;\n    const char *s = luaL_checklstring(L, 1, &ns);\n    if (ns != 1) {\n        goto bad;\n    }\n    if (memchr(VALID, (unsigned char) s[0], sizeof(VALID)) == NULL) {\n        goto bad;\n    }\n    make_tobj_steal(L, g_variant_type_new(s));\n    return 1;\nbad:\n    return luaL_argerror(L, 1, \"not a simple type name\");\n}\n\nstatic int l_mktype_array(lua_State *L)\n{\n    // /t/ is borrowed (STACK).\n    const GVariantType *t = fetch_gtype_borrow(L, 1, \"argument #1\");\n\n    make_tobj_steal(L, g_variant_type_new_array(t));\n    return 1;\n}\n\nstatic int l_mktype_dict_entry(lua_State *L)\n{\n    // Both /tk/ and /tv/ are borrowed (STACK).\n    const GVariantType *tk = fetch_gtype_borrow(L, 1, \"argument #1\");\n    const GVariantType *tv = fetch_gtype_borrow(L, 2, \"argument #2\");\n\n    if (!g_variant_type_is_basic(tk)) {\n        return luaL_argerror(L, 1, \"key type is not a basic type\");\n    }\n\n    make_tobj_steal(L, g_variant_type_new_dict_entry(tk, tv));\n    return 1;\n}\n\ntypedef struct {\n    const GVariantType **items;\n    size_t n_copied;\n} MkTupleUD;\n\nstatic int throwable_l_mktype_tuple(lua_State *L)\n{\n    MkTupleUD *ud = lua_touserdata(L, lua_upvalueindex(1));\n\n    luaL_checktype(L, 1, LUA_TTABLE);\n    size_t n = ls_lua_array_len(L, 1);\n\n    ud->items = LS_XNEW(const GVariantType *, n);\n\n    for (size_t i = 1; i <= n; ++i) {\n        lua_rawgeti(L, 1, i);\n\n        // /t/ is borrowed (ARRAY).\n        const GVariantType *t = fetch_gtype_borrow(L, -1, \"array element\");\n        // Since /t/ is ARRAY-borrowed, we copy it right away.\n        ud->items[ud->n_copied++] = g_variant_type_copy(t);\n\n        lua_pop(L, 1);\n    }\n\n    GVariantType *res = g_variant_type_new_tuple(ud->items, n);\n    make_tobj_steal(L, res);\n    return 1;\n}\n\nstatic int l_mktype_tuple(lua_State *L)\n{\n    MkTupleUD ud = {0};\n    bool ok = zoo_call_prot(L, lua_gettop(L), 1, throwable_l_mktype_tuple, &ud);\n\n    for (size_t i = 0; i < ud.n_copied; ++i) {\n        g_variant_type_free((GVariantType *) ud.items[i]);\n    }\n    free(ud.items);\n\n    if (ok) {\n        return 1;\n    } else {\n        return lua_error(L);\n    }\n}\n\nconst GVariantType *zoo_uncvt_type_fetch_borrow(lua_State *L, int pos, const char *what)\n{\n    return fetch_gtype_borrow(L, pos, what);\n}\n\nvoid zoo_uncvt_type_bake_steal(GVariantType *t, lua_State *L)\n{\n    make_tobj_steal(L, t);\n}\n\nstatic void register_mt(lua_State *L)\n{\n    zoo_mt_begin(L, MT_NAME);\n\n    zoo_mt_add_method(L, \"get_type_string\", l_get_type_string);\n    zoo_mt_add_method(L, \"get_category\", l_get_category);\n    zoo_mt_add_method(L, \"is_basic\", l_is_basic);\n\n    zoo_mt_add_method(L, \"get_item_types\", l_get_item_types);\n    zoo_mt_add_method(L, \"get_elem_type\", l_get_elem_type);\n    zoo_mt_add_method(L, \"get_kv_types\", l_get_kv_types);\n\n    zoo_mt_add_method(L, \"equals_to\", l_equals_to);\n\n    zoo_mt_add_method(L, \"__gc\", tobj_gc);\n\n    zoo_mt_end(L);\n}\n\nvoid zoo_uncvt_type_register_mt_and_funcs(lua_State *L)\n{\n    register_mt(L);\n\n#define REG(Name_, F_) (lua_pushcfunction(L, (F_)), lua_setfield(L, -2, (Name_)))\n\n    REG(\"mktype_simple\", l_mktype_simple);\n    REG(\"mktype_array\", l_mktype_array);\n    REG(\"mktype_dict_entry\", l_mktype_dict_entry);\n    REG(\"mktype_tuple\", l_mktype_tuple);\n\n#undef REG\n}\n"
  },
  {
    "path": "plugins/dbus/zoo/zoo_uncvt_type.h",
    "content": "/*\n * Copyright (C) 2026  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#pragma once\n\n#include <lua.h>\n#include <glib.h>\n\n// Returns a borrow.\nconst GVariantType *zoo_uncvt_type_fetch_borrow(lua_State *L, int pos, const char *what);\n\n// Steals /t/.\nvoid zoo_uncvt_type_bake_steal(GVariantType *t, lua_State *L);\n\nvoid zoo_uncvt_type_register_mt_and_funcs(lua_State *L);\n"
  },
  {
    "path": "plugins/dbus/zoo/zoo_uncvt_val.c",
    "content": "/*\n * Copyright (C) 2026  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#include \"zoo_uncvt_val.h\"\n#include <stdint.h>\n#include <stdbool.h>\n#include <stdio.h>\n#include <errno.h>\n#include <stdlib.h>\n\n#include <lua.h>\n#include <lauxlib.h>\n#include <glib.h>\n\n#include \"libls/ls_panic.h\"\n#include \"libls/ls_alloc_utils.h\"\n#include \"libls/ls_lua_compat.h\"\n\n#include \"zoo_checkudata.h\"\n#include \"zoo_call_prot.h\"\n#include \"zoo_uncvt_type.h\"\n#include \"zoo_mt.h\"\n#include \"../cvt.h\"\n\ntypedef struct {\n    // This is an owning handle (separate reference to GVariant), not a borrow.\n    // This is never a \"floating reference\".\n    // May be NULL, which means it's already been \"finalized\" (garbage-collected).\n    GVariant *v;\n} Vobj;\n\nstatic const char *MT_NAME = \"io.shdown.luastatus.plugin.dbus.official.Value\";\n\nstatic Vobj *fetch_vobj(lua_State *L, int pos, const char *what)\n{\n    return zoo_checkudata(L, pos, MT_NAME, what);\n}\n\nstatic GVariant *fetch_gvar_borrow(lua_State *L, int pos, const char *what)\n{\n    Vobj *vobj = fetch_vobj(L, pos, what);\n    if (!vobj->v) {\n        (void) luaL_error(L, \"this value object has already been finalized\");\n        LS_MUST_BE_UNREACHABLE();\n    }\n    return vobj->v;\n}\n\nstatic void make_vobj_steal(lua_State *L, GVariant *v)\n{\n    LS_ASSERT(v != NULL);\n    LS_ASSERT(!g_variant_is_floating(v));\n\n    // L: ?\n    Vobj *vobj = lua_newuserdata(L, sizeof(Vobj)); // L: ? ud\n    vobj->v = v;\n\n    luaL_getmetatable(L, MT_NAME); // L: ? ud mt\n    lua_setmetatable(L, -2); // L: ? ud\n}\n\nstatic void make_vobj_from_floating(lua_State *L, GVariant *v)\n{\n    LS_ASSERT(v != NULL);\n    LS_ASSERT(g_variant_is_floating(v));\n\n    make_vobj_steal(L, g_variant_ref_sink(v));\n}\n\nstatic int vobj_gc(lua_State *L)\n{\n    Vobj *vobj = fetch_vobj(L, 1, \"argument #1\");\n    if (vobj->v) {\n        g_variant_unref(vobj->v);\n        vobj->v = NULL;\n    }\n    return 0;\n}\n\nstatic bool fetch_bool(lua_State *L, int arg)\n{\n    luaL_checktype(L, arg, LUA_TBOOLEAN);\n    return lua_toboolean(L, arg);\n}\n\nstatic uint8_t fetch_byte(lua_State *L, int arg)\n{\n    int t = lua_type(L, arg);\n    if (t == LUA_TNUMBER) {\n        return lua_tointeger(L, arg);\n\n    } else if (t == LUA_TSTRING) {\n        size_t ns;\n        const char *s = lua_tolstring(L, arg, &ns);\n        if (ns != 1) {\n            return luaL_argerror(L, arg, \"string for 'byte' value is not of length 1\");\n        }\n        return s[0];\n\n    } else {\n        return luaL_argerror(L, arg, \"expected either a number of a string\");\n    }\n}\n\nstatic uint64_t fetch_int(lua_State *L, int arg, bool is_signed)\n{\n    int t = lua_type(L, arg);\n    if (t == LUA_TNUMBER) {\n        return lua_tointeger(L, arg);\n\n    } else if (t == LUA_TSTRING) {\n        const char *s = lua_tostring(L, arg);\n        char *endptr;\n        unsigned long long res;\n        errno = 0;\n        if (is_signed) {\n            res = strtoll(s, &endptr, 0);\n        } else {\n            res = strtoull(s, &endptr, 0);\n        }\n        if (errno != 0 || s[0] == '\\0' || *endptr != '\\0') {\n            const char *errmsg = is_signed ?\n                \"cannot convert this string to signed integer\" :\n                \"cannot convert this string to unsigned integer\";\n            return luaL_argerror(L, arg, errmsg);\n        }\n        return res;\n\n    } else {\n        return luaL_argerror(L, arg, \"expected either a number of a string\");\n    }\n}\n\nstatic const char *fetch_utf8(lua_State *L, int arg)\n{\n    size_t ns;\n    const char *s = luaL_checklstring(L, arg, &ns);\n\n    if (!g_utf8_validate_len(s, ns, NULL)) {\n        (void) luaL_argerror(L, arg, \"this strings contains invalid UTF-8\");\n        LS_MUST_BE_UNREACHABLE();\n    }\n\n    return s;\n}\n\nstatic GVariant *mkval_simple_impl(lua_State *L)\n{\n    // /t/ is borrowed (STACK).\n    const GVariantType *t = zoo_uncvt_type_fetch_borrow(L, 1, \"argument #1\");\n\n    if (g_variant_type_get_string_length(t) != 1) {\n        goto bad_type;\n    }\n    const char *type_str = g_variant_type_peek_string(t);\n    char type_sigil = type_str[0];\n\n    switch (type_sigil) {\n\n    case 'b': return g_variant_new_boolean(fetch_bool(L, 2));\n    case 'y': return g_variant_new_byte(fetch_byte(L, 2));\n\n    case 'n': return g_variant_new_int16(fetch_int(L, 2, true));\n    case 'q': return g_variant_new_uint16(fetch_int(L, 2, false));\n\n    case 'i': return g_variant_new_int32(fetch_int(L, 2, true));\n    case 'u': return g_variant_new_uint32(fetch_int(L, 2, false));\n\n    case 'x': return g_variant_new_int64(fetch_int(L, 2, true));\n    case 't': return g_variant_new_uint64(fetch_int(L, 2, false));\n\n    case 'd': return g_variant_new_double(luaL_checknumber(L, 2));\n\n    case 's': return g_variant_new_string(fetch_utf8(L, 2));\n\n    case 'v': return g_variant_new_variant(fetch_gvar_borrow(L, 2, \"argument #2\"));\n    }\n\n    if (type_sigil == 'o') {\n        const char *s = luaL_checkstring(L, 2);\n        if (!g_variant_is_object_path(s)) {\n            (void) luaL_argerror(L, 2, \"not a valid D-Bus object path\");\n            LS_MUST_BE_UNREACHABLE();\n        }\n        return g_variant_new_object_path(s);\n    }\n\n    if (type_sigil == 'g') {\n        const char *s = luaL_checkstring(L, 2);\n        if (!g_variant_is_signature(s)) {\n            (void) luaL_argerror(L, 2, \"not a valid D-Bus type signature\");\n            LS_MUST_BE_UNREACHABLE();\n        }\n        return g_variant_new_signature(s);\n    }\n\n    if (type_sigil == 'h') {\n        (void) luaL_error(L, \"creation of handles is not supported\");\n        LS_MUST_BE_UNREACHABLE();\n    }\n\nbad_type:\n    (void) luaL_error(L, \"not a simple type\");\n    LS_MUST_BE_UNREACHABLE();\n}\n\nstatic int l_mkval_simple(lua_State *L)\n{\n    make_vobj_from_floating(L, mkval_simple_impl(L));\n    return 1;\n}\n\nstatic int l_mkval_dict_entry(lua_State *L)\n{\n    // Both /k/ and /v/ are borrowed (STACK).\n    GVariant *k = fetch_gvar_borrow(L, 1, \"argument #1\");\n    GVariant *v = fetch_gvar_borrow(L, 2, \"argument #2\");\n\n    if (!g_variant_type_is_basic(g_variant_get_type(k))) {\n        return luaL_argerror(L, 1, \"key is not of a basic type\");\n    }\n\n    make_vobj_from_floating(L, g_variant_new_dict_entry(k, v));\n    return 1;\n}\n\ntypedef struct {\n    GVariant **items;\n    size_t n_refd;\n} MkSomethingUD;\n\nstatic void free_ud(MkSomethingUD ud)\n{\n    for (size_t i = 0; i < ud.n_refd; ++i) {\n        g_variant_unref(ud.items[i]);\n    }\n    free(ud.items);\n}\n\nstatic int throwable_l_mkval_tuple(lua_State *L)\n{\n    MkSomethingUD *ud = lua_touserdata(L, lua_upvalueindex(1));\n\n    luaL_checktype(L, 1, LUA_TTABLE);\n    size_t n = ls_lua_array_len(L, 1);\n\n    ud->items = LS_XNEW(GVariant *, n);\n\n    for (size_t i = 1; i <= n; ++i) {\n        lua_rawgeti(L, 1, i);\n\n        // /v/ is borrowed (ARRAY).\n        GVariant *v = fetch_gvar_borrow(L, -1, \"array element\");\n        // Since /v/ is ARRAY-borrowed, we ref it right away.\n        ud->items[ud->n_refd++] = g_variant_ref(v);\n\n        lua_pop(L, 1);\n    }\n\n    GVariant *res = g_variant_new_tuple(ud->items, n);\n    make_vobj_from_floating(L, res);\n    return 1;\n}\n\nstatic int l_mkval_tuple(lua_State *L)\n{\n    MkSomethingUD ud = {0};\n    bool ok = zoo_call_prot(L, lua_gettop(L), 1, throwable_l_mkval_tuple, &ud);\n\n    free_ud(ud);\n\n    if (ok) {\n        return 1;\n    } else {\n        return lua_error(L);\n    }\n}\n\nstatic int throwable_l_mkval_array(lua_State *L)\n{\n    MkSomethingUD *ud = lua_touserdata(L, lua_upvalueindex(1));\n\n    // /t/ is borrowed (STACK).\n    const GVariantType *t = zoo_uncvt_type_fetch_borrow(L, 1, \"argument #1\");\n\n    luaL_checktype(L, 2, LUA_TTABLE);\n    size_t n = ls_lua_array_len(L, 2);\n\n    ud->items = LS_XNEW(GVariant *, n);\n\n    for (size_t i = 1; i <= n; ++i) {\n        lua_rawgeti(L, 2, i);\n\n        // /v/ is borrowed (ARRAY).\n        GVariant *v = fetch_gvar_borrow(L, -1, \"array element\");\n\n        // Check the type of /v/.\n        if (!g_variant_type_equal(t, g_variant_get_type(v))) {\n            char msg[128];\n            snprintf(\n                msg, sizeof(msg),\n                \"type of array element #%zu doesn't match the passed element type\",\n                i);\n            return luaL_error(L, \"%s\", msg);\n        }\n\n        // Since /v/ is ARRAY-borrowed, we ref it right away.\n        ud->items[ud->n_refd++] = g_variant_ref(v);\n\n        lua_pop(L, 1);\n    }\n\n    GVariant *res = g_variant_new_array(t, ud->items, n);\n    make_vobj_from_floating(L, res);\n    return 1;\n}\n\nstatic int l_mkval_array(lua_State *L)\n{\n    MkSomethingUD ud = {0};\n    bool ok = zoo_call_prot(L, lua_gettop(L), 1, throwable_l_mkval_array, &ud);\n\n    free_ud(ud);\n\n    if (ok) {\n        return 1;\n    } else {\n        return lua_error(L);\n    }\n}\n\nstatic int l_get_type(lua_State *L)\n{\n    // /v/ is borrowed (STACK).\n    GVariant *v = fetch_gvar_borrow(L, 1, \"argument #1\");\n\n    GVariantType *t = g_variant_type_copy(g_variant_get_type(v));\n\n    zoo_uncvt_type_bake_steal(t, L);\n    return 1;\n}\n\nstatic int l_equals_to(lua_State *L)\n{\n    // Both /a/ and /b/ are borrowed (STACK).\n    GVariant *a = fetch_gvar_borrow(L, 1, \"argument #1\");\n    GVariant *b = fetch_gvar_borrow(L, 2, \"argument #2\");\n\n    lua_pushboolean(L, !!g_variant_equal(a, b));\n\n    return 1;\n}\n\nstatic int l_to_lua(lua_State *L)\n{\n    // /v/ is borrowed (STACK).\n    GVariant *v = fetch_gvar_borrow(L, 1, \"argument #1\");\n\n    cvt(L, v);\n    return 1;\n}\n\nGVariant *zoo_uncvt_val_fetch_newref(lua_State *L, int pos, const char *what)\n{\n    GVariant *v = fetch_gvar_borrow(L, pos, what);\n    return g_variant_ref(v);\n}\n\nstatic void register_mt(lua_State *L)\n{\n    zoo_mt_begin(L, MT_NAME);\n\n    zoo_mt_add_method(L, \"get_type\", l_get_type);\n    zoo_mt_add_method(L, \"equals_to\", l_equals_to);\n    zoo_mt_add_method(L, \"to_lua\", l_to_lua);\n\n    zoo_mt_add_method(L, \"__gc\", vobj_gc);\n\n    zoo_mt_end(L);\n}\n\nvoid zoo_uncvt_val_register_mt_and_funcs(lua_State *L)\n{\n    register_mt(L);\n\n#define REG(Name_, F_) (lua_pushcfunction(L, (F_)), lua_setfield(L, -2, (Name_)))\n\n    REG(\"mkval_simple\", l_mkval_simple);\n    REG(\"mkval_dict_entry\", l_mkval_dict_entry);\n    REG(\"mkval_tuple\", l_mkval_tuple);\n    REG(\"mkval_array\", l_mkval_array);\n\n#undef REG\n}\n"
  },
  {
    "path": "plugins/dbus/zoo/zoo_uncvt_val.h",
    "content": "/*\n * Copyright (C) 2026  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#pragma once\n\n#include <lua.h>\n#include <glib.h>\n\n// Returns a new reference to the value.\n// This reference is never a \"floating reference\".\nGVariant *zoo_uncvt_val_fetch_newref(lua_State *L, int pos, const char *what);\n\nvoid zoo_uncvt_val_register_mt_and_funcs(lua_State *L);\n"
  },
  {
    "path": "plugins/disk-io-linux/CMakeLists.txt",
    "content": "install (FILES disk-io-linux.lua DESTINATION ${LUA_PLUGINS_DIR})\n\nluastatus_add_man_page (README.rst luastatus-plugin-disk-io-linux 7)\n"
  },
  {
    "path": "plugins/disk-io-linux/README.rst",
    "content": ".. :X-man-page-only: luastatus-plugin-disk-io-linux\n.. :X-man-page-only: ##############################\n.. :X-man-page-only:\n.. :X-man-page-only: #################################################\n.. :X-man-page-only: Linux-specific disk I/O rate plugin for luastatus\n.. :X-man-page-only: #################################################\n.. :X-man-page-only:\n.. :X-man-page-only: :Copyright: LGPLv3\n.. :X-man-page-only: :Manual section: 7\n\nOverview\n========\nThis derived plugin periodically polls Linux ``procfs`` and calculates\ndisk I/O rates for all disk devices.\n\nFunctions\n=========\nThe following functions are provided:\n\n* ``read_diskstats(old, divisor)``\n\n  Reads current ``diskstats`` measurements and also produces an array with\n  deltas (number of bytes read/written per second for each disk device).\n\n  ``divisor`` must be the period, in seconds, that this function is called.\n  The function will average ``read_bytes`` and ``written_bytes`` over this\n  period.\n\n  Returns ``new, deltas``, where ``new`` is the table with current measurements.\n  ``old`` must be a table with previous measurements (or an empty table if this is\n  the first call to this function).\n\n  ``deltas`` is an array of tables with the following entries:\n\n  - ``num_major`` (number): disk device's major number;\n\n  - ``num_minor`` (number): disk device's minor number;\n\n  - ``name`` (string): disk device's name, as specified in ``/proc/diskstats``;\n\n  - ``read_bytes`` (number): number of bytes read per second, averaged over the last ``divisor`` seconds;\n\n  - ``written_bytes`` (number): number of bytes written per second, averaged over the last ``divisor`` seconds.\n\n  **NOTE:** ``read_bytes`` and ``written_bytes`` entries may be negative; this means an overflow happened in\n  kernel's statistics. If any of these numbers is negative, it is recommended simply not to include this disk\n  device in current output.\n\n  In order to use this function, you are expected to maintain a table ``old``, initially empty,\n  and pass it to ``read_diskstats`` as the first argument each time, then set it to the first\n  return value. For example::\n\n    local old = {}\n    local period = 5\n\n    -- each 'period' seconds:\n        local new, deltas = plugin.diskstats(old, period)\n        old = new\n        -- somehow use 'deltas'\n\n* ``widget(tbl)``\n\n  Constructs a ``widget`` table required by luastatus.\n\n  ``tbl`` is a table with the following fields:\n\n  **(required)**\n\n  - ``cb``: a function\n\n    The callback function that will be called with current deltas.\n    The argument is the same as the second return value (``deltas``) of ``read_diskstats`` (see above\n    for description of ``read_diskstats`` function for more information).\n\n  **(optional)**\n\n  - ``period``: table\n\n    Period, in seconds, to query updates. Defaults to 1.\n\n  - ``event``\n\n    The ``event`` entry of the resulting table (see ``luastatus`` documentation for the\n    description of ``widget.event`` field).\n"
  },
  {
    "path": "plugins/disk-io-linux/disk-io-linux.lua",
    "content": "--[[\n  Copyright (C) 2025  luastatus developers\n\n  This file is part of luastatus.\n\n  luastatus is free software: you can redistribute it and/or modify\n  it under the terms of the GNU Lesser General Public License as published by\n  the Free Software Foundation, either version 3 of the License, or\n  (at your option) any later version.\n\n  luastatus is distributed in the hope that it will be useful,\n  but WITHOUT ANY WARRANTY; without even the implied warranty of\n  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n  GNU Lesser General Public License for more details.\n\n  You should have received a copy of the GNU Lesser General Public License\n  along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n--]]\n\nlocal PATTERN = '^%s*' .. ('(%S+)%s+'):rep(10)\n\n-- This is a kernel constant; it's not related to the actual device's characteristics.\nlocal SECTOR_SIZE = 512\n\nlocal P = {}\n\nlocal function do_with_file(f, callback)\n    local is_ok, err = pcall(callback)\n    f:close()\n    if not is_ok then\n        error(err)\n    end\nend\n\nfunction P.read_diskstats(old, divisor, _proc_path)\n    _proc_path = _proc_path or '/proc'\n    local factor = SECTOR_SIZE / divisor\n    local new = {}\n    local deltas = {}\n\n    local f = assert(io.open(_proc_path .. '/diskstats', 'r'))\n    do_with_file(f, function()\n        for line in f:lines() do\n\n            local num_major_str, num_minor_str, name, _, _, read_str, _, _, _, written_str = line:match(PATTERN)\n            if not num_major_str then\n                -- line does not match the pattern. Kinda weird.\n                -- Let's just stop processing the file.\n                break\n            end\n\n            local key = string.format('%s:%s:%s', num_major_str, num_minor_str, name)\n            local old_entry = old[key]\n\n            local read    = assert(tonumber(read_str))\n            local written = assert(tonumber(written_str))\n\n            if old_entry then\n                deltas[#deltas + 1] = {\n                    num_major     = assert(tonumber(num_major_str)),\n                    num_minor     = assert(tonumber(num_minor_str)),\n                    name          = name,\n                    read_bytes    = factor * (read    - old_entry.read),\n                    written_bytes = factor * (written - old_entry.written),\n                }\n            end\n            new[key] = {read = read, written = written}\n        end\n    end)\n\n    return new, deltas\nend\n\nfunction P.widget(tbl)\n    local period = tbl.period or 1\n    local old = {}\n    return {\n        plugin = 'timer',\n        opts = {\n            period = period,\n        },\n        cb = function(_)\n            local new, deltas = P.read_diskstats(old, period, tbl._proc_path)\n            old = new\n            return tbl.cb(deltas)\n        end,\n        event = tbl.event,\n    }\nend\n\nreturn P\n"
  },
  {
    "path": "plugins/file-contents-linux/CMakeLists.txt",
    "content": "install (FILES file-contents-linux.lua DESTINATION ${LUA_PLUGINS_DIR})\n\nluastatus_add_man_page (README.rst luastatus-plugin-file-contents-linux 7)\n"
  },
  {
    "path": "plugins/file-contents-linux/README.rst",
    "content": ".. :X-man-page-only: luastatus-plugin-file-contents-linux\n.. :X-man-page-only: ####################################\n.. :X-man-page-only:\n.. :X-man-page-only: #################################################\n.. :X-man-page-only: Linux-specific file contents plugin for luastatus\n.. :X-man-page-only: #################################################\n.. :X-man-page-only:\n.. :X-man-page-only: :Copyright: LGPLv3\n.. :X-man-page-only: :Manual section: 7\n\nOverview\n========\nThis derived plugin monitors the content of a file.\n\nFunctions\n=========\nThe following functions are provided:\n\n* ``widget(tbl)``\n\n  Constructs a ``widget`` table required by luastatus. ``tbl`` is a table with the following\n  fields:\n\n  **(required)**\n\n  * ``filename``: string\n\n    Path to the file to monitor.\n\n  * ``cb``: function\n\n    The callback that will be called with ``filename`` opened for reading.\n\n  **(optional)**\n\n  * ``timeout``, ``flags``\n\n    Better do not touch (or see the code).\n\n  * ``event``\n\n    The ``event`` entry of the resulting table (see ``luastatus`` documentation for the\n    description of ``widget.event`` field).\n"
  },
  {
    "path": "plugins/file-contents-linux/file-contents-linux.lua",
    "content": "--[[\n  Copyright (C) 2015-2025  luastatus developers\n\n  This file is part of luastatus.\n\n  luastatus is free software: you can redistribute it and/or modify\n  it under the terms of the GNU Lesser General Public License as published by\n  the Free Software Foundation, either version 3 of the License, or\n  (at your option) any later version.\n\n  luastatus is distributed in the hope that it will be useful,\n  but WITHOUT ANY WARRANTY; without even the implied warranty of\n  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n  GNU Lesser General Public License for more details.\n\n  You should have received a copy of the GNU Lesser General Public License\n  along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n--]]\n\nlocal P = {}\n\nfunction P.widget(tbl)\n    local flags = tbl.flags or {'close_write', 'delete_self', 'oneshot'}\n    local timeout = tbl.timeout or 5\n    return {\n        plugin = 'inotify',\n        opts = {\n            watch = {},\n            greet = true,\n        },\n        cb = function()\n            if not luastatus.plugin.add_watch(tbl.filename, flags) then\n                luastatus.plugin.push_timeout(timeout)\n                error('add_watch() failed')\n            end\n            local f = assert(io.open(tbl.filename, 'r'))\n            local is_ok, res = pcall(tbl.cb, f)\n            f:close()\n            if not is_ok then\n                error(res)\n            end\n            return res\n        end,\n        event = tbl.event,\n    }\nend\n\nreturn P\n"
  },
  {
    "path": "plugins/fs/CMakeLists.txt",
    "content": "file (GLOB sources \"*.c\")\nluastatus_add_plugin (plugin-fs $<TARGET_OBJECTS:ls> $<TARGET_OBJECTS:moonvisit> ${sources})\n\ntarget_compile_definitions (plugin-fs PUBLIC -D_POSIX_C_SOURCE=200809L)\nluastatus_target_compile_with (plugin-fs LUA)\ntarget_include_directories (plugin-fs PUBLIC \"${PROJECT_SOURCE_DIR}\")\n\n# find pthreads\nset (CMAKE_THREAD_PREFER_PTHREAD TRUE)\nset (THREADS_PREFER_PTHREAD_FLAG TRUE)\nfind_package (Threads REQUIRED)\n# link against pthread\ntarget_link_libraries (plugin-fs PUBLIC Threads::Threads)\n\nluastatus_add_man_page (README.rst luastatus-plugin-fs 7)\n"
  },
  {
    "path": "plugins/fs/README.rst",
    "content": ".. :X-man-page-only: luastatus-plugin-fs\n.. :X-man-page-only: #####################\n.. :X-man-page-only:\n.. :X-man-page-only: ###############################\n.. :X-man-page-only: disk usage plugin for luastatus\n.. :X-man-page-only: ###############################\n.. :X-man-page-only:\n.. :X-man-page-only: :Copyright: LGPLv3\n.. :X-man-page-only: :Manual section: 7\n\nOverview\n========\nThis plugin monitors file system usage. It is timer-driven, plus a wake-up FIFO can be specified.\n\nOptions\n========\nThe following options are supported:\n\n* ``paths``: array of strings\n\n  For each of these paths, an information on usage of the file system it belongs to is returned.\n\n* ``globs``: array of strings\n\n  Same as ``paths`` but accepts glob patterns. It is not an error if the pattern expands to\n  nothing. Useful for monitoring filesystems which are mounted at runtime.\n\n* ``enable_dyn_paths``: boolean\n\n  Whether to enable support for adding/removing paths dynamically (in run time).\n  If set to true, functions ``add_dyn_path``, ``remove_dyn_path``, ``get_max_dyn_paths``\n  will be available.\n\n  Defaults to false.\n\n* ``period``: number\n\n  A number of seconds to sleep before calling ``cb`` again. May be fractional. Defaults to 10.\n\n* ``fifo``: string\n\n  Path to an existent FIFO. The plugin does not create FIFO itself. To force a wake-up,\n  ``touch(1)`` the FIFO, that is, open it for writing and then close.\n\n``cb`` argument\n===============\nA table where keys are paths and values are tables with the following entries:\n\n* ``total``: number of bytes total;\n\n* ``free``: number of bytes free;\n\n* ``avail``: number of bytes free for unprivileged users.\n\nFunctions\n=========\nIf ``enable_dyn_paths`` option was set to true, then:\n\n* the set of \"dynamic\" path, initially empty, is maintained in run time;\n\n* each call to ``cb`` also includes all paths from this set;\n\n* the functions related to adding/removing paths dynamically are provided.\n\nThe following functions that are related to adding/removing dynamically are provided\nif ``enable_dyn_paths`` option was set to true:\n\n* ``luastatus.plugin.add_dyn_path(path)``\n\n  Tries to add ``path`` (which must be a string) to the set of \"dynamic\" paths.\n  On successful insertion, it returns ``true``.\n  If the path is already in the set, returns ``false``.\n\n* ``luastatus.plugin.remove_dyn_path(path)``\n\n  Tries to remove ``path`` (which must be string) from the set of \"dynamic\" paths.\n  On successful removal, it returns ``true``.\n  If the path is not in the set, returns ``false``.\n"
  },
  {
    "path": "plugins/fs/fs.c",
    "content": "/*\n * Copyright (C) 2015-2026  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#include <errno.h>\n#include <glob.h>\n#include <stdbool.h>\n#include <lua.h>\n#include <lauxlib.h>\n#include <stdlib.h>\n#include <sys/statvfs.h>\n#include <pthread.h>\n\n#include \"include/plugin_v1.h\"\n#include \"include/sayf_macros.h\"\n\n#include \"libmoonvisit/moonvisit.h\"\n\n#include \"libls/ls_alloc_utils.h\"\n#include \"libls/ls_strarr.h\"\n#include \"libls/ls_fifo_device.h\"\n#include \"libls/ls_tls_ebuf.h\"\n#include \"libls/ls_time_utils.h\"\n#include \"libls/ls_panic.h\"\n\n#include \"strlist.h\"\n\ntypedef struct {\n    LS_StringArray paths;\n    LS_StringArray globs;\n\n    double period;\n    char *fifo;\n\n    bool dyn_paths_enabled;\n    Strlist dyn_paths;\n    pthread_mutex_t dyn_mtx;\n} Priv;\n\nstatic void destroy(LuastatusPluginData *pd)\n{\n    Priv *p = pd->priv;\n    ls_strarr_destroy(p->paths);\n    ls_strarr_destroy(p->globs);\n    free(p->fifo);\n\n    if (p->dyn_paths_enabled) {\n        strlist_destroy(p->dyn_paths);\n        LS_PTH_CHECK(pthread_mutex_destroy(&p->dyn_mtx));\n    }\n\n    free(p);\n}\n\nstatic int parse_paths_elem(MoonVisit *mv, void *ud, int kpos, int vpos)\n{\n    mv->where = \"'paths' element\";\n    (void) kpos;\n\n    Priv *p = ud;\n\n    if (moon_visit_checktype_at(mv, NULL, vpos, LUA_TSTRING) < 0)\n        return -1;\n\n    const char *s = lua_tostring(mv->L, vpos);\n    ls_strarr_append_s(&p->paths, s);\n    return 1;\n}\n\nstatic int parse_globs_elem(MoonVisit *mv, void *ud, int kpos, int vpos)\n{\n    mv->where = \"'globs' element\";\n    (void) kpos;\n\n    Priv *p = ud;\n\n    if (moon_visit_checktype_at(mv, NULL, vpos, LUA_TSTRING) < 0)\n        return -1;\n\n    const char *s = lua_tostring(mv->L, vpos);\n    ls_strarr_append_s(&p->globs, s);\n    return 1;\n}\n\nstatic int init(LuastatusPluginData *pd, lua_State *L)\n{\n    Priv *p = pd->priv = LS_XNEW(Priv, 1);\n    *p = (Priv) {\n        .paths = ls_strarr_new(),\n        .globs = ls_strarr_new(),\n        .dyn_paths_enabled = false,\n        .period = 10.0,\n        .fifo = NULL,\n    };\n    char errbuf[256];\n    MoonVisit mv = {.L = L, .errbuf = errbuf, .nerrbuf = sizeof(errbuf)};\n\n    // Parse paths\n    if (moon_visit_table_f(&mv, -1, \"paths\", parse_paths_elem, p, true) < 0)\n        goto mverror;\n\n    // Parse globs\n    if (moon_visit_table_f(&mv, -1, \"globs\", parse_globs_elem, p, true) < 0)\n        goto mverror;\n\n    // Parse period\n    if (moon_visit_num(&mv, -1, \"period\", &p->period, true) < 0)\n        goto mverror;\n    if (!ls_double_is_good_time_delta(p->period)) {\n        LS_FATALF(pd, \"period is invalid\");\n        goto error;\n    }\n\n    // Parse fifo\n    if (moon_visit_str(&mv, -1, \"fifo\", &p->fifo, NULL, true) < 0)\n        goto mverror;\n\n    // Parse enable_dyn_paths\n    bool enable_dyn_paths = false;\n    if (moon_visit_bool(&mv, -1, \"enable_dyn_paths\", &enable_dyn_paths, true) < 0) {\n        goto mverror;\n    }\n    if (enable_dyn_paths) {\n        p->dyn_paths_enabled = true;\n        p->dyn_paths = strlist_new();\n        LS_PTH_CHECK(pthread_mutex_init(&p->dyn_mtx, NULL));\n    }\n\n    // Warn if both paths and globs are empty and /enable_dyn_paths/ is false\n    if (!enable_dyn_paths && !ls_strarr_size(p->paths) && !ls_strarr_size(p->globs)) {\n        LS_WARNF(pd, \"both paths and globs are empty, and enable_dyn_paths is false\");\n    }\n\n    return LUASTATUS_OK;\n\nmverror:\n    LS_FATALF(pd, \"%s\", errbuf);\nerror:\n    destroy(pd);\n    return LUASTATUS_ERR;\n}\n\nstatic bool push_for(LuastatusPluginData *pd, lua_State *L, const char *path)\n{\n    struct statvfs st;\n    if (statvfs(path, &st) < 0) {\n        LS_WARNF(pd, \"statvfs: %s: %s\", path, ls_tls_strerror(errno));\n        return false;\n    }\n    lua_createtable(L, 0, 3); // L: table\n    lua_pushnumber(L, ((double) st.f_frsize) * st.f_blocks); // L: table n\n    lua_setfield(L, -2, \"total\"); // L: table\n    lua_pushnumber(L, ((double) st.f_frsize) * st.f_bfree); // L: table n\n    lua_setfield(L, -2, \"free\"); // L: table\n    lua_pushnumber(L, ((double) st.f_frsize) * st.f_bavail); // L: table n\n    lua_setfield(L, -2, \"avail\"); // L: table\n    return true;\n}\n\ntypedef struct {\n    lua_State *L;\n    LuastatusPluginData *pd;\n    LuastatusPluginRunFuncs funcs;\n} Call;\n\nstatic inline Call start_call(LuastatusPluginData *pd, LuastatusPluginRunFuncs funcs)\n{\n    lua_State *L = funcs.call_begin(pd->userdata);\n    lua_newtable(L);\n\n    return (Call) {\n        .L = L,\n        .pd = pd,\n        .funcs = funcs,\n    };\n}\n\nstatic inline void add_path_to_call(Call c, const char *path)\n{\n    LS_ASSERT(path != NULL);\n\n    if (push_for(c.pd, c.L, path)) {\n        lua_setfield(c.L, -2, path);\n    }\n}\n\nstatic inline void end_call(Call c)\n{\n    c.funcs.call_end(c.pd->userdata);\n}\n\nstatic void run(LuastatusPluginData *pd, LuastatusPluginRunFuncs funcs)\n{\n    Priv *p = pd->priv;\n\n    LS_FifoDevice dev = ls_fifo_device_new();\n\n    LS_TimeDelta TD = ls_double_to_TD_or_die(p->period);\n\n    while (1) {\n        // make a call\n        Call call = start_call(pd, funcs);\n\n        for (size_t i = 0; i < ls_strarr_size(p->paths); ++i) {\n            add_path_to_call(call, ls_strarr_at(p->paths, i, NULL));\n        }\n\n        if (p->dyn_paths_enabled) {\n            LS_PTH_CHECK(pthread_mutex_lock(&p->dyn_mtx));\n            for (size_t i = 0; i < p->dyn_paths.size; ++i) {\n                add_path_to_call(call, p->dyn_paths.data[i]);\n            }\n            LS_PTH_CHECK(pthread_mutex_unlock(&p->dyn_mtx));\n        }\n\n        for (size_t i = 0; i < ls_strarr_size(p->globs); ++i) {\n            const char *pattern = ls_strarr_at(p->globs, i, NULL);\n            glob_t gbuf;\n            switch (glob(pattern, GLOB_NOSORT, NULL, &gbuf)) {\n            case 0:\n            case GLOB_NOMATCH:\n                break;\n            default:\n                LS_WARNF(pd, \"glob() failed (out of memory?)\");\n            }\n            for (size_t j = 0; j < gbuf.gl_pathc; ++j) {\n                add_path_to_call(call, gbuf.gl_pathv[j]);\n            }\n            globfree(&gbuf);\n        }\n\n        end_call(call);\n\n        // wait\n        if (ls_fifo_device_open(&dev, p->fifo) < 0) {\n            LS_WARNF(pd, \"ls_fifo_device_open: %s: %s\", p->fifo, ls_tls_strerror(errno));\n        }\n        if (ls_fifo_device_wait(&dev, TD) < 0) {\n            LS_FATALF(pd, \"ls_fifo_device_wait: %s: %s\", p->fifo, ls_tls_strerror(errno));\n            goto error;\n        }\n    }\n\nerror:\n    ls_fifo_device_close(&dev);\n}\n\nstatic int lfunc_add_dyn_path(lua_State *L)\n{\n    Priv *p = lua_touserdata(L, lua_upvalueindex(1));\n    const char *path = luaL_checkstring(L, 1);\n\n    LS_ASSERT(p->dyn_paths_enabled);\n\n    LS_PTH_CHECK(pthread_mutex_lock(&p->dyn_mtx));\n    bool rc = strlist_push(&p->dyn_paths, path);\n    LS_PTH_CHECK(pthread_mutex_unlock(&p->dyn_mtx));\n\n    lua_pushboolean(L, rc);\n    return 1;\n}\n\nstatic int lfunc_remove_dyn_path(lua_State *L)\n{\n    Priv *p = lua_touserdata(L, lua_upvalueindex(1));\n    const char *path = luaL_checkstring(L, 1);\n\n    LS_ASSERT(p->dyn_paths_enabled);\n\n    LS_PTH_CHECK(pthread_mutex_lock(&p->dyn_mtx));\n    bool rc = strlist_remove(&p->dyn_paths, path);\n    LS_PTH_CHECK(pthread_mutex_unlock(&p->dyn_mtx));\n\n    lua_pushboolean(L, rc);\n    return 1;\n}\n\n// Note: this Lua function is provided for backward compatibility purposes only.\nstatic int lfunc_get_max_dyn_paths(lua_State *L)\n{\n    lua_pushinteger(L, INT_MAX);\n    return 1;\n}\n\nstatic void register_funcs(LuastatusPluginData *pd, lua_State *L)\n{\n    Priv *p = pd->priv;\n\n    if (!p->dyn_paths_enabled) {\n        return;\n    }\n\n    // L: ? table\n\n    lua_pushlightuserdata(L, p); // L: ? table ud\n    lua_pushcclosure(L, lfunc_add_dyn_path, 1); // L: ? table func\n    lua_setfield(L, -2, \"add_dyn_path\"); // L: ? table\n\n    lua_pushlightuserdata(L, p); // L: ? table ud\n    lua_pushcclosure(L, lfunc_remove_dyn_path, 1); // L: ? table func\n    lua_setfield(L, -2, \"remove_dyn_path\"); // L: ? table\n\n    // Note: function /get_max_dyn_paths/ is provided for backward compatibility purposes only.\n\n    lua_pushcfunction(L, lfunc_get_max_dyn_paths); // L: ? table func\n    lua_setfield(L, -2, \"get_max_dyn_paths\"); // L: ? table\n}\n\nLuastatusPluginIface luastatus_plugin_iface_v1 = {\n    .init = init,\n    .register_funcs = register_funcs,\n    .run = run,\n    .destroy = destroy,\n};\n"
  },
  {
    "path": "plugins/fs/strlist.c",
    "content": "/*\n * Copyright (C) 2026  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#include \"strlist.h\"\n#include <stdlib.h>\n#include <string.h>\n#include \"libls/ls_alloc_utils.h\"\n#include \"libls/ls_panic.h\"\n\nStrlist strlist_new(void)\n{\n    return (Strlist) {0};\n}\n\nstatic void resize_to(Strlist *x, size_t new_size)\n{\n    x->data = LS_M_XREALLOC(x->data, new_size);\n    x->size = new_size;\n}\n\n#define BAD_INDEX ((size_t) -1)\n\nstatic size_t find(Strlist *x, const char *s)\n{\n    char **data = x->data;\n    size_t size = x->size;\n    for (size_t i = 0; i < size; ++i) {\n        if (strcmp(data[i], s) == 0) {\n            return i;\n        }\n    }\n    return BAD_INDEX;\n}\n\nbool strlist_push(Strlist *x, const char *s)\n{\n    LS_ASSERT(s != NULL);\n\n    if (find(x, s) != BAD_INDEX) {\n        return false;\n    }\n\n    // Increase the size.\n    resize_to(x, x->size + 1);\n\n    // Insert the new element.\n    x->data[x->size - 1] = ls_xstrdup(s);\n    return true;\n}\n\nstatic void remove_at(Strlist *x, size_t i)\n{\n    LS_ASSERT(i < x->size);\n\n    // Free the string at index /i/.\n    free(x->data[i]);\n\n    // Move the last element to position /i/.\n    size_t i_last = x->size - 1;\n    x->data[i] = x->data[i_last];\n\n    // Decrease the size.\n    resize_to(x, x->size - 1);\n}\n\nbool strlist_remove(Strlist *x, const char *s)\n{\n    LS_ASSERT(s != NULL);\n\n    size_t i = find(x, s);\n    if (i == BAD_INDEX) {\n        return false;\n    }\n\n    remove_at(x, i);\n    return true;\n}\n\nvoid strlist_destroy(Strlist x)\n{\n    for (size_t i = 0; i < x.size; ++i) {\n        free(x.data[i]);\n    }\n    free(x.data);\n}\n"
  },
  {
    "path": "plugins/fs/strlist.h",
    "content": "/*\n * Copyright (C) 2026  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifndef strlist_h_\n#define strlist_h_\n\n#include <stddef.h>\n#include <stdbool.h>\n\ntypedef struct {\n    char **data;\n    size_t size;\n} Strlist;\n\nStrlist strlist_new(void);\n\nbool strlist_push(Strlist *x, const char *s);\n\nbool strlist_remove(Strlist *x, const char *s);\n\nvoid strlist_destroy(Strlist x);\n\n#endif\n"
  },
  {
    "path": "plugins/imap/CMakeLists.txt",
    "content": "install (FILES imap.lua DESTINATION ${LUA_PLUGINS_DIR})\n\nluastatus_add_man_page (README.rst luastatus-plugin-imap 7)\n"
  },
  {
    "path": "plugins/imap/README.rst",
    "content": ".. :X-man-page-only: luastatus-plugin-imap\n.. :X-man-page-only: #####################\n.. :X-man-page-only:\n.. :X-man-page-only: ###########################\n.. :X-man-page-only: IMAPv4 plugin for luastatus\n.. :X-man-page-only: ###########################\n.. :X-man-page-only:\n.. :X-man-page-only: :Copyright: LGPLv3\n.. :X-man-page-only: :Manual section: 7\n\nOverview\n========\nThis derived plugin monitors the number of unread mails in an IMAP mailbox.\n\nFunctions\n=========\n\n* ``widget(tbl)``\n\n  Constructs a ``widget`` table required by luastatus. ``tbl`` is a table with the following\n  fields:\n\n  **(required)**\n\n  - ``cb``: function\n\n    The callback that will be called with the number of unread mails, or with ``nil`` if it is\n    unknown.\n\n  - ``host``: string\n\n    Host name.\n\n  - ``port``: number\n\n    Port number.\n\n  - ``login``, ``password``: strings\n\n    Your credentials.\n\n  - ``mailbox``: string\n\n    Mailbox name (typically ``\"Inbox\"``).\n\n  - ``error_sleep_period``: number\n\n    Number of seconds to sleep after an error.\n\n  **(optional)**\n\n- ``use_ssl``: boolean\n\n    Whether to use SSL (you probably should set it to ``true``). Defaults to false.\n\n  - ``verbose``: boolean\n\n    Whether to be verbose (useful for troubleshooting). Default to false.\n\n  - ``timeout``: number\n\n    Idle timeout, in seconds, after which to reconnect; you probably want to set this to\n    something like ``5 * 60``.\n\n  - ``handshake_timeout``: number\n\n    SSL handshake timeout, in seconds.\n\n  - ``event``\n\n    The ``event`` entry of the resulting table (see ``luastatus`` documentation for the\n    description of ``widget.event`` field).\n"
  },
  {
    "path": "plugins/imap/imap.lua",
    "content": "--[[\n  Copyright (C) 2015-2025  luastatus developers\n\n  This file is part of luastatus.\n\n  luastatus is free software: you can redistribute it and/or modify\n  it under the terms of the GNU Lesser General Public License as published by\n  the Free Software Foundation, either version 3 of the License, or\n  (at your option) any later version.\n\n  luastatus is distributed in the hope that it will be useful,\n  but WITHOUT ANY WARRANTY; without even the implied warranty of\n  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n  GNU Lesser General Public License for more details.\n\n  You should have received a copy of the GNU Lesser General Public License\n  along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n--]]\n\nlocal socket = require 'socket'\nlocal ssl = require 'ssl'\n\nlocal log = print\n\n----------------------------------------------------------------------------------------------------\n\nlocal IMAP_TIMEOUT_ERROR = {}\n\nlocal IMAP = {}\n\nlocal function safely_wrap_ssl(conn)\n    local new_conn\n    local conn_to_close = conn\n\n    local is_ok, err = pcall(function()\n        new_conn = assert(ssl.wrap(conn, {\n            mode = 'client',\n            protocol = 'tlsv1',\n            cafile = '/etc/ssl/certs/ca-certificates.crt',\n            verify = 'peer',\n            options = 'all',\n        }))\n        conn_to_close = new_conn\n        assert(new_conn:dohandshake())\n    end)\n\n    if not is_ok then\n        conn_to_close:close()\n        error(err)\n    end\n\n    return new_conn\nend\n\nfunction IMAP:open(host, port, params)\n    local conn = socket.tcp()\n    conn:connect(host, port)\n    conn:settimeout(params.handshake_timeout)\n    if params.use_ssl then\n        conn = safely_wrap_ssl(conn)\n    end\n    conn:settimeout(params.timeout)\n    self.__index = self\n    return setmetatable({\n        conn = conn,\n        last_id = 1,\n        verbose = params.verbose,\n    }, self)\nend\n\nfunction IMAP:receive()\n    local line, err = self.conn:receive()\n    if line == nil then\n        error(err == 'wantread' and IMAP_TIMEOUT_ERROR or err)\n    end\n    if self.verbose then\n        log('<', line)\n    end\n    return line\nend\n\nfunction IMAP:send(str)\n    if self.verbose then\n        log('>', str)\n    end\n    local data = str .. '\\r\\n'\n    local nsent = 0\n    while nsent ~= #data do\n        nsent = assert(self.conn:send(data, nsent + 1))\n    end\nend\n\nfunction IMAP:command(query, ondata)\n    local id = string.format('a%04d', self.last_id)\n    self.last_id = self.last_id + 1\n    local pattern = '^' .. id .. ' (%w+)'\n\n    self:send(id .. ' ' .. query)\n\n    while true do\n        local line = self:receive()\n        local resp = line:match(pattern)\n        if resp ~= nil then\n            return resp == 'OK', line\n        end\n        if ondata ~= nil then\n            ondata(line)\n        end\n    end\nend\n\nfunction IMAP:close()\n    self.conn:close()\nend\n\n----------------------------------------------------------------------------------------------------\n\nlocal P = {}\n\nfunction P.widget(tbl)\n    local error_sleep_period = tbl.error_sleep_period\n    -- to prevent busy-looping if an invalid value is passed to /luastatus.plugin.push_period/.\n    assert(type(error_sleep_period) == 'number' and error_sleep_period > 0,\n           'invalid \"error_sleep_period\" value')\n\n    local mbox = nil\n\n    local function connect()\n        mbox = IMAP:open(tbl.host, tbl.port, {\n            use_ssl = tbl.use_ssl,\n            timeout = tbl.timeout,\n            handshake_timeout = tbl.handshake_timeout,\n            verbose = tbl.verbose,\n        })\n\n        assert(mbox:command(string.format('LOGIN %s %s', tbl.login, tbl.password)))\n        assert(mbox:command('SELECT ' .. tbl.mailbox))\n    end\n\n    local function get_unseen()\n        local unseen\n        repeat\n            local finish = true\n            assert(mbox:command('STATUS ' .. tbl.mailbox .. ' (UNSEEN)', function(line)\n                if not line:match('^%*') then\n                    return\n                end\n                local m = line:match('^%* STATUS .* %(UNSEEN (%d+)%)%s*$')\n                if m then\n                    unseen = m\n                else\n                    finish = false\n                end\n            end))\n        until finish\n        assert(unseen ~= nil)\n        return tonumber(unseen)\n    end\n\n    local function idle()\n        local done_sent = false\n        assert(mbox:command('IDLE', function(line)\n            if not done_sent and line:match('^%*') then\n                mbox:send('DONE')\n                done_sent = true\n            end\n        end))\n    end\n\n    local function iteration()\n        if mbox == nil then\n            connect()\n        else\n            idle()\n        end\n        return get_unseen()\n    end\n\n    local last_content = tbl.cb(nil)\n    return {\n        plugin = 'timer',\n        opts = {period = 0},\n        cb = function()\n            local is_ok, obj = pcall(iteration)\n            if is_ok then\n                last_content = tbl.cb(obj)\n                return last_content\n            end\n\n            if mbox ~= nil then\n                mbox:close()\n                mbox = nil\n            end\n\n            if obj == IMAP_TIMEOUT_ERROR then\n                return last_content\n            else\n                if tbl.verbose then\n                    log('!', obj)\n                end\n                luastatus.plugin.push_period(error_sleep_period)\n                last_content = tbl.cb(nil)\n                return last_content\n            end\n        end,\n        event = tbl.event,\n    }\nend\n\nreturn P\n"
  },
  {
    "path": "plugins/inotify/CMakeLists.txt",
    "content": "file (GLOB sources \"*.c\")\nluastatus_add_plugin (\n    plugin-inotify\n    $<TARGET_OBJECTS:ls>\n    $<TARGET_OBJECTS:moonvisit>\n    $<TARGET_OBJECTS:procalive>\n    ${sources}\n)\n\ninclude (CheckSymbolExists)\ncheck_symbol_exists (inotify_init1 \"sys/inotify.h\" HAVE_INOTIFY_INIT1)\nconfigure_file (\"probes.in.h\" \"probes.generated.h\")\n\ntarget_compile_definitions (plugin-inotify PUBLIC -D_POSIX_C_SOURCE=200809L)\nluastatus_target_compile_with (plugin-inotify LUA)\ntarget_include_directories (plugin-inotify PUBLIC \"${PROJECT_SOURCE_DIR}\" \"${CMAKE_CURRENT_BINARY_DIR}\")\n\nluastatus_add_man_page (README.rst luastatus-plugin-inotify 7)\n"
  },
  {
    "path": "plugins/inotify/README.rst",
    "content": ".. :X-man-page-only: luastatus-plugin-inotify\n.. :X-man-page-only: ########################\n.. :X-man-page-only:\n.. :X-man-page-only: ############################\n.. :X-man-page-only: inotify plugin for luastatus\n.. :X-man-page-only: ############################\n.. :X-man-page-only:\n.. :X-man-page-only: :Copyright: LGPLv3\n.. :X-man-page-only: :Manual section: 7\n\nOverview\n========\nThis plugin monitors inotify events.\n\nOptions\n=======\n* ``watch``: table\n\n  A table in which keys are the paths to watch and values are the tables with even names,\n  for example, ``{[\"/home/user\"] = {\"create\", \"delete\", \"move\"}}`` (see the\n  `Events and flag names`_ section).\n\n* ``greet``: boolean\n\n  Whether or not to call ``cb`` with ``what=\"hello\"`` as soon as the widget starts. Defaults to\n  false.\n\n* ``timeout``: number\n\n  If specified and not negative, this plugin calls ``cb`` with ``what=\"timeout\"`` if no event has\n  occurred in ``timeout`` seconds.\n\n``cb`` argument\n===============\nA table with ``what`` entry.\n\n* If ``what`` is ``\"hello\"``, the function is being called for the first time (and the ``greet``\n  option was set to ``true``).\n\n* If ``what`` is ``\"timeout\"``, the function has not been called for the number of seconds specified\n  as the ``timeout`` option.\n\n* If ``what`` is ``\"event\"``, an inotify event has been read; in this case, the table has the\n  following additional entries:\n\n  - ``wd``: integer\n\n    Watch descriptor (see the `Functions`_ section) of the event.\n\n  - ``mask``: table\n\n    For each event name or event flag (see the `Events and flag names`_ section), this table\n    contains an entry with key equal to its name and ``true`` value.\n\n  - ``cookie``: number\n\n    Unique cookie associating related events (or, if there are no associated related events, a\n    zero).\n\n  - ``name``: string (optional)\n\n    Present only when an event is returned for a file inside a watched directory; identifies the\n    filename within the watched directory.\n\nFunctions\n=========\nEach file being watched is assigned a *watch descriptor*, which is a non-negative integer.\n\n* ``wds = luastatus.plugin.get_initial_wds()``\n\n  Returns a table that maps *initial* paths to their watch descriptors.\n\n* ``wd = luastatus.plugin.add_watch(path, events)``\n\n  Add a new file to watch. Returns a watch descriptor on success, or ``nil`` on failure.\n\n* ``is_ok = luastatus.plugin.remove_watch(wd)``\n\n  Removes a watch by its watch descriptor. Returns ``true`` on success, or ``false`` on failure.\n\n* ``tbl = luastatus.plugin.get_supported_events()``\n\n  Returns a table with all events supported by the plugin.\n\n  In this table, keys are events names, values are strings which represent the mode of the event.\n  The value is\n  ``\"i\"`` for an input-only event (can only be listened to, never occurs in a returned event),\n  ``\"o\"`` for an output-only event (only occurs in a returned event, cannot be listened to),\n  ``\"io\"`` for an event that can be both listened to and occur in a returned event.\n\n  This set depends on the version of glibc that the plugin has been compiled with, which is\n  something a widget should not be required to know. By calling this function, the widget can\n  query which events are supported by the plugin.\n\n  Testing which events are actually supported by the Linux kernel that the system is running is a\n  harder problem.\n  The ``inotify(7)`` man page gives kernel versions in which certain events were introduced\n  (e.g. ``IN_DONT_FOLLOW (since Linux 2.6.15)``).\n  One should probably parse the output of ``uname -r`` command or the contents of\n  ``/proc/sys/kernel/osrelease`` file in order to check for the version of the kernel.\n\n* ``luastatus.plugin.push_timeout(seconds)``\n\n  Changes the timeout for one iteration.\n\nThe following functions are provided as a part of \"procalive\" function set.\nThese functions are available in plugins, including this one, that can be used\nto watch the state of some process(es):\n\n* ``is_ok, err_msg = luastatus.plugin.access(path)``\n\n  Checks if a given path exists, as if with ``access(path, F_OK)``.\n  If it does exist, returns ``true, nil``. If it does not, returns\n  ``false, nil``. If an error occurs, returns ``false, err_msg``.\n\n* ``file_type, err_msg = luastatus.plugin.stat(path)``\n\n  Tries to get the type of the file at the given path. On success returns\n  either of: ``\"regular\"``, ``\"dir\"`` (directory), ``\"chardev\"`` (character device),\n  ``\"blockdev\"`` (block device), ``\"fifo\"``, ``\"symlink\"``, ``\"socket\"``, ``\"other\"``.\n  On failure returns ``nil, err_msg``.\n\n* ``arr, err_msg = luastatus.plugin.glob(pattern)``\n\n  Performs glob expansion of ``pattern``.\n  A glob is a wildcard pattern like ``/tmp/*.txt`` that can be applied as\n  a filter to a list of existing file names. Supported expansions are\n  ``*``, ``?`` and ``[...]``. Please refer to ``glob(7)`` for more information\n  on wildcard patterns.\n\n  Note also that the globbing is performed with ``GLOB_MARK`` flag, so that\n  in output, directories have trailing slash appended to their name.\n\n  Returns ``arr, nil`` on success, where ``arr`` is an array of strings; these\n  are existing file names that matched the given pattern. The order is arbitrary.\n  On failure, returns ``nil, err_msg``.\n\n* ``is_alive = luastatus.plugin.is_process_alive(pid)``\n\n  Checks if a process with PID ``pid`` is currently alive. ``pid`` must be either\n  a number or a string.\n  Returns a boolean that indicates whether the process is alive.\n\nEvents and flag names\n=====================\nEach ``IN_*`` constant defined in ``<sys/inotify.h>`` corresponds to a string obtained from its name\nby dropping the initial ``IN_`` and making the rest lower-case, e.g. ``IN_CLOSE_WRITE`` corresponds\nto ``\"close_write\"``.\n\nSee ``inotify(7)`` for details.\n"
  },
  {
    "path": "plugins/inotify/inotify.c",
    "content": "/*\n * Copyright (C) 2015-2025  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#include <lua.h>\n#include <lauxlib.h>\n#include <stdbool.h>\n#include <stdlib.h>\n#include <string.h>\n#include <unistd.h>\n#include <limits.h>\n#include <stdint.h>\n#include <errno.h>\n#include <sys/types.h>\n#include <sys/inotify.h>\n\n#include \"include/plugin_v1.h\"\n#include \"include/sayf_macros.h\"\n\n#include \"libmoonvisit/moonvisit.h\"\n\n#include \"libls/ls_algo.h\"\n#include \"libls/ls_alloc_utils.h\"\n#include \"libls/ls_tls_ebuf.h\"\n#include \"libls/ls_evloop_lfuncs.h\"\n#include \"libls/ls_io_utils.h\"\n#include \"libls/ls_time_utils.h\"\n#include \"libprocalive/procalive_lfuncs.h\"\n\n#include \"inotify_compat.h\"\n\ntypedef struct {\n    char *path;\n    int wd;\n} Watch;\n\ntypedef struct {\n    Watch *data;\n    size_t size;\n    size_t capacity;\n} WatchList;\n\nstatic inline WatchList watch_list_new(void)\n{\n    return (WatchList) {NULL, 0, 0};\n}\n\nstatic inline void watch_list_add(WatchList *x, const char *path, int wd)\n{\n    if (x->size == x->capacity) {\n        x->data = LS_M_X2REALLOC(x->data, &x->capacity);\n    }\n    x->data[x->size++] = (Watch) {ls_xstrdup(path), wd};\n}\n\nstatic inline void watch_list_free(WatchList *x)\n{\n    for (size_t i = 0; i < x->size; ++i)\n        free(x->data[i].path);\n    free(x->data);\n}\n\ntypedef struct {\n    int fd;\n    WatchList init_watch;\n    bool greet;\n    double tmo;\n    LS_PushedTimeout pushed_tmo;\n} Priv;\n\nstatic void destroy(LuastatusPluginData *pd)\n{\n    Priv *p = pd->priv;\n    ls_close(p->fd);\n    watch_list_free(&p->init_watch);\n    ls_pushed_timeout_destroy(&p->pushed_tmo);\n    free(p);\n}\n\ntypedef struct {\n    uint32_t mask;\n    bool in;\n    bool out;\n    const char *name;\n} EventType;\n\nstatic const EventType EVENT_TYPES[] = {\n    {IN_ACCESS,        true,  true,  \"access\"},\n    {IN_ATTRIB,        true,  true,  \"attrib\"},\n    {IN_CLOSE_WRITE,   true,  true,  \"close_write\"},\n    {IN_CLOSE_NOWRITE, true,  true,  \"close_nowrite\"},\n    {IN_CREATE,        true,  true,  \"create\"},\n    {IN_DELETE,        true,  true,  \"delete\"},\n    {IN_DELETE_SELF,   true,  true,  \"delete_self\"},\n    {IN_MODIFY,        true,  true,  \"modify\"},\n    {IN_MOVE_SELF,     true,  true,  \"move_self\"},\n    {IN_MOVED_FROM,    true,  true,  \"moved_from\"},\n    {IN_MOVED_TO,      true,  true,  \"moved_to\"},\n    {IN_OPEN,          true,  true,  \"open\"},\n\n    {IN_ALL_EVENTS,    true,  false, \"all_events\"},\n    {IN_MOVE,          true,  false, \"move\"},\n    {IN_CLOSE,         true,  false, \"close\"},\n#ifdef IN_DONT_FOLLOW\n    {IN_DONT_FOLLOW,   true,  false, \"dont_follow\"},\n#endif\n#ifdef IN_EXCL_UNLINK\n    {IN_EXCL_UNLINK,   true,  false, \"excl_unlink\"},\n#endif\n    {IN_MASK_ADD,      true,  false, \"mask_add\"},\n    {IN_ONESHOT,       true,  false, \"oneshot\"},\n#ifdef IN_ONLYDIR\n    {IN_ONLYDIR,       true,  false, \"onlydir\"},\n#endif\n#ifdef IN_MASK_CREATE\n    {IN_MASK_CREATE,   true,  false, \"mask_create\"},\n#endif\n\n    {IN_IGNORED,       false, true,  \"ignored\"},\n    {IN_ISDIR,         false, true,  \"isdir\"},\n    {IN_Q_OVERFLOW,    false, true,  \"q_overflow\"},\n    {IN_UNMOUNT,       false, true,  \"unmount\"},\n};\nenum { EVENT_TYPES_NUM = LS_ARRAY_SIZE(EVENT_TYPES) };\n\n#define EVENT_TYPES_END (EVENT_TYPES + EVENT_TYPES_NUM)\n\nstatic int parse_evlist_elem(MoonVisit *mv, void *ud, int kpos, int vpos)\n{\n    mv->where = \"element of event names list\";\n    (void) kpos;\n\n    uint32_t *mask = ud;\n\n    if (moon_visit_checktype_at(mv, NULL, vpos, LUA_TSTRING) < 0)\n        goto error;\n\n    const char *s = lua_tostring(mv->L, vpos);\n\n    for (const EventType *et = EVENT_TYPES; et != EVENT_TYPES_END; ++et) {\n        if (et->in && strcmp(et->name, s) == 0) {\n            *mask |= et->mask;\n            return 1;\n        }\n    }\n    moon_visit_err(mv, \"unknown input event '%s'\", s);\nerror:\n    return -1;\n}\n\nstatic int parse_watch_entry(MoonVisit *mv, void *ud, int kpos, int vpos)\n{\n    mv->where = \"'watch' entry\";\n\n    LuastatusPluginData *pd = ud;\n    Priv *p = pd->priv;\n\n    // Parse key\n    if (moon_visit_checktype_at(mv, \"key\", kpos, LUA_TSTRING) < 0)\n        goto error;\n    const char *path = lua_tostring(mv->L, kpos);\n\n    // Parse value\n    uint32_t mask = 0;\n    if (moon_visit_table_f_at(mv, \"value\", vpos, parse_evlist_elem, &mask) < 0)\n        goto error;\n\n    // Add watch\n    int wd = inotify_add_watch(p->fd, path, mask);\n    if (wd < 0) {\n        LS_ERRF(pd, \"inotify_add_watch: %s: %s\", path, ls_tls_strerror(errno));\n    } else {\n        watch_list_add(&p->init_watch, path, wd);\n    }\n    return 1;\nerror:\n    return -1;\n}\n\nstatic int init(LuastatusPluginData *pd, lua_State *L)\n{\n    Priv *p = pd->priv = LS_XNEW(Priv, 1);\n    *p = (Priv) {\n        .fd = -1,\n        .init_watch = watch_list_new(),\n        .greet = false,\n        .tmo = -1,\n    };\n    ls_pushed_timeout_init(&p->pushed_tmo);\n\n    char errbuf[256];\n    MoonVisit mv = {.L = L, .errbuf = errbuf, .nerrbuf = sizeof(errbuf)};\n\n    if ((p->fd = compat_inotify_init(false, true)) < 0) {\n        LS_FATALF(pd, \"inotify_init: %s\", ls_tls_strerror(errno));\n        goto error;\n    }\n\n    // Parse greet\n    if (moon_visit_bool(&mv, -1, \"greet\", &p->greet, true) < 0)\n        goto mverror;\n\n    // Parse timeout\n    if (moon_visit_num(&mv, -1, \"timeout\", &p->tmo, true) < 0)\n        goto mverror;\n\n    // Parse watch\n    if (moon_visit_table_f(&mv, -1, \"watch\", parse_watch_entry, pd, false) < 0)\n        goto mverror;\n\n    return LUASTATUS_OK;\n\nmverror:\n    LS_FATALF(pd, \"%s\", errbuf);\nerror:\n    destroy(pd);\n    return LUASTATUS_ERR;\n}\n\nstatic int l_add_watch(lua_State *L)\n{\n    char errbuf[256];\n    MoonVisit mv = {.L = L, .errbuf = errbuf, .nerrbuf = sizeof(errbuf)};\n\n    // Check we have at least two arguments\n    luaL_checkany(L, 1);\n    luaL_checkany(L, 2);\n\n    // Parse first arg\n    if (moon_visit_checktype_at(&mv, \"argument #1\", 1, LUA_TSTRING) < 0)\n        goto mverror;\n    const char *path = lua_tostring(L, 1);\n\n    // Parse second arg\n    uint32_t mask = 0;\n    if (moon_visit_table_f_at(&mv, \"argument #2\", 2, parse_evlist_elem, &mask) < 0)\n        goto mverror;\n\n    // Add watch\n    LuastatusPluginData *pd = lua_touserdata(L, lua_upvalueindex(1));\n    Priv *p = pd->priv;\n\n    int wd = inotify_add_watch(p->fd, path, mask);\n    if (wd < 0) {\n        LS_ERRF(pd, \"inotify_add_watch: %s: %s\", path, ls_tls_strerror(errno));\n        lua_pushnil(L);\n    } else {\n        lua_pushinteger(L, wd);\n    }\n    return 1;\n\nmverror:\n    return luaL_error(L, \"%s\", errbuf);\n}\n\nstatic int l_remove_watch(lua_State *L)\n{\n    int wd = luaL_checkinteger(L, 1);\n\n    LuastatusPluginData *pd = lua_touserdata(L, lua_upvalueindex(1));\n    Priv *p = pd->priv;\n\n    if (inotify_rm_watch(p->fd, wd) < 0) {\n        LS_ERRF(pd, \"inotify_rm_watch: %d: %s\", wd, ls_tls_strerror(errno));\n        lua_pushboolean(L, false);\n    } else {\n        lua_pushboolean(L, true);\n    }\n    return 1;\n}\n\nstatic int l_get_initial_wds(lua_State *L)\n{\n    LuastatusPluginData *pd = lua_touserdata(L, lua_upvalueindex(1));\n    Priv *p = pd->priv;\n\n    lua_newtable(L); // L: table\n    for (size_t i = 0; i < p->init_watch.size; ++i) {\n        lua_pushinteger(L, p->init_watch.data[i].wd); // L: table wd\n        lua_setfield(L, -2, p->init_watch.data[i].path); // L: table\n    }\n    return 1;\n}\n\nstatic int l_get_supported_events(lua_State *L)\n{\n    lua_createtable(L, 0, EVENT_TYPES_NUM); // L: table\n    for (const EventType *et = EVENT_TYPES; et != EVENT_TYPES_END; ++et) {\n        const char *v = et->in ? (et->out ? \"io\" : \"i\") : \"o\";\n        lua_pushstring(L, v); // L: table v\n        lua_setfield(L, -2, et->name); // L: table\n    }\n    return 1;\n}\n\nstatic void register_funcs(LuastatusPluginData *pd, lua_State *L)\n{\n    // L: table\n    lua_pushlightuserdata(L, pd); // L: table pd\n    lua_pushcclosure(L, l_add_watch, 1); // L: table closure\n    lua_setfield(L, -2, \"add_watch\"); // L: table\n\n    // L: table\n    lua_pushlightuserdata(L, pd); // L: table pd\n    lua_pushcclosure(L, l_remove_watch, 1); // L: table closure\n    lua_setfield(L, -2, \"remove_watch\"); // L: table\n\n    // L: table\n    lua_pushlightuserdata(L, pd); // L: table pd\n    lua_pushcclosure(L, l_get_initial_wds, 1); // L: table closure\n    lua_setfield(L, -2, \"get_initial_wds\"); // L: table\n\n    // L: table\n    lua_pushcfunction(L, l_get_supported_events); // L: table func\n    lua_setfield(L, -2, \"get_supported_events\"); // L: table\n\n    procalive_lfuncs_register_all(L); // L: table\n\n    Priv *p = pd->priv;\n    // L: table\n    ls_pushed_timeout_push_luafunc(&p->pushed_tmo, L); // L: table func\n    lua_setfield(L, -2, \"push_timeout\"); // L: table\n}\n\nstatic void push_event(lua_State *L, const struct inotify_event *event)\n{\n    // L: -\n    lua_createtable(L, 0, 4); // L: table\n\n    lua_pushstring(L, \"event\"); // L: table string\n    lua_setfield(L, -2, \"what\"); // L: table\n\n    lua_pushinteger(L, event->wd); // L: table wd\n    lua_setfield(L, -2, \"wd\"); // L: table\n\n    lua_newtable(L); // L: table table\n    for (const EventType *et = EVENT_TYPES; et != EVENT_TYPES_END; ++et) {\n        if (et->out && (event->mask & et->mask)) {\n            lua_pushboolean(L, true); // L: table table true\n            lua_setfield(L, -2, et->name); // L: table table\n        }\n    }\n    lua_setfield(L, -2, \"mask\"); // L: table\n\n    lua_pushnumber(L, event->cookie); // L: table cookie\n    lua_setfield(L, -2, \"cookie\"); // L: table\n\n    if (event->len) {\n        lua_pushstring(L, event->name); // L: table name\n        lua_setfield(L, -2, \"name\"); // L: table\n    }\n}\n\nstatic void run(LuastatusPluginData *pd, LuastatusPluginRunFuncs funcs)\n{\n    Priv *p = pd->priv;\n    // We allocate the buffer for /struct inotify_event/'s on the heap rather than on the stack in\n    // order to get the maximum possible alignment for it and not resort to compiler-dependent hacks\n    // like this one recommended by inotify(7):\n    //     /__attribute__ ((aligned(__alignof__(struct inotify_event))))/.\n    enum { NBUF = sizeof(struct inotify_event) + NAME_MAX + 2 };\n    char *buf = LS_XNEW(char, NBUF);\n\n    if (p->greet) {\n        lua_State *L = funcs.call_begin(pd->userdata);\n        lua_createtable(L, 0, 1); // L: table\n        lua_pushstring(L, \"hello\"); // L: table string\n        lua_setfield(L, -2, \"what\"); // L: table\n        funcs.call_end(pd->userdata);\n    }\n\n    LS_TimeDelta default_tmo = ls_double_to_TD(p->tmo, LS_TD_FOREVER);\n    while (1) {\n        LS_TimeDelta TD = ls_pushed_timeout_fetch(&p->pushed_tmo, default_tmo);\n        int nfds = ls_wait_input_on_fd(p->fd, TD);\n\n        if (nfds < 0) {\n            LS_FATALF(pd, \"ls_wait_input_on_fd: %s\", ls_tls_strerror(errno));\n            goto error;\n\n        } else if (nfds == 0) {\n            lua_State *L = funcs.call_begin(pd->userdata);\n            lua_createtable(L, 0, 1); // L: table\n            lua_pushstring(L, \"timeout\"); // L: table string\n            lua_setfield(L, -2, \"what\"); // L: table\n            funcs.call_end(pd->userdata);\n\n        } else {\n            ssize_t r = read(p->fd, buf, NBUF);\n            if (r < 0) {\n                if (errno == EINTR) {\n                    continue;\n                }\n                LS_FATALF(pd, \"read: %s\", ls_tls_strerror(errno));\n                goto error;\n            } else if (r == 0) {\n                LS_FATALF(pd, \"read() from the inotify file descriptor returned 0\");\n                goto error;\n            } else if (r == NBUF) {\n                LS_FATALF(pd, \"got an event with filename length > NAME_MAX+1\");\n                goto error;\n            }\n            const struct inotify_event *event;\n            for (char *ptr = buf;\n                 ptr < buf + r;\n                 ptr += sizeof(struct inotify_event) + event->len)\n            {\n                event = (const struct inotify_event *) ptr;\n                push_event(funcs.call_begin(pd->userdata), event);\n                funcs.call_end(pd->userdata);\n            }\n        }\n    }\n\nerror:\n    free(buf);\n}\n\nLuastatusPluginIface luastatus_plugin_iface_v1 = {\n    .init = init,\n    .register_funcs = register_funcs,\n    .run = run,\n    .destroy = destroy,\n};\n"
  },
  {
    "path": "plugins/inotify/inotify_compat.h",
    "content": "/*\n * Copyright (C) 2015-2025  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifndef inotify_compat_h_\n#define inotify_compat_h_\n\n#include <stdbool.h>\n#include <sys/inotify.h>\n#include <errno.h>\n\n#include \"libls/ls_io_utils.h\"\n#include \"libls/ls_compdep.h\"\n\n#include \"probes.generated.h\"\n\nLS_INHEADER int compat_inotify_init(bool nonblock, bool cloexec)\n{\n#if HAVE_INOTIFY_INIT1\n    return inotify_init1((nonblock ? IN_NONBLOCK : 0) |\n                         (cloexec  ? IN_CLOEXEC  : 0));\n#else\n    int fd = inotify_init();\n    if (fd < 0) {\n        return -1;\n    }\n    if (nonblock) {\n        ls_make_nonblock(fd);\n    }\n    if (cloexec) {\n        ls_make_cloexec(fd);\n    }\n    return fd;\n#endif\n}\n\n#endif\n"
  },
  {
    "path": "plugins/inotify/probes.in.h",
    "content": "#ifndef luastatus_plugin_inotify_probes_h_\n#define luastatus_plugin_inotify_probes_h_\n\n#cmakedefine01 HAVE_INOTIFY_INIT1\n\n#endif\n"
  },
  {
    "path": "plugins/is-program-running/CMakeLists.txt",
    "content": "install (FILES is-program-running.lua DESTINATION ${LUA_PLUGINS_DIR})\n\nluastatus_add_man_page (README.rst luastatus-plugin-is-program-running 7)\n"
  },
  {
    "path": "plugins/is-program-running/README.rst",
    "content": ".. :X-man-page-only: luastatus-plugin-is-program-running\n.. :X-man-page-only: ###################################\n.. :X-man-page-only:\n.. :X-man-page-only: #########################################################\n.. :X-man-page-only: Plugin for luastatus which checks if a program is running\n.. :X-man-page-only: #########################################################\n.. :X-man-page-only:\n.. :X-man-page-only: :Copyright: LGPLv3\n.. :X-man-page-only: :Manual section: 7\n\nOverview\n========\nThis derived plugin checks whether a certain program is running, although\nin order to this plugin to work, the program should indicate its state\nvia one of the following widespread mechanisms:\n\n1. PID file;\n2. Creation of a file. The file must have a fixed path;\n3. Creation of a file (with any name) in an otherwise-empty directory. The directory\n   must have a fixed path.\n\nFunctions\n=========\nThe following functions are provided:\n\n* ``make_watcher(kind, path)``\n\n  Returns a watcher; a watcher is a function that takes no argument and returns a single\n  boolean value indicating whether the program is running.\n\n  Kind must be either ``\"pidfile\"``, ``\"file_exists\"``, ``\"dir_nonempty\"`` or ``\"dir_nonempty_with_hidden\"``:\n  * ``\"pidfile\"`` means check by PID file located at path ``path``;\n  * ``\"file_exists\"`` means check by existence of a file or directory at path ``path``;\n  * ``\"dir_nonempty\"`` means check by whether a directory located at path ``path`` is non-empty (**excluding** hidden files);\n  * ``\"dir_nonempty_with_hidden\"`` means check by whether a directory located at path ``path`` is non-empty (**including** hidden files).\n\n* ``widget(tbl)``\n\n  Constructs a ``widget`` table required by luastatus. ``tbl`` is a table with the following\n  fields:\n\n  **(required)**\n\n  - ``cb``: a function\n\n    The callback function that will be called either with a boolean (if ``many`` is not specified)\n    or a table (if ``many`` is specified).\n\n  **(exactly one of the following is required)**\n\n  - ``kind`` and ``path``: strings\n\n    Please refer to the documentation for the ``make_watcher`` function above for\n    semantics of ``kind`` and ``path``.\n    If ``kind`` and ``path`` are specified, the plugin will only monitor state of a\n    single program, and ``cb`` will be called with a boolean argument.\n\n  - ``many``: array\n\n    If specified, the plugin will monitor states of N programs, where N is the length\n    of the ``many`` array. The array should contain tables (each being a watcher\n    specification) with the following entries:\n\n    * ``id``: string\n\n      A string used to identify this entry in the ``many`` array;\n\n    * ``kind`` and ``path``: strings\n\n      Please refer to the documentation for the ``make_watcher`` function above for\n      semantics of ``kind`` and ``path``.\n\n    If ``many`` is specified, ``cb`` will be called with a table argument in which\n    keys are identification strings (``id`` entry in the watcher specification;\n    see above), and values are booleans indicating whether the corresponding\n    process is running.\n\n  **(optional)**\n\n  - ``timer_opts``\n\n    Options to pass to the underlying ``timer`` plugin. Note that this includes the period\n    with which this plugin will check if the program(s) is running.\n    The period of the ``timer`` plugin defaults to 1 second.\n\n  - ``event``\n\n    The ``event`` entry of the resulting table (see ``luastatus`` documentation for the\n    description of ``widget.event`` field).\n"
  },
  {
    "path": "plugins/is-program-running/is-program-running.lua",
    "content": "--[[\n  Copyright (C) 2015-2025  luastatus developers\n\n  This file is part of luastatus.\n\n  luastatus is free software: you can redistribute it and/or modify\n  it under the terms of the GNU Lesser General Public License as published by\n  the Free Software Foundation, either version 3 of the License, or\n  (at your option) any later version.\n\n  luastatus is distributed in the hope that it will be useful,\n  but WITHOUT ANY WARRANTY; without even the implied warranty of\n  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n  GNU Lesser General Public License for more details.\n\n  You should have received a copy of the GNU Lesser General Public License\n  along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n--]]\n\nlocal P = {}\n\nlocal function check_glob(pattern, with_hidden)\n    local res = luastatus.plugin.glob(pattern)\n    if (not res) or (#res == 0) then\n        return false\n    end\n    if not with_hidden then\n        return true\n    end\n    for _, s in ipairs(res) do\n        -- Check if 's' ends with either \"/./\" or \"/../\" to skip \".\" and \"..\"\n        -- entries that get appended if the glob ends with \".*\".\n        if not s:match('/%.%.?/?$') then\n            return true\n        end\n    end\n    return false\nend\n\nfunction P.make_watcher(kind, path)\n    assert(type(kind) == 'string')\n    assert(type(path) == 'string')\n\n    if kind == 'pidfile' then\n        return function()\n            local f = io.open(path, 'r')\n            if not f then\n                return false\n            end\n            local pid = f:read('*line')\n            f:close()\n            if not pid then\n                return false\n            end\n            local is_ok, is_alive = pcall(luastatus.plugin.is_process_alive, pid)\n            return is_ok and is_alive\n        end\n\n    elseif kind == 'file_exists' then\n        return function()\n            return luastatus.plugin.access(path)\n        end\n\n    elseif kind == 'dir_nonempty' then\n        return function()\n            return check_glob(path .. '/*')\n        end\n\n    elseif kind == 'dir_nonempty_with_hidden' then\n        return function()\n            return check_glob(path .. '/*') or check_glob(path .. '/.*', true)\n        end\n\n    else\n        error(string.format('unknown kind \"%s\"', kind))\n    end\nend\n\nfunction P.widget(tbl)\n    local watcher_ids\n    local watchers\n    local single_watcher\n    if tbl.many then\n        assert(P.kind == nil)\n        assert(P.path == nil)\n        watchers = {}\n        watcher_ids = {}\n        for _, elem in ipairs(tbl.many) do\n            table.insert(watcher_ids, elem.id)\n            table.insert(watchers, P.make_watcher(elem.knid, elem.path))\n        end\n    else\n        single_watcher = P.make_watcher(tbl.kind, tbl.path)\n    end\n\n    return {\n        plugin = 'timer',\n        opts = tbl.timer_opts,\n        cb = function()\n            if single_watcher then\n                return tbl.cb(single_watcher())\n            else\n                local t = {}\n                for i, id in ipairs(watcher_ids) do\n                    local watcher = watchers[i]\n                    t[id] = watcher()\n                end\n                return tbl.cb(t)\n            end\n        end,\n        event = tbl.event,\n    }\nend\n\nreturn P\n"
  },
  {
    "path": "plugins/mem-usage-linux/CMakeLists.txt",
    "content": "install (FILES mem-usage-linux.lua DESTINATION ${LUA_PLUGINS_DIR})\n\nluastatus_add_man_page (README.rst luastatus-plugin-mem-usage-linux 7)\n"
  },
  {
    "path": "plugins/mem-usage-linux/README.rst",
    "content": ".. :X-man-page-only: luastatus-mem-usage-linux\n.. :X-man-page-only: #########################\n.. :X-man-page-only:\n.. :X-man-page-only: ################################################\n.. :X-man-page-only: Linux-specific memory usage plugin for luastatus\n.. :X-man-page-only: ################################################\n.. :X-man-page-only:\n.. :X-man-page-only: :Copyright: LGPLv3\n.. :X-man-page-only: :Manual section: 7\n\n\nOverview\n========\nThis derived plugin periodically polls Linux ``procfs`` for memory usage.\n\nFunctions\n=========\n* ``get_usage()``\n\n  Returns a table with two entries, ``avail`` and ``total``. Both are tables that have ``value``\n  (a number) and ``unit`` (a string) entries; you can assume ``unit`` is always ``\"kB\"``.\n\n* ``widget(tbl)``\n\n  Constructs a ``widget`` table required by luastatus. ``tbl`` is a table with the following\n  fields:\n\n  **(required)**\n\n  - ``cb``: function\n\n    The callback that will be called with a table, which the same as one returned by ``get_usage()``.\n\n  **(optional)**\n\n  - ``timer_opts``: table\n\n    Options for the underlying ``timer`` plugin.\n\n  - ``event``\n\n    The ``event`` entry of the resulting table (see ``luastatus`` documentation for the\n    description of ``widget.event`` field).\n"
  },
  {
    "path": "plugins/mem-usage-linux/mem-usage-linux.lua",
    "content": "--[[\n  Copyright (C) 2015-2025  luastatus developers\n\n  This file is part of luastatus.\n\n  luastatus is free software: you can redistribute it and/or modify\n  it under the terms of the GNU Lesser General Public License as published by\n  the Free Software Foundation, either version 3 of the License, or\n  (at your option) any later version.\n\n  luastatus is distributed in the hope that it will be useful,\n  but WITHOUT ANY WARRANTY; without even the implied warranty of\n  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n  GNU Lesser General Public License for more details.\n\n  You should have received a copy of the GNU Lesser General Public License\n  along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n--]]\n\nlocal P = {}\n\nlocal DEFAULT_PROCPATH = '/proc'\n\nlocal function do_with_file(f, callback)\n    local is_ok, err = pcall(callback)\n    f:close()\n    if not is_ok then\n        error(err)\n    end\nend\n\nlocal function get_usage_impl(procpath)\n    local f = assert(io.open(procpath .. '/meminfo', 'r'))\n    local r = {}\n    do_with_file(f, function()\n        for line in f:lines() do\n            local key, value, unit = line:match('(%w+):%s+(%w+)%s+(%w+)')\n            if key == 'MemTotal' then\n                r.total = {value = assert(tonumber(value)), unit = unit}\n            elseif key == 'MemAvailable' then\n                r.avail = {value = assert(tonumber(value)), unit = unit}\n            end\n        end\n    end)\n    return r\nend\n\nfunction P.get_usage()\n    return get_usage_impl(DEFAULT_PROCPATH)\nend\n\nfunction P.widget(tbl)\n    local procpath = tbl._procpath or DEFAULT_PROCPATH\n    return {\n        plugin = 'timer',\n        opts = tbl.timer_opts,\n        cb = function()\n            return tbl.cb(get_usage_impl(procpath))\n        end,\n        event = tbl.event,\n    }\nend\n\nreturn P\n"
  },
  {
    "path": "plugins/mpd/CMakeLists.txt",
    "content": "file (GLOB sources \"*.c\")\nluastatus_add_plugin (\n    plugin-mpd\n    $<TARGET_OBJECTS:ls>\n    $<TARGET_OBJECTS:moonvisit>\n    $<TARGET_OBJECTS:safe>\n    ${sources}\n)\n\ntarget_compile_definitions (plugin-mpd PUBLIC -D_POSIX_C_SOURCE=200809L)\nluastatus_target_compile_with (plugin-mpd LUA)\ntarget_include_directories (plugin-mpd PUBLIC \"${PROJECT_SOURCE_DIR}\")\n\nluastatus_add_man_page (README.rst luastatus-plugin-mpd 7)\n"
  },
  {
    "path": "plugins/mpd/README.rst",
    "content": ".. :X-man-page-only: luastatus-plugin-mpd\n.. :X-man-page-only: #####################\n.. :X-man-page-only:\n.. :X-man-page-only: ########################\n.. :X-man-page-only: mpd plugin for luastatus\n.. :X-man-page-only: ########################\n.. :X-man-page-only:\n.. :X-man-page-only: :Copyright: LGPLv3\n.. :X-man-page-only: :Manual section: 7\n\nOverview\n========\nThis plugin monitors state of an mpd server.\n\nOptions\n=======\n* ``hostname``: string\n\n  Hostname to connect to. Default is to connect to the local host. An absolute path to a UNIX\n  domain socket can also be specified (``port`` and ``bind`` are ignored then).\n\n* ``port``: number\n\n  Port to connect to. Default is 6600.\n\n* ``password``: string\n\n  Server's password.\n\n* ``timeout``: number\n\n  If specified and not negative, the number of seconds to wait before calling ``cb`` with\n  ``what=\"timeout\"`` again (after a connection has been established). May be fractional.\n\n* ``retry_in``: number\n\n  Number of seconds to retry in after the connection is lost. A negative value means do not retry\n  and return immediately. May be fractional. Defaults to 10.\n\n* ``retry_fifo``: string\n\n  Path to an existent FIFO. The plugin does not create FIFO itself. To force a reconnect,\n  ``touch(1)`` the FIFO, that is, open it for writing and then close.\n\n* ``events``: array of strings\n\n  List of MPD subsystems to poll for changes in. See the description of ``idle`` command at\n  https://www.musicpd.org/doc/html/protocol.html#querying-mpd-s-status for the complete list.\n  Default is ``{\"mixer\",\"player\"}``.\n\n* ``enable_tcp_keepalive``: bool\n\n  Whether or not to enable TCP keepalive. Defaults to ``false``.\n  This option is ignored if the plugin is configured to connect to a UNIX domain socket.\n\n* ``bind``: table\n\n  If provided, the plugin will bind the (TCP, not UNIX) socket to a specific address.\n  The parameters for the binding are specified by this table.\n\n  If the plugin is otherwise configured to connect to a UNIX domain socket (via ``hostname``\n  option that starts with a slash), this option is ignored.\n\n  If this option is provided, it must be a table with the following keys:\n\n  - ``addr``: the value must be a string representing either IPv4 or IPv6 address.\n\n  - ``ipver``: the value must be a string, either ``\"ipv4\"`` for IPv4 address or ``\"ipv6\"`` for IPv6 address.\n\n  Note that there is no default for the ``ipver`` field, and there is no guessing what sort of\n  address it is; if the ``bind`` table is present, but does not contain ``ipver`` key, the\n  plugin will fail to initialize.\n\n\n``cb`` argument\n===============\nA table with ``what`` entry.\n\n* If ``what`` is ``\"connecting\"``, the plugin is now connecting to the server.\n\n* If ``what`` is ``\"update\"``, either the plugin has just connected to the server and queried its\n  state, or the server has just changed its state. Additionally, the following entries are provided:\n\n  - ``song``: table with server's response to the ``currentsong`` command. Surprisingly, it is not\n    documented at all, so here is an example (all values are strings):\n\n    .. rst2man does not support tables with headers, so let's just use bold.\n\n    +----------------------+-----------------------------+\n    | **Key**              | **Value**                   |\n    +----------------------+-----------------------------+\n    | file                 | Sensou to Heiwa.mp3         |\n    +----------------------+-----------------------------+\n    | Last-Modified        | 2016-07-31T09:56:31Z        |\n    +----------------------+-----------------------------+\n    | Artist               | ALI PROJECT                 |\n    +----------------------+-----------------------------+\n    | AlbumArtist          | ALI PROJECT                 |\n    +----------------------+-----------------------------+\n    | Title                | 戦争と平和                  |\n    +----------------------+-----------------------------+\n    | Album                | Erotic & Heretic            |\n    +----------------------+-----------------------------+\n    | Track                | 8                           |\n    +----------------------+-----------------------------+\n    | Date                 | 2002-02-20                  |\n    +----------------------+-----------------------------+\n    | Genre                | J-Pop                       |\n    +----------------------+-----------------------------+\n    | Disc                 | 1/1                         |\n    +----------------------+-----------------------------+\n    | Time                 | 260                         |\n    +----------------------+-----------------------------+\n    | Pos                  | 0                           |\n    +----------------------+-----------------------------+\n    | Id                   | 4                           |\n    +----------------------+-----------------------------+\n\n  - ``status``: table with server's response to the ``status`` command. See the ``status`` command\n    description at https://www.musicpd.org/doc/html/protocol.html#querying-mpd-s-status.\n\n    All values are strings.\n\n* It ``what`` is ``\"timeout\"``, the server hasn't changed its state for the number of seconds\n  specified as the ``timeout`` option.\n\n* If ``what`` is ``\"error\"``, the connection has been lost; the plugin is now going to sleep and try\n  to reconnect. This is only reported if ``retry_in`` is non-negative.\n"
  },
  {
    "path": "plugins/mpd/connect.c",
    "content": "/*\n * Copyright (C) 2015-2025  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#include \"connect.h\"\n\n#include <stddef.h>\n#include <unistd.h>\n#include <string.h>\n#include <sys/un.h>\n#include <sys/socket.h>\n#include <arpa/inet.h>\n#include <netinet/in.h>\n#include <netdb.h>\n#include <errno.h>\n\n#include \"include/plugin_data_v1.h\"\n#include \"include/sayf_macros.h\"\n\n#include \"libls/ls_tls_ebuf.h\"\n#include \"libls/ls_osdep.h\"\n#include \"libls/ls_io_utils.h\"\n#include \"libls/ls_panic.h\"\n\nint unixdom_open(LuastatusPluginData *pd, const char *path)\n{\n    LS_ASSERT(path != NULL);\n\n    int fd = -1;\n\n    struct sockaddr_un saun = {.sun_family = AF_UNIX};\n    size_t npath = strlen(path);\n    if (npath + 1 > sizeof(saun.sun_path)) {\n        LS_ERRF(pd, \"socket path is too long: %s\", path);\n        goto error;\n    }\n    memcpy(saun.sun_path, path, npath + 1);\n    fd = ls_cloexec_socket(PF_UNIX, SOCK_STREAM, 0);\n    if (fd < 0) {\n        LS_ERRF(pd, \"socket: %s\", ls_tls_strerror(errno));\n        goto error;\n    }\n    if (connect(fd, (struct sockaddr *) &saun, sizeof(saun)) < 0) {\n        LS_ERRF(pd, \"connect: %s: %s\", path, ls_tls_strerror(errno));\n        goto error;\n    }\n    return fd;\nerror:\n    ls_close(fd);\n    return -1;\n}\n\nstatic int do_bind_to_addr(int fd, const char *addr_str, BindAddrFamily family)\n{\n    switch (family) {\n\n    case FAMILY_NONE:\n        return 0;\n\n    case FAMILY_IPV4:\n        {\n            LS_ASSERT(addr_str != NULL);\n            struct sockaddr_in sa = {\n                .sin_family = AF_INET,\n            };\n            if (!inet_pton(AF_INET, addr_str, &sa.sin_addr)) {\n                goto bad_str;\n            }\n            return bind(fd, (struct sockaddr *) &sa, sizeof(sa));\n        }\n\n    case FAMILY_IPV6:\n        {\n            LS_ASSERT(addr_str != NULL);\n            struct sockaddr_in6 sa = {\n                .sin6_family = AF_INET6,\n            };\n            if (!inet_pton(AF_INET6, addr_str, &sa.sin6_addr)) {\n                goto bad_str;\n            }\n            return bind(fd, (struct sockaddr *) &sa, sizeof(sa));\n        }\n    }\n    LS_MUST_BE_UNREACHABLE();\n\nbad_str:\n    errno = EINVAL;\n    return -1;\n}\n\nstatic int bind_addr_family2af(BindAddrFamily family)\n{\n    switch (family) {\n    case FAMILY_NONE:\n        return AF_UNSPEC;\n    case FAMILY_IPV4:\n        return AF_INET;\n    case FAMILY_IPV6:\n        return AF_INET6;\n    }\n    LS_MUST_BE_UNREACHABLE();\n}\n\nint inetdom_open(\n        LuastatusPluginData *pd,\n        const char *hostname,\n        const char *service,\n        const char *bind_addr,\n        BindAddrFamily bind_addr_family)\n{\n    LS_ASSERT(service != NULL);\n\n    struct addrinfo *ai = NULL;\n    int fd = -1;\n\n    int af = bind_addr_family2af(bind_addr_family);\n    struct addrinfo hints = {\n        .ai_family = af,\n        .ai_socktype = SOCK_STREAM,\n        .ai_protocol = 0,\n        .ai_flags = AI_ADDRCONFIG,\n    };\n\n    int gai_r = getaddrinfo(hostname, service, &hints, &ai);\n    if (gai_r) {\n        if (gai_r == EAI_SYSTEM) {\n            LS_ERRF(pd, \"getaddrinfo: %s\", ls_tls_strerror(errno));\n        } else {\n            LS_ERRF(pd, \"getaddrinfo: %s\", gai_strerror(gai_r));\n        }\n        ai = NULL;\n        goto cleanup;\n    }\n\n    for (struct addrinfo *pai = ai; pai; pai = pai->ai_next) {\n        fd = ls_cloexec_socket(pai->ai_family, pai->ai_socktype, pai->ai_protocol);\n        if (fd < 0) {\n            LS_WARNF(pd, \"(candiate) socket: %s\", ls_tls_strerror(errno));\n            continue;\n        }\n        if (do_bind_to_addr(fd, bind_addr, bind_addr_family) < 0) {\n            LS_WARNF(pd, \"(candiate) cannot bind to address: %s\", ls_tls_strerror(errno));\n            close(fd);\n            fd = -1;\n            continue;\n        }\n        if (connect(fd, pai->ai_addr, pai->ai_addrlen) < 0) {\n            LS_WARNF(pd, \"(candiate) connect: %s\", ls_tls_strerror(errno));\n            close(fd);\n            fd = -1;\n            continue;\n        }\n        break;\n    }\n    if (fd < 0) {\n        LS_ERRF(pd, \"can't connect to any of the candidates\");\n    }\n\ncleanup:\n    if (ai) {\n        freeaddrinfo(ai);\n    }\n    return fd;\n}\n"
  },
  {
    "path": "plugins/mpd/connect.h",
    "content": "/*\n * Copyright (C) 2015-2025  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifndef connect_h_\n#define connect_h_\n\n#include \"include/plugin_data_v1.h\"\n\ntypedef enum {\n    FAMILY_NONE,\n    FAMILY_IPV4,\n    FAMILY_IPV6,\n} BindAddrFamily;\n\nint unixdom_open(LuastatusPluginData *pd, const char *path);\n\nint inetdom_open(\n    LuastatusPluginData *pd,\n    const char *hostname,\n    const char *service,\n    const char *bind_addr,\n    BindAddrFamily bind_addr_family);\n\n#endif\n"
  },
  {
    "path": "plugins/mpd/fuzz/.gitignore",
    "content": "harness_check_greeting\nharness_get_resp_type\nharness_write_quoted\nharness_append_kv\nfindings_check_greeting\nfindings_get_resp_type\nfindings_write_quoted\nfindings_append_kv\n"
  },
  {
    "path": "plugins/mpd/fuzz/build.sh",
    "content": "#!/bin/sh\n\nset -e\n\nif [ -z \"$CC\" ]; then\n    echo >&2 \"You must set the 'CC' environment variable.\"\n    echo >&2 \"Hint: you probably want to set 'CC' to 'some-directory/afl-gcc'.\"\n    exit 1\nfi\n\ncd -- \"$(dirname \"$(readlink \"$0\" || printf '%s\\n' \"$0\")\")\"\n\nluastatus_root=../../..\n\ndo_build_variant() {\n    local extra_opts=$1\n    local output=$2\n\n    printf '%s\\n' >&2 \"Building with $extra_opts -> $output\"\n\n    $CC -Wall -Wextra -O3 -fsanitize=undefined -std=c99 -D_POSIX_C_SOURCE=200809L \\\n        $extra_opts \\\n        -I\"$luastatus_root\" \\\n        ./harness.c \\\n        ../safe_haven.c \\\n        \"$luastatus_root\"/libls/ls_string.c \\\n        \"$luastatus_root\"/libls/ls_alloc_utils.c \\\n        \"$luastatus_root\"/libls/ls_panic.c \\\n        \"$luastatus_root\"/libls/ls_cstring_utils.c \\\n        \"$luastatus_root\"/libsafe/*.c \\\n        -o \"$output\"\n}\n\ndo_build_variant -DMODE_CHECK_GREETING=1 harness_check_greeting\ndo_build_variant -DMODE_GET_RESP_TYPE=1 harness_get_resp_type\ndo_build_variant -DMODE_WRITE_QUOTED=1 harness_write_quoted\ndo_build_variant -DMODE_APPEND_KV=1 harness_append_kv\n"
  },
  {
    "path": "plugins/mpd/fuzz/clear.sh",
    "content": "#!/bin/sh\n\nset -e\n\ncd -- \"$(dirname \"$(readlink \"$0\" || printf '%s\\n' \"$0\")\")\"\n\nrm -rf \\\n    ./findings_check_greeting \\\n    ./findings_get_resp_type \\\n    ./findings_write_quoted \\\n    ./findings_append_kv\n"
  },
  {
    "path": "plugins/mpd/fuzz/fuzz.sh",
    "content": "#!/bin/sh\n\nset -e\n\nif [ -z \"$XXX_AFL_DIR\" ]; then\n    echo >&2 \"You must set the 'XXX_AFL_DIR' environment variable.\"\n    exit 1\nfi\n\ncd -- \"$(dirname \"$(readlink \"$0\" || printf '%s\\n' \"$0\")\")\"\n\ncase \"$1\" in\n1)\n    suffix=check_greeting\n    ;;\n2)\n    suffix=get_resp_type\n    ;;\n3)\n    suffix=write_quoted\n    ;;\n4)\n    suffix=append_kv\n    ;;\n*)\n    printf '%s\\n' >&2 \"USAGE: $0 {1 | 2 | 3 | 4}\"\n    exit 2\n    ;;\nesac\n\nfindings_dir=./findings_\"$suffix\"\ntestcases_dir=./testcases_\"$suffix\"\nharness_binary=./harness_\"$suffix\"\n\nmkdir -p \"$findings_dir\"\n\nexport UBSAN_OPTIONS=halt_on_error=1\n\nexport AFL_EXIT_WHEN_DONE=1\n\n\"$XXX_AFL_DIR\"/afl-fuzz -i \"$testcases_dir\" -o \"$findings_dir\" -t 5 \"$harness_binary\" @@\n"
  },
  {
    "path": "plugins/mpd/fuzz/fuzz_all.sh",
    "content": "#!/bin/sh\n\nset -e\n\ncd -- \"$(dirname \"$(readlink \"$0\" || printf '%s\\n' \"$0\")\")\"\n\nfor i in 1 2 3 4; do\n    ./fuzz.sh \"$i\"\ndone\n"
  },
  {
    "path": "plugins/mpd/fuzz/gen_testcases.sh",
    "content": "#!/bin/sh\n\nset -e\n\ncd -- \"$(dirname \"$(readlink \"$0\" || printf '%s\\n' \"$0\")\")\"\n\nluastatus_root=../../..\n\ncommon_args_noab='--length=5-20 --random-seed=123'\ncommon_args=\"--a=1:a --b=1:b $common_args_noab\"\n\n# check_greeting\n\"$luastatus_root\"/fuzz_utils/gen_testcases/gen_testcases.py \\\n    ./testcases_check_greeting \\\n    $common_args \\\n    --mut-prefix='OK MPD ' \\\n    --num-files=10 \\\n    --extra-testcase='pfx_only:OK MPD '\n\n# get_resp_type: OK\n\"$luastatus_root\"/fuzz_utils/gen_testcases/gen_testcases.py \\\n    ./testcases_get_resp_type \\\n    $common_args \\\n    --mut-prefix='OK' \\\n    --extra-testcase='pfx_only:OK' \\\n    --num-files=5 \\\n    --file-prefix='ok_'\n\n# get_resp_type: ACK\n\"$luastatus_root\"/fuzz_utils/gen_testcases/gen_testcases.py \\\n    ./testcases_get_resp_type \\\n    $common_args \\\n    --mut-prefix='ACK ' \\\n    --num-files=5 \\\n    --file-prefix='ack_'\n\n# get_resp_type: other\n\"$luastatus_root\"/fuzz_utils/gen_testcases/gen_testcases.py \\\n    ./testcases_get_resp_type \\\n    $common_args \\\n    --num-files=5 \\\n    --file-prefix='other_'\n\n# write_quoted\n\"$luastatus_root\"/fuzz_utils/gen_testcases/gen_testcases.py \\\n    ./testcases_write_quoted \\\n    $common_args_noab \\\n    --a=1:'\"' \\\n    --b=1:xyz \\\n    --a-is-important \\\n    --num-files=10\n\n# append_kv\n\"$luastatus_root\"/fuzz_utils/gen_testcases/gen_testcases.py \\\n    ./testcases_append_kv \\\n    $common_args \\\n    --mut-substrings='|: |:|' \\\n    --num-files=10\n"
  },
  {
    "path": "plugins/mpd/fuzz/harness.c",
    "content": "/*\n * Copyright (C) 2025  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#include <stdio.h>\n#include <stdlib.h>\n#include <stdbool.h>\n#include <string.h>\n#include <unistd.h>\n#include <fcntl.h>\n\n#include \"libls/ls_strarr.h\"\n#include \"libsafe/safev.h\"\n#include \"fuzz_utils/fuzz_utils.h\"\n\n#include \"../safe_haven.h\"\n\n#if MODE_CHECK_GREETING\n\nstatic void do_the_thing(SAFEV v)\n{\n    char c = is_good_greeting(v);\n    fuzz_utils_used(&c, 1);\n}\n\n#elif MODE_GET_RESP_TYPE\n\nstatic void do_the_thing(SAFEV v)\n{\n    char c = response_type(v);\n    fuzz_utils_used(&c, 1);\n}\n\n#elif MODE_WRITE_QUOTED\n\nstatic void do_the_thing(SAFEV v)\n{\n    char *buf;\n    size_t len;\n\n    FILE *f = open_memstream(&buf, &len);\n    if (!f) {\n        perror(\"open_memstream\");\n        abort();\n    }\n\n    write_quoted(f, v);\n\n    if (fclose(f) < 0) {\n        perror(\"fclose\");\n        abort();\n    }\n\n    fuzz_utils_used(buf, len);\n\n    free(buf);\n}\n\n#elif MODE_APPEND_KV\n\nstatic void do_the_thing(SAFEV v)\n{\n    LS_StringArray sa = ls_strarr_new_reserve(1024, 2);\n\n    append_line_to_kv_strarr(&sa, v);\n\n    size_t n = ls_strarr_size(sa);\n    for (size_t i = 0; i < n; ++i) {\n        size_t ns;\n        const char *s = ls_strarr_at(sa, i, &ns);\n        fuzz_utils_used(s, ns);\n    }\n\n    ls_strarr_destroy(sa);\n}\n\n#else\n# error \"Please define to 1 either: MODE_CHECK_GREETING || MODE_GET_RESP_TYPE || MODE_WRITE_QUOTED || MODE_APPEND_KV.\"\n#endif\n\nint main(int argc, char **argv)\n{\n    if (argc != 2) {\n        fprintf(stderr, \"USAGE: harness_* INPUT_FILE\\n\");\n        return 2;\n    }\n\n    int fd_in = open(argv[1], O_RDONLY | O_CLOEXEC);\n    if (fd_in < 0) {\n        perror(argv[1]);\n        abort();\n    }\n\n    FuzzInput input = fuzz_input_new_prealloc(1024);\n    if (fuzz_input_read(fd_in, &input) < 0) {\n        perror(\"read\");\n        abort();\n    }\n\n    SAFEV v = SAFEV_new_UNSAFE(input.data, input.size);\n\n    do_the_thing(v);\n\n    fuzz_input_free(input);\n    close(fd_in);\n\n    return 0;\n}\n"
  },
  {
    "path": "plugins/mpd/fuzz/testcases_append_kv/testcase_000",
    "content": "aaaaaa: "
  },
  {
    "path": "plugins/mpd/fuzz/testcases_append_kv/testcase_001",
    "content": "aaabaaa:"
  },
  {
    "path": "plugins/mpd/fuzz/testcases_append_kv/testcase_002",
    "content": "aaaaabaaaab"
  },
  {
    "path": "plugins/mpd/fuzz/testcases_append_kv/testcase_003",
    "content": "aaababaabaabb: aaa"
  },
  {
    "path": "plugins/mpd/fuzz/testcases_append_kv/testcase_004",
    "content": "baaaab:b"
  },
  {
    "path": "plugins/mpd/fuzz/testcases_append_kv/testcase_005",
    "content": "bbaabbbabbbaaabbaa"
  },
  {
    "path": "plugins/mpd/fuzz/testcases_append_kv/testcase_006",
    "content": "abbbbbab: a"
  },
  {
    "path": "plugins/mpd/fuzz/testcases_append_kv/testcase_007",
    "content": "bbbbbbaabb:bbabbab"
  },
  {
    "path": "plugins/mpd/fuzz/testcases_append_kv/testcase_008",
    "content": "bbbabbbabbbbbbbbb"
  },
  {
    "path": "plugins/mpd/fuzz/testcases_append_kv/testcase_009",
    "content": "bbb: bb"
  },
  {
    "path": "plugins/mpd/fuzz/testcases_check_greeting/testcase_000",
    "content": "aaaaaa"
  },
  {
    "path": "plugins/mpd/fuzz/testcases_check_greeting/testcase_001",
    "content": "Oaaabaaa"
  },
  {
    "path": "plugins/mpd/fuzz/testcases_check_greeting/testcase_002",
    "content": "OKaaaabaaaaababaaaaaba"
  },
  {
    "path": "plugins/mpd/fuzz/testcases_check_greeting/testcase_003",
    "content": "OK babbaaaa"
  },
  {
    "path": "plugins/mpd/fuzz/testcases_check_greeting/testcase_004",
    "content": "OK Mbaaaabb"
  },
  {
    "path": "plugins/mpd/fuzz/testcases_check_greeting/testcase_005",
    "content": "OK MPababbabbbaabaabab"
  },
  {
    "path": "plugins/mpd/fuzz/testcases_check_greeting/testcase_006",
    "content": "OK MPDabbaabbaabbbabbabbbb"
  },
  {
    "path": "plugins/mpd/fuzz/testcases_check_greeting/testcase_007",
    "content": "OK MPD bbbbbbbbbbbbaaa"
  },
  {
    "path": "plugins/mpd/fuzz/testcases_check_greeting/testcase_008",
    "content": "bbabbbbb"
  },
  {
    "path": "plugins/mpd/fuzz/testcases_check_greeting/testcase_009",
    "content": "Obbbbbbbbbbb"
  },
  {
    "path": "plugins/mpd/fuzz/testcases_check_greeting/testcase_pfx_only",
    "content": "OK MPD "
  },
  {
    "path": "plugins/mpd/fuzz/testcases_get_resp_type/ack_testcase_000",
    "content": "aaaaaa"
  },
  {
    "path": "plugins/mpd/fuzz/testcases_get_resp_type/ack_testcase_001",
    "content": "Abaabaaa"
  },
  {
    "path": "plugins/mpd/fuzz/testcases_get_resp_type/ack_testcase_002",
    "content": "ACaaaabaababbabbbbabba"
  },
  {
    "path": "plugins/mpd/fuzz/testcases_get_resp_type/ack_testcase_003",
    "content": "ACKbbbbabba"
  },
  {
    "path": "plugins/mpd/fuzz/testcases_get_resp_type/ack_testcase_004",
    "content": "ACK bbbbbbb"
  },
  {
    "path": "plugins/mpd/fuzz/testcases_get_resp_type/ok_testcase_000",
    "content": "aaaaaa"
  },
  {
    "path": "plugins/mpd/fuzz/testcases_get_resp_type/ok_testcase_001",
    "content": "Obaabaaa"
  },
  {
    "path": "plugins/mpd/fuzz/testcases_get_resp_type/ok_testcase_002",
    "content": "OKaaaabaababbabbbbabba"
  },
  {
    "path": "plugins/mpd/fuzz/testcases_get_resp_type/ok_testcase_003",
    "content": "bbbbabba"
  },
  {
    "path": "plugins/mpd/fuzz/testcases_get_resp_type/ok_testcase_004",
    "content": "Obbbbbbb"
  },
  {
    "path": "plugins/mpd/fuzz/testcases_get_resp_type/ok_testcase_pfx_only",
    "content": "OK"
  },
  {
    "path": "plugins/mpd/fuzz/testcases_get_resp_type/other_testcase_000",
    "content": "aaaaaa"
  },
  {
    "path": "plugins/mpd/fuzz/testcases_get_resp_type/other_testcase_001",
    "content": "baabaaa"
  },
  {
    "path": "plugins/mpd/fuzz/testcases_get_resp_type/other_testcase_002",
    "content": "aaaabaababbabbbbabba"
  },
  {
    "path": "plugins/mpd/fuzz/testcases_get_resp_type/other_testcase_003",
    "content": "bbbbabba"
  },
  {
    "path": "plugins/mpd/fuzz/testcases_get_resp_type/other_testcase_004",
    "content": "bbbbbbb"
  },
  {
    "path": "plugins/mpd/fuzz/testcases_write_quoted/testcase_000",
    "content": "\"\"\"\"\"\""
  },
  {
    "path": "plugins/mpd/fuzz/testcases_write_quoted/testcase_001",
    "content": "\"\"\"\"y\"\"\"\"\""
  },
  {
    "path": "plugins/mpd/fuzz/testcases_write_quoted/testcase_002",
    "content": "\"\"\"\"\"\"\"z\"\"\"\"\"\"y\"x\"\"x"
  },
  {
    "path": "plugins/mpd/fuzz/testcases_write_quoted/testcase_003",
    "content": "xz\"\"\"\"z\"z\"y\"\"\"\"z\""
  },
  {
    "path": "plugins/mpd/fuzz/testcases_write_quoted/testcase_004",
    "content": "y\"x\"\"z\"z"
  },
  {
    "path": "plugins/mpd/fuzz/testcases_write_quoted/testcase_005",
    "content": "z\"\"xx\"z\"y"
  },
  {
    "path": "plugins/mpd/fuzz/testcases_write_quoted/testcase_006",
    "content": "x\"y\"z\"yy\"yyy\"yxx"
  },
  {
    "path": "plugins/mpd/fuzz/testcases_write_quoted/testcase_007",
    "content": "\"xyyxyx\"zy\"zy\"zxyy"
  },
  {
    "path": "plugins/mpd/fuzz/testcases_write_quoted/testcase_008",
    "content": "xxxxzxx\""
  },
  {
    "path": "plugins/mpd/fuzz/testcases_write_quoted/testcase_009",
    "content": "zyyyyzxyyxyxxx"
  },
  {
    "path": "plugins/mpd/line_reader.c",
    "content": "/*\n * Copyright (C) 2015-2025  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#include \"line_reader.h\"\n\n#include <stdio.h>\n#include <stdlib.h>\n#include <sys/types.h>\n#include \"libls/ls_alloc_utils.h\"\n#include \"libsafe/safev.h\"\n\nLineReader line_reader_new(size_t prealloc)\n{\n    return (LineReader) {\n        .buf = LS_XNEW(char, prealloc),\n        .capacity = prealloc,\n    };\n}\n\nint line_reader_read_line(LineReader *LR, FILE *f, SAFEV *out)\n{\n    ssize_t r = getline(&LR->buf, &LR->capacity, f);\n    if (r < 0) {\n        return -1;\n    }\n    SAFEV raw_v = SAFEV_new_UNSAFE(LR->buf, r);\n    *out = SAFEV_rstrip_once(raw_v, '\\n');\n    return 0;\n}\n\nvoid line_reader_destroy(LineReader *LR)\n{\n    free(LR->buf);\n}\n"
  },
  {
    "path": "plugins/mpd/line_reader.h",
    "content": "/*\n * Copyright (C) 2015-2025  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifndef line_reader_h_\n#define line_reader_h_\n\n#include <stddef.h>\n#include <stdio.h>\n#include \"libsafe/safev.h\"\n\ntypedef struct {\n    char *buf;\n    size_t capacity;\n} LineReader;\n\nLineReader line_reader_new(size_t prealloc);\n\nint line_reader_read_line(LineReader *LR, FILE *f, SAFEV *out);\n\nvoid line_reader_destroy(LineReader *LR);\n\n#endif\n"
  },
  {
    "path": "plugins/mpd/mpd.c",
    "content": "/*\n * Copyright (C) 2015-2025  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#include <lua.h>\n#include <errno.h>\n#include <stdlib.h>\n#include <stdint.h>\n#include <stdio.h>\n#include <string.h>\n#include <stdbool.h>\n#include <sys/socket.h>\n#include <netinet/tcp.h>\n#include <netinet/in.h>\n\n#include \"include/plugin_v1.h\"\n#include \"include/sayf_macros.h\"\n\n#include \"libmoonvisit/moonvisit.h\"\n\n#include \"libls/ls_string.h\"\n#include \"libls/ls_alloc_utils.h\"\n#include \"libls/ls_tls_ebuf.h\"\n#include \"libls/ls_time_utils.h\"\n#include \"libls/ls_fifo_device.h\"\n#include \"libls/ls_strarr.h\"\n#include \"libls/ls_io_utils.h\"\n#include \"libls/ls_panic.h\"\n#include \"libsafe/safev.h\"\n\n#include \"connect.h\"\n#include \"safe_haven.h\"\n#include \"line_reader.h\"\n\ntypedef struct {\n    char *hostname;\n    uint64_t port;\n    char *password;\n    double tmo;\n    double retry_tmo;\n    char *retry_fifo;\n    LS_String idle_str;\n\n    bool enable_tcp_keepalive;\n    char *bind_addr;\n    BindAddrFamily bind_addr_family;\n} Priv;\n\nstatic void destroy(LuastatusPluginData *pd)\n{\n    Priv *p = pd->priv;\n    free(p->hostname);\n    free(p->password);\n    free(p->retry_fifo);\n    ls_string_free(p->idle_str);\n    free(p->bind_addr);\n    free(p);\n}\n\nstatic int parse_events_elem(MoonVisit *mv, void *ud, int kpos, int vpos)\n{\n    mv->where = \"'events' element\";\n    (void) kpos;\n\n    Priv *p = ud;\n\n    if (moon_visit_checktype_at(mv, NULL, vpos, LUA_TSTRING) < 0)\n        return -1;\n\n    const char *s = lua_tostring(mv->L, vpos);\n    ls_string_append_c(&p->idle_str, ' ');\n    ls_string_append_s(&p->idle_str, s);\n    return 1;\n}\n\nstatic int parse_ipver(const char *s)\n{\n    if (strcmp(s, \"ipv4\") == 0) {\n        return FAMILY_IPV4;\n    }\n    if (strcmp(s, \"ipv6\") == 0) {\n        return FAMILY_IPV6;\n    }\n    return -1;\n}\n\nstatic int parse_bind_params(Priv *p, MoonVisit *mv, int table_pos)\n{\n    int old_top = lua_gettop(mv->L);\n\n    // mv->L: ?\n    if (moon_visit_scrutinize_table(mv, table_pos, \"bind\", true) < 0) {\n        goto fail;\n    }\n    // mv->L: ? bind\n    if (lua_isnil(mv->L, -1)) {\n        goto ok;\n    }\n\n    if (moon_visit_str(mv, -1, \"addr\", &p->bind_addr, NULL, false) < 0) {\n        goto fail;\n    }\n\n    const char *ipver;\n    if (moon_visit_scrutinize_str(mv, -1, \"ipver\", &ipver, NULL, false) < 0) {\n        goto fail;\n    }\n    // mv->L: ? bind ipver\n\n    int family = parse_ipver(ipver);\n    if (family < 0) {\n        moon_visit_err(mv, \"bind.ipver is invalid\");\n        goto fail;\n    }\n    p->bind_addr_family = family;\n\nok:\n    lua_settop(mv->L, old_top);\n    return 0;\n\nfail:\n    lua_settop(mv->L, old_top);\n    return -1;\n}\n\nstatic int init(LuastatusPluginData *pd, lua_State *L)\n{\n    Priv *p = pd->priv = LS_XNEW(Priv, 1);\n    *p = (Priv) {\n        .hostname = NULL,\n        .port = 6600,\n        .password = NULL,\n        .tmo = -1,\n        .retry_tmo = 10,\n        .retry_fifo = NULL,\n        .idle_str = ls_string_new_from_s(\"idle\"),\n        .enable_tcp_keepalive = false,\n        .bind_addr = NULL,\n        .bind_addr_family = FAMILY_NONE,\n    };\n    char errbuf[256];\n    MoonVisit mv = {.L = L, .errbuf = errbuf, .nerrbuf = sizeof(errbuf)};\n\n    // Parse hostname\n    if (moon_visit_str(&mv, -1, \"hostname\", &p->hostname, NULL, true) < 0)\n        goto mverror;\n\n    // Parse port\n    if (moon_visit_uint(&mv, -1, \"port\", &p->port, true) < 0)\n        goto mverror;\n    if (p->port > 65535) {\n        LS_FATALF(pd, \"port is not a valid port number\");\n        goto error;\n    }\n\n    // Parse password\n    if (moon_visit_str(&mv, -1, \"password\", &p->password, NULL, true) < 0)\n        goto mverror;\n    if (p->password && (strchr(p->password, '\\n'))) {\n        LS_FATALF(pd, \"password contains a line break\");\n        goto error;\n    }\n\n    // Parse timeout\n    if (moon_visit_num(&mv, -1, \"timeout\", &p->tmo, true) < 0)\n        goto mverror;\n\n    // Parse retry_in\n    if (moon_visit_num(&mv, -1, \"retry_in\", &p->retry_tmo, true) < 0)\n        goto mverror;\n\n    // Parse retry_fifo\n    if (moon_visit_str(&mv, -1, \"retry_fifo\", &p->retry_fifo, NULL, true) < 0)\n        goto mverror;\n\n    // Parse enable_tcp_keepalive\n    if (moon_visit_bool(&mv, -1, \"enable_tcp_keepalive\", &p->enable_tcp_keepalive, true) < 0) {\n        goto mverror;\n    }\n\n    // Parse bind\n    if (parse_bind_params(p, &mv, -1) < 0) {\n        goto mverror;\n    }\n\n    // Parse events\n    int has_events = moon_visit_table_f(&mv, -1, \"events\", parse_events_elem, p, true);\n    if (has_events < 0) {\n        goto mverror;\n    }\n    if (!has_events) {\n        ls_string_append_s(&p->idle_str, \" mixer player\");\n    }\n    ls_string_append_b(&p->idle_str, \"\\n\", 2); // append '\\n' and '\\0'\n\n    return LUASTATUS_OK;\n\nmverror:\n    LS_FATALF(pd, \"%s\", errbuf);\nerror:\n    destroy(pd);\n    return LUASTATUS_ERR;\n}\n\nstatic void kv_strarr_table_push(LS_StringArray sa, lua_State *L)\n{\n    size_t n = ls_strarr_size(sa);\n    LS_ASSERT(n % 2 == 0);\n    lua_newtable(L); // L: table\n    for (size_t i = 0; i < n; i += 2) {\n        size_t nkey;\n        const char *key = ls_strarr_at(sa, i, &nkey);\n        lua_pushlstring(L, key, nkey); // L: table key\n        size_t nvalue;\n        const char *value = ls_strarr_at(sa, i + 1, &nvalue);\n        lua_pushlstring(L, value, nvalue); // L: table key value\n        lua_settable(L, -3); // L: table\n    }\n}\n\nstatic void report_status(\n        LuastatusPluginData *pd,\n        LuastatusPluginRunFuncs funcs,\n        const char *what)\n{\n    lua_State *L = funcs.call_begin(pd->userdata);\n    lua_createtable(L, 0, 1); // L: table\n    lua_pushstring(L, what); // L: table what\n    lua_setfield(L, -2, \"what\"); // L: table\n    funcs.call_end(pd->userdata);\n}\n\ntypedef struct {\n    FILE *f;\n    LineReader LR;\n    SAFEV line_v;\n} Context;\n\nstatic inline int read_line(Context *ctx)\n{\n    return line_reader_read_line(&ctx->LR, ctx->f, &ctx->line_v);\n}\n\nstatic void log_io_error(LuastatusPluginData *pd, Context *ctx)\n{\n    if (feof(ctx->f)) {\n        LS_ERRF(pd, \"connection closed\");\n    } else {\n        LS_ERRF(pd, \"I/O error: %s\", ls_tls_strerror(errno));\n    }\n}\n\nstatic void log_bad_response(\n        LuastatusPluginData *pd,\n        Context *ctx,\n        const char *where)\n{\n    LS_ERRF(\n        pd, \"server said (%s): %.*s\",\n        where,\n        SAFEV_FMT_ARG(ctx->line_v, 1024));\n}\n\nstatic int loop_until_ok(\n        LuastatusPluginData *pd,\n        Context *ctx,\n        LS_StringArray *kv)\n{\n    for (;;) {\n        if (read_line(ctx) < 0) {\n            log_io_error(pd, ctx);\n            return -1;\n        }\n        switch (response_type(ctx->line_v)) {\n        case RESP_OK:\n            return 0;\n        case RESP_ACK:\n            log_bad_response(pd, ctx, \"in loop\");\n            return -1;\n        case RESP_OTHER:\n            if (kv) {\n                append_line_to_kv_strarr(kv, ctx->line_v);\n            }\n        }\n    }\n}\n\nstatic void interact(\n        LuastatusPluginData *pd,\n        LuastatusPluginRunFuncs funcs,\n        int fd)\n{\n    Priv *p = pd->priv;\n\n    Context ctx = {\n        .f = NULL,\n        .LR = line_reader_new(1024),\n        .line_v = SAFEV_new_empty(),\n    };\n    LS_StringArray kv_song   = ls_strarr_new();\n    LS_StringArray kv_status = ls_strarr_new();\n    int fd_to_close = fd;\n\n    if (!(ctx.f = fdopen(fd, \"r+\"))) {\n        LS_ERRF(pd, \"can't fdopen connection fd: %s\", ls_tls_strerror(errno));\n        goto done;\n    }\n    fd_to_close = -1;\n\n    // read and check the greeting\n    if (read_line(&ctx) < 0) {\n        log_io_error(pd, &ctx);\n        goto done;\n    }\n\n    if (!is_good_greeting(ctx.line_v)) {\n        log_bad_response(pd, &ctx, \"to greeting\");\n        goto done;\n    }\n\n    // send the password, if specified\n    if (p->password) {\n        fputs(\"password \", ctx.f);\n        write_quoted(ctx.f, SAFEV_new_from_cstr_UNSAFE(p->password));\n        putc('\\n', ctx.f);\n\n        fflush(ctx.f);\n        if (ferror(ctx.f)) {\n            log_io_error(pd, &ctx);\n            goto done;\n        }\n\n        if (read_line(&ctx) < 0) {\n            log_io_error(pd, &ctx);\n            goto done;\n        }\n        if (response_type(ctx.line_v) != RESP_OK) {\n            log_bad_response(pd, &ctx, \"to password\");\n            goto done;\n        }\n    }\n\n    LS_TimeDelta tmo = ls_double_to_TD(p->tmo, LS_TD_FOREVER);\n\n    for (;;) {\n        // write \"currentsong\\n\"\n        fputs(\"currentsong\\n\", ctx.f);\n        fflush(ctx.f);\n        if (ferror(ctx.f)) {\n            log_io_error(pd, &ctx);\n            goto done;\n        }\n\n        // until OK, append data to 'kv_song'\n        if (loop_until_ok(pd, &ctx, &kv_song) < 0)\n            goto done;\n\n        // write \"status\\n\"\n        fputs(\"status\\n\", ctx.f);\n        fflush(ctx.f);\n        if (ferror(ctx.f)) {\n            log_io_error(pd, &ctx);\n            goto done;\n        }\n\n        // until OK, append data to 'kv_status'\n        if (loop_until_ok(pd, &ctx, &kv_status) < 0)\n            goto done;\n\n        // make a call\n        lua_State *L = funcs.call_begin(pd->userdata);\n        lua_createtable(L, 0, 3); // L: table\n\n        lua_pushstring(L, \"update\"); // L: table \"update\"\n        lua_setfield(L, -2, \"what\"); // L: table\n\n        kv_strarr_table_push(kv_song, L); // L: table table\n        lua_setfield(L, -2, \"song\"); // L: table\n\n        kv_strarr_table_push(kv_status, L); // L: table table\n        lua_setfield(L, -2, \"status\"); // L: table\n\n        funcs.call_end(pd->userdata);\n\n        // clear the arrays\n        ls_strarr_clear(&kv_status);\n        ls_strarr_clear(&kv_song);\n\n        // write the idle string (\"idle <...list of events...>\\n\")\n        fputs(p->idle_str.data, ctx.f);\n        fflush(ctx.f);\n        if (ferror(ctx.f)) {\n            log_io_error(pd, &ctx);\n            goto done;\n        }\n\n        // if we need to, report timeouts until we have data on fd\n        if (!ls_TD_is_forever(tmo)) {\n            for (;;) {\n                int nfds = ls_wait_input_on_fd(fd, tmo);\n                if (nfds < 0) {\n                    LS_ERRF(pd, \"ls_wait_input_on_fd: %s\", ls_tls_strerror(errno));\n                    goto done;\n                } else if (nfds == 0) {\n                    report_status(pd, funcs, \"timeout\");\n                } else {\n                    break;\n                }\n            }\n        }\n\n        // wait for an OK\n        if (loop_until_ok(pd, &ctx, NULL) < 0)\n            goto done;\n    }\n\ndone:\n    if (ctx.f) {\n        fclose(ctx.f);\n    }\n    ls_close(fd_to_close);\n    line_reader_destroy(&ctx.LR);\n    ls_strarr_destroy(kv_song);\n    ls_strarr_destroy(kv_status);\n}\n\nstatic void do_enable_tcp_keepalive(LuastatusPluginData *pd, int fd)\n{\n    int value = 1;\n    if (setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, &value, sizeof(value)) < 0) {\n        LS_WARNF(pd, \"setsockopt: SO_KEEPALIVE: %s\", ls_tls_strerror(errno));\n    }\n}\n\nstatic void do_enable_tcp_nodelay(LuastatusPluginData *pd, int fd)\n{\n    int value = 1;\n    if (setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &value, sizeof(value)) < 0) {\n        LS_WARNF(pd, \"setsockopt: TCP_NODELAY: %s\", ls_tls_strerror(errno));\n    }\n}\n\nstatic void run(LuastatusPluginData *pd, LuastatusPluginRunFuncs funcs)\n{\n    Priv *p = pd->priv;\n\n    LS_FifoDevice retry_fifo_dev = ls_fifo_device_new();\n\n    char portstr[8];\n    snprintf(portstr, sizeof(portstr), \"%d\", (int) p->port);\n\n    while (1) {\n        report_status(pd, funcs, \"connecting\");\n\n        int fd;\n        if (p->hostname && p->hostname[0] == '/') {\n            fd = unixdom_open(pd, p->hostname);\n            if (fd < 0) {\n                goto retry;\n            }\n        } else {\n            fd = inetdom_open(pd, p->hostname, portstr, p->bind_addr, p->bind_addr_family);\n            if (fd < 0) {\n                goto retry;\n            }\n            if (p->enable_tcp_keepalive) {\n                do_enable_tcp_keepalive(pd, fd);\n            }\n            do_enable_tcp_nodelay(pd, fd);\n        }\n\n        interact(pd, funcs, fd);\n\nretry:\n        report_status(pd, funcs, \"error\");\n\n        LS_TimeDelta retry_tmo;\n        if (!ls_double_to_TD_checked(p->retry_tmo, &retry_tmo)) {\n            LS_FATALF(pd, \"an error occurred; not retrying as requested\");\n            goto error;\n        }\n\n        if (ls_fifo_device_open(&retry_fifo_dev, p->retry_fifo) < 0) {\n            LS_WARNF(pd, \"ls_fifo_device_open: %s: %s\", p->retry_fifo, ls_tls_strerror(errno));\n        }\n\n        if (ls_fifo_device_wait(&retry_fifo_dev, retry_tmo) < 0) {\n            LS_FATALF(pd, \"ls_fifo_device_wait: %s: %s\", p->retry_fifo, ls_tls_strerror(errno));\n            goto error;\n        }\n    }\n\nerror:\n    ls_fifo_device_close(&retry_fifo_dev);\n}\n\nLuastatusPluginIface luastatus_plugin_iface_v1 = {\n    .init = init,\n    .run = run,\n    .destroy = destroy,\n};\n"
  },
  {
    "path": "plugins/mpd/safe_haven.c",
    "content": "/*\n * Copyright (C) 2015-2025  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#include \"safe_haven.h\"\n#include <stddef.h>\n#include <stdio.h>\n#include <stdbool.h>\n#include \"libls/ls_strarr.h\"\n\n#define LIT(Literal_) SAFEV_new_from_literal(Literal_)\n\nbool is_good_greeting(SAFEV v)\n{\n    return SAFEV_starts_with(v, LIT(\"OK MPD \"));\n}\n\nResponseType response_type(SAFEV v)\n{\n    if (SAFEV_equals(v, LIT(\"OK\"))) {\n        return RESP_OK;\n    }\n    if (SAFEV_starts_with(v, LIT(\"ACK \"))) {\n        return RESP_ACK;\n    }\n    return RESP_OTHER;\n}\n\nstatic inline void do_fwrite_str_view(FILE *f, SAFEV v)\n{\n    size_t n = SAFEV_len(v);\n    if (n) {\n        fwrite(SAFEV_ptr_UNSAFE(v), 1, n, f);\n    }\n}\n\nvoid write_quoted(FILE *f, SAFEV v)\n{\n    fputc('\"', f);\n\n    for (;;) {\n        size_t i = SAFEV_index_of(v, '\"');\n        if (i == (size_t) -1) {\n            break;\n        }\n        SAFEV cur_seg = SAFEV_subspan(v, 0, i);\n        do_fwrite_str_view(f, cur_seg);\n        fputs(\"\\\\\\\"\", f);\n        v = SAFEV_suffix(v, i + 1);\n    }\n\n    do_fwrite_str_view(f, v);\n\n    fputc('\"', f);\n}\n\nstatic inline void append_sv_to_strarr(LS_StringArray *sa, SAFEV v)\n{\n    ls_strarr_append(sa, SAFEV_ptr_UNSAFE(v), SAFEV_len(v));\n}\n\nvoid append_line_to_kv_strarr(LS_StringArray *sa, SAFEV line)\n{\n    size_t i = SAFEV_index_of(line, ':');\n    if (i == (size_t) -1) {\n        return;\n    }\n    if (SAFEV_at_or(line, i + 1, '\\0') != ' ') {\n        return;\n    }\n\n    SAFEV key = SAFEV_subspan(line, 0, i);\n    append_sv_to_strarr(sa, key);\n\n    SAFEV value = SAFEV_suffix(line, i + 2);\n    append_sv_to_strarr(sa, value);\n}\n"
  },
  {
    "path": "plugins/mpd/safe_haven.h",
    "content": "/*\n * Copyright (C) 2015-2025  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifndef safe_haven_h_\n#define safe_haven_h_\n\n#include <stdio.h>\n#include <stdbool.h>\n#include \"libls/ls_strarr.h\"\n#include \"libsafe/safev.h\"\n\ntypedef enum {\n    RESP_OK,\n    RESP_ACK,\n    RESP_OTHER,\n} ResponseType;\n\nbool is_good_greeting(SAFEV v);\n\nResponseType response_type(SAFEV v);\n\nvoid write_quoted(FILE *f, SAFEV v);\n\n// If /line/ is of form \"key: value\", appends /key/ and /value/ to /sa/.\nvoid append_line_to_kv_strarr(LS_StringArray *sa, SAFEV line);\n\n#endif\n"
  },
  {
    "path": "plugins/mpris/CMakeLists.txt",
    "content": "install (FILES mpris.lua DESTINATION ${LUA_PLUGINS_DIR})\n\nluastatus_add_man_page (README.rst luastatus-plugin-mpris 7)\n"
  },
  {
    "path": "plugins/mpris/README.rst",
    "content": ".. :X-man-page-only: luastatus-plugin-mpris\n.. :X-man-page-only: ######################\n.. :X-man-page-only:\n.. :X-man-page-only: #######################################\n.. :X-man-page-only: MPRIS media player plugin for luastatus\n.. :X-man-page-only: #######################################\n.. :X-man-page-only:\n.. :X-man-page-only: :Copyright: LGPLv3\n.. :X-man-page-only: :Manual section: 7\n\nOverview\n========\nThis derived plugin interfaces with MPRIS-compatible media players\nvia D-Bus to monitor playback status and track metadata.\n\nFunctions\n=========\nThe following functions are provided:\n\n* ``widget(tbl)``\n\n  Constructs a ``widget`` table required by luastatus. ``tbl`` is a table with the following fields:\n\n  **(required)**\n\n  - ``player``: string\n\n    The name of the MPRIS player (e.g., ``\"clementine\"``).\n    It will be automatically prefixed with ``org.mpris.MediaPlayer2.`` when communicating over D-Bus.\n    You can list active MPRIS players on your system by running the following command::\n\n        dbus-send \\\n            --session --dest=org.freedesktop.DBus --type=method_call --print-reply \\\n            /org/freedesktop/DBus org.freedesktop.DBus.ListNames \\\n            | grep -F 'org.mpris.MediaPlayer2'\n\n\n  - ``cb``: function\n\n    The callback function that will be called with a table containing the current MPRIS player\n    properties (such as ``Metadata``, ``PlaybackStatus``, ``Volume``, etc.).\n    The table will be empty if the data cannot be retrieved or the player is unavailable.\n\n  **(optional)**\n\n  - ``forced_refresh_interval``: number\n\n    The interval in seconds for manually querying the player properties.\n    Defaults to 30.\n    This acts as a safety fallback if D-Bus signals are missed or invalidated.\n\n  - ``event``\n\n    The ``event`` entry of the resulting table (see ``luastatus`` documentation\n    for the description of ``widget.event`` field).\n"
  },
  {
    "path": "plugins/mpris/mpris.lua",
    "content": "--[[\n  Copyright (C) 2026  luastatus developers\n\n  This file is part of luastatus.\n\n  luastatus is free software: you can redistribute it and/or modify\n  it under the terms of the GNU Lesser General Public License as published by\n  the Free Software Foundation, either version 3 of the License, or\n  (at your option) any later version.\n\n  luastatus is distributed in the hope that it will be useful,\n  but WITHOUT ANY WARRANTY; without even the implied warranty of\n  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n  GNU Lesser General Public License for more details.\n\n  You should have received a copy of the GNU Lesser General Public License\n  along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n--]]\n\nlocal function unwrap1_into_table(x)\n    assert(type(x) == 'table')\n    assert(#x == 1)\n    local res = x[1]\n    assert(type(res) == 'table')\n    return res\nend\n\nlocal function mix_into(dst, x, handle_metadata_key)\n    for _, kv in ipairs(x) do\n        local k, v = kv[1], kv[2]\n        if handle_metadata_key and k == 'Metadata' then\n            dst[k] = {}\n            mix_into(dst[k], v, false)\n        else\n            dst[k] = v\n        end\n    end\nend\n\nlocal function print_warning(what)\n    print(string.format('WARNING: luastatus: mpris plugin: %s', what))\nend\n\nlocal function storage_requery(storage)\n    storage.current_data = {}\n\n    local is_ok, res_raw = luastatus.plugin.get_all_properties({\n        bus = 'session',\n        flag_no_autostart = true,\n        dest = 'org.mpris.MediaPlayer2.' .. storage.player,\n        object_path = '/org/mpris/MediaPlayer2',\n        interface = 'org.mpris.MediaPlayer2.Player',\n    })\n    if not is_ok then\n        print_warning(string.format('cannot get properties: %s', res_raw))\n        return\n    end\n\n    mix_into(storage.current_data, unwrap1_into_table(res_raw), true)\nend\n\nlocal function storage_handle_update(storage, parameters)\n    local changed_props = parameters[2]\n    local invalidated_props = parameters[3]\n\n    if #invalidated_props > 0 then\n        storage_requery(storage)\n        return\n    end\n\n    mix_into(storage.current_data, changed_props, true)\nend\n\nlocal P = {}\n\nfunction P.widget(tbl)\n    assert(tbl.player)\n\n    local storage = {\n        player = tbl.player,\n        current_data = {},\n    }\n\n    return {\n        plugin = 'dbus',\n        opts = {\n            signals = {\n                {\n                    bus = 'session',\n                    sender = 'org.mpris.MediaPlayer2.' .. tbl.player,\n                    interface = 'org.freedesktop.DBus.Properties',\n                    signal = 'PropertiesChanged',\n                    object_path = '/org/mpris/MediaPlayer2',\n                    arg0 = 'org.mpris.MediaPlayer2.Player',\n                },\n            },\n            timeout = tbl.forced_refresh_interval or 30,\n            report_when_ready = true,\n        },\n        cb = function(t)\n            if t.what == 'signal' then\n                storage_handle_update(storage, t.parameters)\n            else\n                storage_requery(storage)\n            end\n\n            return tbl.cb(storage.current_data)\n        end,\n        event = tbl.event,\n    }\nend\n\nreturn P\n"
  },
  {
    "path": "plugins/multiplex/CMakeLists.txt",
    "content": "file (GLOB sources \"*.c\")\nluastatus_add_plugin (\n    plugin-multiplex\n    $<TARGET_OBJECTS:ls>\n    $<TARGET_OBJECTS:moonvisit>\n    $<TARGET_OBJECTS:runshell>\n    $<TARGET_OBJECTS:widechar>\n    $<TARGET_OBJECTS:safe>\n    ${sources}\n)\n\nconfigure_file (\"config.in.h\" \"config.generated.h\")\n\ntarget_compile_definitions (plugin-multiplex PUBLIC -D_POSIX_C_SOURCE=200809L)\nluastatus_target_compile_with (plugin-multiplex LUA)\ntarget_include_directories (plugin-multiplex PUBLIC \"${PROJECT_SOURCE_DIR}\" \"${CMAKE_CURRENT_BINARY_DIR}\")\n\n# find pthreads\nset (CMAKE_THREAD_PREFER_PTHREAD TRUE)\nset (THREADS_PREFER_PTHREAD_FLAG TRUE)\nfind_package (Threads REQUIRED)\n# link against dl and pthread\ntarget_link_libraries (plugin-multiplex PUBLIC ${CMAKE_DL_LIBS} Threads::Threads)\n\nluastatus_add_man_page (README.rst luastatus-plugin-multiplex 7)\n"
  },
  {
    "path": "plugins/multiplex/README.rst",
    "content": ".. :X-man-page-only: luastatus-plugin-timer\n.. :X-man-page-only: ######################\n.. :X-man-page-only:\n.. :X-man-page-only: ##########################\n.. :X-man-page-only: timer plugin for luastatus\n.. :X-man-page-only: ##########################\n.. :X-man-page-only:\n.. :X-man-page-only: :Copyright: LGPLv3\n.. :X-man-page-only: :Manual section: 7\n\nOverview\n========\nThis plugin is a \"multiplexor\": it allows you to spawn multiple \"nested widgets\" and\nreceive information from all of them.\nIt also can invoke the ``event`` functions of nested widgets.\n\nOptions\n=======\nThe following options are supported:\n\n* ``data_sources`` (**required**): table\n\n  This must be a dictionary (a table with string keys) of *data source specifications*,\n\n  Each entry is treated as follows: the key is the identifier of the data source, and the\n  value is the *data source specification*.\n  A *data source specification* is a string with a Lua program which defines ``LL_data_source``\n  global variable. This specification is used to initialize a new **nested widget**.\n\n  Just like with normal luastatus widgets, ``plugin`` and ``cb`` entries are required, ``opts`` entry is optional.\n  These entries have the same meaning as with normal luastatus widgets.\n\n  The ``event`` function of a data source can later be called by the \"main\" multiplexor widget that is being\n  discussed.\n\n  Here is an example of a *data source specification*::\n\n    [[\n        widget = {\n            plugin = 'xtitle',\n            opts = {\n                extended_fmt = true,\n            },\n            cb = function(t)\n                return t.instance or ''\n            end,\n        }\n    ]]\n\n  Currently, there is a limit on the number of data sources: there can be at most 64 different data sources.\n\n  For nested widget's ``cb`` functions, only string and nil return values are supported.\n\n* ``greet``: boolean\n\n  Whether or not to call the callback with ``what=\"hello\"`` before doing anything else.\n  Defaults to false.\n\n``cb`` argument\n===============\nA table with ``what`` field.\n\n* If ``what`` is ``\"hello\"``, then the ``greet`` was enabled and the plugin is making\n  this call before doing anything else. Defaults to false.\n\n* If ``what`` is ``\"update\"``, then some nested widget's plugin has produced a value.\n  In this case, ``updates`` field is also present and is a table that contains all the\n  updates (note that only updates are specified, not the full snapshot).\n  Its keys are identifiers of data sources, and values are as follows:\n\n  + if this nested widget's ``cb`` has generated a string value: a string;\n\n  + if this nested widget's ``cb`` has generated a nil value: ``false``;\n\n  + some error occurred: an integer with error code (see below).\n\nError codes\n-----------\nThe following error codes can be reported (as of now):\n\n* 64: plugin's ``run()`` function for this nested widget has returned (due to a fatal error),\n  there will be no events from it anymore;\n\n* 65: Lua error occurred in this nested widget's ``cb`` function;\n\n* 66: This nested widget's ``cb`` function returned something other than a string or nil.\n\nFunctions\n=========\nThe following functions are provided:\n\n* ``luastatus.plugin.call_event(ident, content)``\n\n  Calls the ``event`` function of the data source with identifier ``ident`` (which must be a string)\n  with string ``content``.\n\n  On success, returns ``true``. On failure, returns ``false, err_msg`` (but see `Notes`_).\n\nNotes\n=====\nIf the nested widgets have not been spawned yet, ``call_event`` throws an error\ninstead of returning ``false, err_msg``.\nThis is only possible if the function is being invoked from an event handler.\nSo, if you use this function from an event handler, you should consider this possibility.\n"
  },
  {
    "path": "plugins/multiplex/config.in.h",
    "content": "#ifndef luastatus_plugin_multiplex_config_h_\n#define luastatus_plugin_multiplex_config_h_\n\n#define LUASTATUS_PLUGINS_DIR       \"@PLUGINS_DIR@\"\n#define LUASTATUS_LUA_PLUGINS_DIR   \"@LUA_PLUGINS_DIR@\"\n\n#endif\n"
  },
  {
    "path": "plugins/multiplex/conq.c",
    "content": "/*\n * Copyright (C) 2026  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#include \"conq.h\"\n\n#include <stdlib.h>\n#include <pthread.h>\n\n#include \"libls/ls_alloc_utils.h\"\n#include \"libls/ls_panic.h\"\n#include \"libls/ls_string.h\"\n\nstruct Conq {\n    pthread_mutex_t mtx;\n    pthread_cond_t condvar;\n\n    LS_String *slots;\n    size_t nslots;\n\n    char slot_states[CONQ_MAX_SLOTS];\n\n    CONQ_MASK new_data_mask;\n};\n\nConq *conq_create(size_t nslots)\n{\n    LS_ASSERT(nslots <= CONQ_MAX_SLOTS);\n\n    Conq *q = LS_XNEW(Conq, 1);\n\n    LS_PTH_CHECK(pthread_mutex_init(&q->mtx, NULL));\n    LS_PTH_CHECK(pthread_cond_init(&q->condvar, NULL));\n\n    q->slots = LS_XNEW(LS_String, nslots);\n    for (size_t i = 0; i < nslots; ++i) {\n        q->slots[i] = ls_string_new_reserve(1024);\n        q->slot_states[i] = CONQ_SLOT_STATE_EMPTY;\n    }\n    q->nslots = nslots;\n\n    q->new_data_mask = 0;\n\n    return q;\n}\n\nvoid conq_update_slot(\n    Conq *q,\n    size_t slot_idx,\n    const char *buf, size_t nbuf,\n    ConqSlotState state)\n{\n    LS_ASSERT(slot_idx < q->nslots);\n\n    LS_PTH_CHECK(pthread_mutex_lock(&q->mtx));\n\n    LS_String *old = &q->slots[slot_idx];\n    ConqSlotState old_state = q->slot_states[slot_idx];\n    if (ls_string_eq_b(*old, buf, nbuf) && old_state == state) {\n        goto unlock;\n    }\n    ls_string_assign_b(old, buf, nbuf);\n    q->slot_states[slot_idx] = state;\n\n    q->new_data_mask |= ((CONQ_MASK) 1) << slot_idx;\n    LS_PTH_CHECK(pthread_cond_signal(&q->condvar));\n\nunlock:\n    LS_PTH_CHECK(pthread_mutex_unlock(&q->mtx));\n}\n\nCONQ_MASK conq_fetch_updates(\n    Conq *q,\n    LS_String *out,\n    ConqSlotState *out_states)\n{\n    LS_PTH_CHECK(pthread_mutex_lock(&q->mtx));\n\n    CONQ_MASK mask;\n    while (!(mask = q->new_data_mask)) {\n        LS_PTH_CHECK(pthread_cond_wait(&q->condvar, &q->mtx));\n    }\n\n    for (size_t i = 0; i < q->nslots; ++i) {\n        if ((mask >> i) & 1) {\n            LS_String slot = q->slots[i];\n            ls_string_assign_b(&out[i], slot.data, slot.size);\n            out_states[i] = q->slot_states[i];\n        }\n    }\n\n    q->new_data_mask = 0;\n\n    LS_PTH_CHECK(pthread_mutex_unlock(&q->mtx));\n\n    return mask;\n}\n\nvoid conq_destroy(Conq *q)\n{\n    LS_PTH_CHECK(pthread_mutex_destroy(&q->mtx));\n    LS_PTH_CHECK(pthread_cond_destroy(&q->condvar));\n\n    for (size_t i = 0; i < q->nslots; ++i) {\n        ls_string_free(q->slots[i]);\n    }\n\n    free(q->slots);\n\n    free(q);\n}\n"
  },
  {
    "path": "plugins/multiplex/conq.h",
    "content": "/*\n * Copyright (C) 2026  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#pragma once\n\n#include <stdint.h>\n#include <stdlib.h>\n#include \"libls/ls_string.h\"\n\n// A concurrent queue.\n//\n// Instead of single value, it maintains a list of /LS_String/s,\n// each of which might get \"updated\" independently.\n//\n// The size of the list is limited to /CONQ_MAX_SLOTS/.\n\ntypedef uint64_t CONQ_MASK;\n\nenum { CONQ_MAX_SLOTS = sizeof(CONQ_MASK) * 8 };\n\n// These must be >= 0 and < 128.\ntypedef enum {\n    CONQ_SLOT_STATE_EMPTY,\n    CONQ_SLOT_STATE_NIL,\n    CONQ_SLOT_STATE_HAS_VALUE,\n\n    CONQ_SLOT_STATE_ERROR_PLUGIN_DONE = 64,\n    CONQ_SLOT_STATE_ERROR_LUA_ERR,\n    CONQ_SLOT_STATE_ERROR_BAD_TYPE,\n} ConqSlotState;\n\nstruct Conq;\ntypedef struct Conq Conq;\n\nConq *conq_create(size_t nslots);\n\nvoid conq_update_slot(\n    Conq *q,\n    size_t slot_idx,\n    const char *buf, size_t nbuf,\n    ConqSlotState state);\n\nCONQ_MASK conq_fetch_updates(\n    Conq *q,\n    LS_String *out,\n    ConqSlotState *out_states);\n\nvoid conq_destroy(Conq *q);\n"
  },
  {
    "path": "plugins/multiplex/external_context.h",
    "content": "/*\n * Copyright (C) 2026  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#pragma once\n\n#include \"include/plugin_data_v1.h\"\n\ntypedef LuastatusPluginData_v1 *ExternalContext;\n"
  },
  {
    "path": "plugins/multiplex/map_ref.c",
    "content": "/*\n * Copyright (C) 2026  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#include \"map_ref.h\"\n#include \"external_context.h\"\n#include \"libls/ls_panic.h\"\n#include \"libls/ls_alloc_utils.h\"\n#include <stdlib.h>\n#include <pthread.h>\n\ntypedef struct {\n    pthread_mutex_t mtx;\n    size_t nrefs;\n} Control;\n\nMapRef map_ref_new_empty(void)\n{\n    return (MapRef) {0};\n}\n\nstatic inline Control *load(MapRef ref)\n{\n    LS_ASSERT(ref.pptr != NULL);\n    return *ref.pptr;\n}\n\nstatic inline void store(MapRef ref, Control *C)\n{\n    LS_ASSERT(ref.pptr != NULL);\n    *ref.pptr = C;\n}\n\nvoid map_ref_init(MapRef *ref, ExternalContext ectx)\n{\n    ref->pptr = ectx->map_get(ectx->userdata, \"plugin-init-mtx\");\n\n    Control *C = load(*ref);\n    if (C) {\n        ++C->nrefs;\n    } else {\n        C = LS_XNEW(Control, 1);\n        LS_PTH_CHECK(pthread_mutex_init(&C->mtx, NULL));\n        C->nrefs = 1;\n        store(*ref, C);\n    }\n}\n\nvoid map_ref_lock_mtx(MapRef ref)\n{\n    Control *C = load(ref);\n    LS_PTH_CHECK(pthread_mutex_lock(&C->mtx));\n}\n\nvoid map_ref_unlock_mtx(MapRef ref)\n{\n    Control *C = load(ref);\n    LS_PTH_CHECK(pthread_mutex_unlock(&C->mtx));\n}\n\nvoid map_ref_destroy(MapRef ref)\n{\n    if (!ref.pptr) {\n        // Wasn't even initialized (with /map_ref_init()/).\n        return;\n    }\n\n    Control *C = load(ref);\n\n    // Since we currently own a reference, /C/ must be non-NULL and have a positive refcount.\n    LS_ASSERT(C != NULL);\n    LS_ASSERT(C->nrefs != 0);\n\n    if (!--C->nrefs) {\n        LS_PTH_CHECK(pthread_mutex_destroy(&C->mtx));\n        free(C);\n        store(ref, NULL);\n    }\n}\n"
  },
  {
    "path": "plugins/multiplex/map_ref.h",
    "content": "/*\n * Copyright (C) 2026  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#pragma once\n\n#include \"external_context.h\"\n\ntypedef struct {\n    void **pptr;\n} MapRef;\n\nMapRef map_ref_new_empty(void);\n\nvoid map_ref_init(MapRef *ref, ExternalContext ectx);\n\nvoid map_ref_lock_mtx(MapRef ref);\n\nvoid map_ref_unlock_mtx(MapRef ref);\n\nvoid map_ref_destroy(MapRef ref);\n"
  },
  {
    "path": "plugins/multiplex/multiplex.c",
    "content": "/*\n * Copyright (C) 2015-2026  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#include <stdlib.h>\n#include <stdbool.h>\n#include <pthread.h>\n\n#include <stdio.h>\n\n#include <lua.h>\n#include <lauxlib.h>\n\n#include \"libls/ls_panic.h\"\n#include \"libls/ls_tls_ebuf.h\"\n#include \"libls/ls_alloc_utils.h\"\n#include \"libls/ls_string.h\"\n\n#include \"include/plugin_v1.h\"\n#include \"include/sayf_macros.h\"\n\n#include \"libmoonvisit/moonvisit.h\"\n\n#include \"wspec_list.h\"\n#include \"runner.h\"\n#include \"conq.h\"\n#include \"priv.h\"\n#include \"runtime_data.h\"\n#include \"map_ref.h\"\n\nstatic void destroy(LuastatusPluginData *pd)\n{\n    Priv *p = pd->priv;\n\n    LS_PTH_CHECK(pthread_mutex_destroy(&p->runners_mtx));\n\n    wspec_list_destroy(&p->wspecs);\n\n    map_ref_destroy(p->map_ref);\n\n    free(p);\n}\n\nstatic int visit_wspec(MoonVisit *mv, void *ud, int kpos, int vpos)\n{\n    Priv *p = ud;\n\n    if (moon_visit_checktype_at(mv, \"key\", kpos, LUA_TSTRING) < 0) {\n        return -1;\n    }\n    if (moon_visit_checktype_at(mv, \"value\", vpos, LUA_TSTRING) < 0) {\n        return -1;\n    }\n\n    const char *name = lua_tostring(mv->L, kpos);\n    const char *code = lua_tostring(mv->L, vpos);\n\n    wspec_list_add(&p->wspecs, name, code);\n\n    return 0;\n}\n\nstatic bool check_wspecs(WspecList *x, char *errbuf, size_t nerrbuf)\n{\n    size_t n = wspec_list_size(x);\n    if (!n) {\n        snprintf(errbuf, nerrbuf, \"data_sources table is empty\");\n        return false;\n    }\n    if (n > CONQ_MAX_SLOTS) {\n        snprintf(\n            errbuf, nerrbuf,\n            \"too many data sources (%zu, limit is %d)\",\n            n, (int) CONQ_MAX_SLOTS\n        );\n        return false;\n    }\n\n    const char *dup = wspec_list_find_duplicates(x);\n    if (dup) {\n        snprintf(errbuf, nerrbuf, \"data_sources table: duplicate key '%s'\", dup);\n        return false;\n    }\n\n    return true;\n}\n\nstatic int init(LuastatusPluginData *pd, lua_State *L)\n{\n    Priv *p = pd->priv = LS_XNEW(Priv, 1);\n    *p = (Priv) {\n        .wspecs = wspec_list_new(),\n        .greet = false,\n        .runners = NULL,\n        .map_ref = map_ref_new_empty(),\n    };\n    LS_PTH_CHECK(pthread_mutex_init(&p->runners_mtx, NULL));\n    map_ref_init(&p->map_ref, pd);\n\n    char errbuf[256];\n    MoonVisit mv = {.L = L, .errbuf = errbuf, .nerrbuf = sizeof(errbuf)};\n\n    // Parse data_sources\n    if (moon_visit_table_f(&mv, -1, \"data_sources\", visit_wspec, p, false) < 0) {\n        goto mverror;\n    }\n    if (!check_wspecs(&p->wspecs, errbuf, sizeof(errbuf))) {\n        goto mverror;\n    }\n\n    // Parse greet\n    if (moon_visit_bool(&mv, -1, \"greet\", &p->greet, true) < 0) {\n        goto mverror;\n    }\n\n    return LUASTATUS_OK;\n\nmverror:\n    LS_FATALF(pd, \"%s\", errbuf);\n//error:\n    destroy(pd);\n    return LUASTATUS_ERR;\n}\n\nstatic inline const char *evt_result_to_str(RunnerEventResult val)\n{\n    switch (val) {\n    case EVT_RESULT_OK:\n        return NULL;\n    case EVT_RESULT_LUA_ERROR:\n        return \"lua-error-in-handler\";\n    case EVT_RESULT_NO_HANDLER:\n        return \"no-handler\";\n    }\n    LS_MUST_BE_UNREACHABLE();\n}\n\nstatic int l_call_event(lua_State *L)\n{\n    Priv *p = lua_touserdata(L, lua_upvalueindex(1));\n\n    const char *data_src_name = luaL_checkstring(L, 1);\n    int data_src_idx = wspec_list_find(&p->wspecs, data_src_name);\n    if (data_src_idx < 0) {\n        return luaL_error(L, \"cannot find data source with name '%s'\", data_src_name);\n    }\n\n    size_t ndata;\n    const char *data = luaL_checklstring(L, 2, &ndata);\n\n    Runner **R;\n\n    LS_PTH_CHECK(pthread_mutex_lock(&p->runners_mtx));\n    if (p->runners) {\n        R = &p->runners[data_src_idx];\n    } else {\n        R = NULL;\n    }\n    LS_PTH_CHECK(pthread_mutex_unlock(&p->runners_mtx));\n\n    if (!R) {\n        return luaL_error(L, \"runners have not been spawned yet\");\n    }\n\n    const char *err;\n    if (*R) {\n        lua_State *borrowed_L = runner_event_begin(*R);\n        lua_pushlstring(borrowed_L, data, ndata);\n        RunnerEventResult res = runner_event_end(*R);\n        err = evt_result_to_str(res);\n    } else {\n        err = \"widget-failed-to-init\";\n    }\n\n    if (err) {\n        lua_pushboolean(L, 0);\n        lua_pushstring(L, err);\n        return 2;\n    } else {\n        lua_pushboolean(L, 1);\n        return 1;\n    }\n}\n\nstatic void register_funcs(LuastatusPluginData *pd, lua_State *L)\n{\n    Priv *p = pd->priv;\n\n    // L: table\n    lua_pushlightuserdata(L, p); // L: table ud\n    lua_pushcclosure(L, l_call_event, 1); // L: table func\n    lua_setfield(L, -2, \"call_event\"); // L: table\n}\n\nstatic void *my_thread_func(void *vud)\n{\n    UniversalUserdata *u_ud = vud;\n    LuastatusPluginData *pd = u_ud->ectx;\n    Priv *p = pd->priv;\n    Runner *R = p->runners[u_ud->i];\n\n    LS_ASSERT(R != NULL);\n\n    runner_run(R);\n\n    return NULL;\n}\n\nstatic void recv_handle_new_data(\n        LuastatusPluginData *pd,\n        Conq *cq,\n        size_t i,\n        lua_State *L)\n{\n    if (lua_isnil(L, -1)) {\n        conq_update_slot(cq, i, NULL, 0, CONQ_SLOT_STATE_NIL);\n\n    } else if (lua_isstring(L, -1)) {\n        size_t ns;\n        const char *s = lua_tolstring(L, -1, &ns);\n        conq_update_slot(cq, i, s, ns, CONQ_SLOT_STATE_HAS_VALUE);\n\n    } else {\n        Priv *p = pd->priv;\n        const char *name = wspec_list_get_name(&p->wspecs, i);\n        LS_WARNF(\n            pd, \"cb of widget [%s] returned %s value (expected string or nil)\",\n            name,\n            luaL_typename(L, -1)\n        );\n        conq_update_slot(cq, i, NULL, 0, CONQ_SLOT_STATE_ERROR_BAD_TYPE);\n    }\n}\n\nstatic void recv_callback(void *vud, RunnerCallbackReason reason, lua_State *L)\n{\n    UniversalUserdata *u_ud = vud;\n    LuastatusPluginData *pd = u_ud->ectx;\n    Priv *p = pd->priv;\n\n    Conq *cq = p->rtdata.cq;\n    size_t i = u_ud->i;\n\n    switch (reason) {\n    case REASON_NEW_DATA:\n        recv_handle_new_data(pd, cq, i, L);\n        return;\n    case REASON_LUA_ERROR:\n        conq_update_slot(cq, i, NULL, 0, CONQ_SLOT_STATE_ERROR_LUA_ERR);\n        return;\n    case REASON_PLUGIN_EXITED:\n        conq_update_slot(cq, i, NULL, 0, CONQ_SLOT_STATE_ERROR_PLUGIN_DONE);\n        return;\n    }\n\n    LS_MUST_BE_UNREACHABLE();\n}\n\nstatic void make_call_simple(\n        LuastatusPluginData *pd,\n        LuastatusPluginRunFuncs funcs,\n        const char *what)\n{\n    lua_State *L = funcs.call_begin(pd->userdata);\n\n    lua_createtable(L, 0, 1); // L: table\n    lua_pushstring(L, what); // L: table string\n    lua_setfield(L, -2, \"what\"); // L: table\n\n    funcs.call_end(pd->userdata);\n}\n\nstatic void push_update(lua_State *L, const LS_String *buf, ConqSlotState state)\n{\n    switch (state) {\n    case CONQ_SLOT_STATE_EMPTY:\n        lua_pushnil(L);\n        return;\n    case CONQ_SLOT_STATE_NIL:\n        lua_pushboolean(L, 0);\n        return;\n    case CONQ_SLOT_STATE_HAS_VALUE:\n        lua_pushlstring(L, buf->data, buf->size);\n        return;\n    case CONQ_SLOT_STATE_ERROR_PLUGIN_DONE:\n    case CONQ_SLOT_STATE_ERROR_LUA_ERR:\n    case CONQ_SLOT_STATE_ERROR_BAD_TYPE:\n        lua_pushinteger(L, (int) state);\n        return;\n    }\n    LS_MUST_BE_UNREACHABLE();\n}\n\nstatic void make_call_update(\n        LuastatusPluginData *pd,\n        LuastatusPluginRunFuncs funcs,\n        size_t n,\n        CONQ_MASK mask,\n        LS_String *bufs,\n        ConqSlotState *states)\n{\n    Priv *p = pd->priv;\n\n    lua_State *L = funcs.call_begin(pd->userdata);\n\n    lua_createtable(L, 0, 2); // L: table\n\n    lua_pushstring(L, \"update\"); // L: table string\n    lua_setfield(L, -2, \"what\"); // L: table\n\n    lua_newtable(L); // L: table table\n    for (size_t i = 0; i < n; ++i) {\n        if ((mask >> i) & 1) {\n            push_update(L, &bufs[i], states[i]); // L: table table value\n            const char *key = wspec_list_get_name(&p->wspecs, i);\n            lua_setfield(L, -2, key); // L: table table\n        }\n    }\n\n    lua_setfield(L, -2, \"updates\"); // L: table\n\n    funcs.call_end(pd->userdata);\n}\n\nstatic void run(LuastatusPluginData *pd, LuastatusPluginRunFuncs funcs)\n{\n    Priv *p = pd->priv;\n\n    size_t n = wspec_list_size(&p->wspecs);\n\n    runtime_data_init(&p->rtdata, pd, n);\n\n    map_ref_lock_mtx(p->map_ref);\n\n    for (size_t i = 0; i < n; ++i) {\n        const char *name = wspec_list_get_name(&p->wspecs, i);\n        const char *code = wspec_list_get_code(&p->wspecs, i);\n\n        Runner *R = runner_new(\n            name,\n            code,\n            pd,\n            recv_callback,\n            &p->rtdata.u_uds[i]\n        );\n        p->rtdata.runners[i] = R;\n\n        wspec_list_get_rid_of_code(&p->wspecs, i);\n    }\n\n    map_ref_unlock_mtx(p->map_ref);\n\n    LS_PTH_CHECK(pthread_mutex_lock(&p->runners_mtx));\n    p->runners = p->rtdata.runners;\n    LS_PTH_CHECK(pthread_mutex_unlock(&p->runners_mtx));\n\n    if (p->greet) {\n        make_call_simple(pd, funcs, \"hello\");\n    }\n\n    for (size_t i = 0; i < n; ++i) {\n        if (!p->rtdata.runners[i]) {\n            continue;\n        }\n        pthread_t ignored_tid;\n        int err_num = pthread_create(\n            &ignored_tid,\n            NULL,\n            my_thread_func,\n            &p->rtdata.u_uds[i]\n        );\n        if (err_num != 0) {\n            LS_ERRF(pd, \"cannot create thread: %s\", ls_tls_strerror(err_num));\n        }\n    }\n\n    Conq *cq = p->rtdata.cq;\n    LS_String *bufs = p->rtdata.bufs;\n    ConqSlotState *states = p->rtdata.states;\n    for (;;) {\n        CONQ_MASK mask = conq_fetch_updates(cq, bufs, states);\n        make_call_update(pd, funcs, n, mask, bufs, states);\n    }\n}\n\nLuastatusPluginIface luastatus_plugin_iface_v1 = {\n    .init = init,\n    .register_funcs = register_funcs,\n    .run = run,\n    .destroy = destroy,\n};\n"
  },
  {
    "path": "plugins/multiplex/priv.h",
    "content": "/*\n * Copyright (C) 2026  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#pragma once\n\n#include <stdbool.h>\n#include <pthread.h>\n#include \"wspec_list.h\"\n#include \"runtime_data.h\"\n#include \"runner.h\"\n#include \"map_ref.h\"\n\ntypedef struct {\n    WspecList wspecs;\n    bool greet;\n\n    pthread_mutex_t runners_mtx;\n    Runner **runners;\n\n    RuntimeData rtdata;\n\n    MapRef map_ref;\n} Priv;\n"
  },
  {
    "path": "plugins/multiplex/runner.c",
    "content": "/*\n * Copyright (C) 2026  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#include \"runner.h\"\n#include <lua.h>\n#include <lauxlib.h>\n#include <lualib.h>\n#include <stdlib.h>\n#include <stdbool.h>\n#include <stdio.h>\n#include <stdarg.h>\n#include <string.h>\n#include <pthread.h>\n#include <unistd.h>\n#include <dlfcn.h>\n\n#include \"include/plugin_data_v1.h\"\n\n#include \"libls/ls_panic.h\"\n#include \"libls/ls_xallocf.h\"\n#include \"libls/ls_alloc_utils.h\"\n#include \"libls/ls_getenv_r.h\"\n#include \"libls/ls_lua_compat.h\"\n\n#include \"librunshell/runshell.h\"\n#include \"libwidechar/libwidechar.h\"\n\n#include \"external_context.h\"\n\n#include \"config.generated.h\"\n\n// Logging macros.\n#define FATALF(Runner_, ...)    my_sayf(Runner_, LUASTATUS_LOG_FATAL,    __VA_ARGS__)\n#define ERRF(Runner_, ...)      my_sayf(Runner_, LUASTATUS_LOG_ERR,      __VA_ARGS__)\n#define WARNF(Runner_, ...)     my_sayf(Runner_, LUASTATUS_LOG_WARN,     __VA_ARGS__)\n#define INFOF(Runner_, ...)     my_sayf(Runner_, LUASTATUS_LOG_INFO,     __VA_ARGS__)\n#define VERBOSEF(Runner_, ...)  my_sayf(Runner_, LUASTATUS_LOG_VERBOSE,  __VA_ARGS__)\n#define DEBUGF(Runner_, ...)    my_sayf(Runner_, LUASTATUS_LOG_DEBUG,    __VA_ARGS__)\n#define TRACEF(Runner_, ...)    my_sayf(Runner_, LUASTATUS_LOG_TRACE,    __VA_ARGS__)\n\n#define LOCK_L(W_)   LS_PTH_CHECK(pthread_mutex_lock(&(W_)->L_mtx))\n#define UNLOCK_L(W_) LS_PTH_CHECK(pthread_mutex_unlock(&(W_)->L_mtx))\n\nenum { SAYF_BUF_SIZE = 1024 };\n\ntypedef struct {\n    // The interface loaded from this plugin's .so file.\n    LuastatusPluginIface_v1 iface;\n\n    // An allocated zero-terminated string with plugin name, as specified in widget's\n    // /widget.plugin/ string.\n    char *name;\n\n    // A handle returned from /dlopen/ for this plugin's .so file.\n    void *dlhandle;\n} Plugin;\n\ntypedef struct {\n    // An initialized plugin.\n    Plugin plugin;\n\n    // /plugin/'s data for this widget.\n    LuastatusPluginData_v1 data;\n\n    // This widget's Lua interpreter instance.\n    lua_State *L;\n\n    // A mutex guarding /L/.\n    pthread_mutex_t L_mtx;\n\n    // Lua reference (in /L/'s registry) to this widget's /widget.cb/ function.\n    int lref_cb;\n\n    // Lua reference (in /L/'s registry) to this widget's /widget.event/ function (is\n    // /LUA_REFNIL/ if the latter is /nil/).\n    int lref_event;\n\n    // An allocated widget's name.\n    char *name;\n} Widget;\n\nstruct Runner {\n    ExternalContext ectx;\n    Widget widget;\n    RunnerCallback callback;\n    void *callback_ud;\n};\n\n// This function exists because /dlerror()/ may return /NULL/ even if /dlsym()/ returned /NULL/.\nstatic inline const char *safe_dlerror(void)\n{\n    const char *err = dlerror();\n    return err ? err : \"(no error, but the symbol is NULL)\";\n}\n\nstatic inline void safe_vsnprintf(char *buf, size_t nbuf, const char *fmt, va_list vl)\n{\n    if (vsnprintf(buf, nbuf, fmt, vl) < 0) {\n        buf[0] = '\\0';\n    }\n}\n\nstatic void my_sayf(Runner *runner, int level, const char *fmt, ...)\n{\n    char buf[SAYF_BUF_SIZE];\n    va_list vl;\n    va_start(vl, fmt);\n    safe_vsnprintf(buf, sizeof(buf), fmt, vl);\n    va_end(vl);\n\n    runner->ectx->sayf(\n        runner->ectx->userdata,\n        level,\n        \"%s\", buf\n    );\n}\n\nstatic void external_sayf(void *userdata, int level, const char *fmt, ...)\n{\n    Runner *runner = userdata;\n\n    char buf[SAYF_BUF_SIZE];\n    va_list vl;\n    va_start(vl, fmt);\n    safe_vsnprintf(buf, sizeof(buf), fmt, vl);\n    va_end(vl);\n\n    runner->ectx->sayf(\n        runner->ectx->userdata,\n        level,\n        \"[%s] %s\", runner->widget.name, buf\n    );\n}\n\n// Returns a pointer to the value of the entry with key /key/; or creates a new entry with the given\n// key and /NULL/ value, and returns a pointer to that value.\nstatic void **map_get(void *userdata, const char *key)\n{\n    LS_ASSERT(key != NULL);\n\n    Runner *runner = userdata;\n\n    TRACEF(runner, \"map_get(userdata=%p, key='%s')\", userdata, key);\n\n    return runner->ectx->map_get(runner->ectx->userdata, key);\n}\n\nstatic bool plugin_load(Runner *runner, Plugin *p, const char *filename, const char *name)\n{\n    p->dlhandle = NULL;\n    p->name = ls_xstrdup(name);\n\n    DEBUGF(runner, \"loading plugin from file '%s'\", filename);\n\n    (void) dlerror(); // clear last error\n    if (!(p->dlhandle = dlopen(filename, RTLD_NOW | RTLD_LOCAL))) {\n        ERRF(runner, \"dlopen: %s: %s\", filename, safe_dlerror());\n        goto error;\n    }\n    int *p_lua_ver = dlsym(p->dlhandle, \"LUASTATUS_PLUGIN_LUA_VERSION_NUM\");\n    if (!p_lua_ver) {\n        ERRF(runner, \"dlsym: LUASTATUS_PLUGIN_LUA_VERSION_NUM: %s\", safe_dlerror());\n        goto error;\n    }\n    if (*p_lua_ver != LUA_VERSION_NUM) {\n        ERRF(runner, \"plugin '%s' was compiled with LUA_VERSION_NUM=%d and luastatus with %d\",\n             filename, *p_lua_ver, LUA_VERSION_NUM);\n        goto error;\n    }\n    LuastatusPluginIface_v1 *p_iface = dlsym(p->dlhandle, \"luastatus_plugin_iface_v1\");\n    if (!p_iface) {\n        ERRF(runner, \"dlsym: luastatus_plugin_iface_v1: %s\", safe_dlerror());\n        goto error;\n    }\n    p->iface = *p_iface;\n    DEBUGF(runner, \"plugin successfully loaded\");\n    return true;\n\nerror:\n    if (p->dlhandle) {\n        dlclose(p->dlhandle);\n    }\n    free(p->name);\n    return false;\n}\n\nstatic bool plugin_load_by_name(Runner *runner, Plugin *p, const char *name)\n{\n    if ((strchr(name, '/'))) {\n        return plugin_load(runner, p, name, name);\n    } else {\n        char *filename = ls_xallocf(\"%s/plugin-%s.so\", LUASTATUS_PLUGINS_DIR, name);\n        bool r = plugin_load(runner, p, filename, name);\n        free(filename);\n        return r;\n    }\n}\n\nstatic void plugin_unload(Plugin *p)\n{\n    free(p->name);\n    dlclose(p->dlhandle);\n}\n\nstatic lua_State *xnew_lua_state(void)\n{\n    lua_State *L = luaL_newstate();\n    if (!L) {\n        LS_PANIC(\"luaL_newstate() failed: out of memory?\");\n    }\n    return L;\n}\n\n// Returns a string representation of an error object located at the position /pos/ of /L/'s stack.\nstatic inline const char *get_lua_error_msg(lua_State *L, int pos)\n{\n    const char *msg = lua_tostring(L, pos);\n    return msg ? msg : \"(error object cannot be converted to string)\";\n}\n\n// Checks a /lua_*/ call that returns a /LUA_*/ error code, performed on a Lua interpreter instance\n// /L/. /ret/ is the return value of the call.\n//\n// If /ret/ is /0/, returns /true/; otherwise, logs the error and returns /false/.\nstatic bool check_lua_call(Runner *runner, lua_State *L, int ret)\n{\n    const char *prefix;\n    switch (ret) {\n    case 0:\n        return true;\n    case LUA_ERRRUN:\n    case LUA_ERRSYNTAX:\n    case LUA_ERRMEM:  // Lua itself produces a meaningful error message in this case\n    case LUA_ERRFILE: // ditto\n        prefix = \"(lua) \";\n        break;\n    case LUA_ERRERR:\n        prefix = \"(lua) error while running error handler: \";\n        break;\n#ifdef LUA_ERRGCMM\n    // Introduced in Lua 5.2 and removed in in Lua 5.4.\n    case LUA_ERRGCMM:\n        prefix = \"(lua) error while running __gc metamethod: \";\n        break;\n#endif\n    default:\n        prefix = \"unknown Lua error code (please report!), message is: \";\n    }\n    // L: ? error\n    ERRF(runner, \"%s%s\", prefix, get_lua_error_msg(L, -1));\n    lua_pop(L, 1);\n    // L: ?\n    return false;\n}\n\n// The Lua error handler that gets called whenever an error occurs inside a chunk called with\n// /do_lua_call/.\n//\n// Currently, it returns (to /check_lua_call/) the traceback of the error.\nstatic int l_error_handler(lua_State *L)\n{\n    // L: error\n    lua_getglobal(L, LUA_DBLIBNAME); // L: error debug\n    lua_getfield(L, -1, \"traceback\"); // L: error debug traceback\n    lua_pushstring(L, get_lua_error_msg(L, 1)); // L: error debug traceback msg\n    lua_pushinteger(L, 2); // L: error debug traceback msg level\n    lua_call(L, 2, 1); // L: error debug result\n    return 1;\n}\n\n// Similar to /lua_call/, but expects an error handler to be at the bottom of /L/'s stack, runs the\n// chunk with that error handler, and logs the error message, if any.\n//\n// Returns /true/ on success, /false/ on failure.\nstatic inline bool do_lua_call(Runner *runner, lua_State *L, int nargs, int nresults)\n{\n    return check_lua_call(runner, L, lua_pcall(L, nargs, nresults, 1));\n}\n\n// Replacement for Lua's /os.exit()/: a simple /exit()/ used by Lua is not thread-safe in Linux.\nstatic int l_os_exit(lua_State *L)\n{\n    int code = luaL_optinteger(L, 1, /*default value*/ EXIT_SUCCESS);\n    fflush(stdout);\n    fflush(stderr);\n    _exit(code);\n}\n\n// Replacement for Lua's /os.getenv()/: a simple /getenv()/ used by Lua is not guaranteed by POSIX\n// to be thread-safe.\nstatic int l_os_getenv(lua_State *L)\n{\n    const char *r = ls_getenv_r(luaL_checkstring(L, 1));\n    if (r) {\n        lua_pushstring(L, r);\n    } else {\n        ls_lua_pushfail(L);\n    }\n    return 1;\n}\n\n// Replacement for Lua's /os.setlocale()/: this thing is inherently thread-unsafe.\nstatic int l_os_setlocale(lua_State *L)\n{\n    ls_lua_pushfail(L);\n    return 1;\n}\n\n// Implementation of /luastatus.require_plugin()/. Expects a single upvalue: an initially empty\n// table that will be used as a registry of loaded Lua plugins.\nstatic int l_require_plugin(lua_State *L)\n{\n    const char *arg = luaL_checkstring(L, 1);\n    if ((strchr(arg, '/'))) {\n        return luaL_argerror(L, 1, \"plugin name contains a slash\");\n    }\n    lua_pushvalue(L, lua_upvalueindex(1)); // L: ? table\n    lua_getfield(L, -1, arg); // L: ? table value\n    if (!lua_isnil(L, -1)) {\n        return 1;\n    }\n    lua_pop(L, 1); // L: ? table\n\n    char *filename = ls_xallocf(\"%s/%s.lua\", LUASTATUS_LUA_PLUGINS_DIR, arg);\n    int r = luaL_loadfile(L, filename);\n    free(filename);\n    if (r != 0) {\n        return lua_error(L);\n    }\n\n    // L: ? table chunk\n    lua_call(L, 0, 1); // L: ? table result\n    lua_pushvalue(L, -1); // L: ? table result result\n    lua_setfield(L, -3, arg); // L: ? table result\n    return 1;\n}\n\nstatic void inject_libs_replacements(lua_State *L)\n{\n    lua_getglobal(L, \"os\"); // L: ? os\n\n    lua_pushcfunction(L, l_os_exit); // L: ? os l_os_exit\n    lua_setfield(L, -2, \"exit\"); // L: ? os\n\n    lua_pushcfunction(L, l_os_getenv); // L: ? os l_os_getenv\n    lua_setfield(L, -2, \"getenv\"); // L: ? os\n\n    lua_pushcfunction(L, l_os_setlocale); // L: ? os l_os_setlocale\n    lua_setfield(L, -2, \"setlocale\"); // L: ? os\n\n    bool is_lua51 = ls_lua_is_lua51(L);\n    lua_pushcfunction(\n        L,\n        is_lua51 ? runshell_l_os_execute_lua51ver : runshell_l_os_execute);\n    // L: ? os os_execute_func\n    lua_setfield(L, -2, \"execute\"); // L: ? os\n\n    lua_pop(L, 1); // L: ?\n}\n\nstatic void inject_luastatus_module(lua_State *L)\n{\n    lua_createtable(L, 0, 4); // L: ? table\n\n    // ========== require_plugin ==========\n    lua_newtable(L); // L: ? table table\n    lua_pushcclosure(L, l_require_plugin, 1); // L: ? table l_require_plugin\n    lua_setfield(L, -2, \"require_plugin\"); // L: ? table\n\n    // ========== execute ==========\n    lua_pushcfunction(L, runshell_l_os_execute); // L: ? table cfunction\n    lua_setfield(L, -2, \"execute\"); // L: ? table\n\n    // ========== libwidechar ==========\n    lua_newtable(L); // L: ? table table\n    libwidechar_register_lua_funcs(L); // L: ? table table\n    lua_setfield(L, -2, \"libwidechar\"); // L: ? table\n\n    lua_setglobal(L, \"luastatus\"); // L: ?\n}\n\n// 1. Replaces some of the functions in the standard library with our thread-safe counterparts.\n// 2. Registers the /luastatus/ module (just creates a global table actually) except for the\n//    /luastatus.plugin/ and /luastatus.barlib/ submodules (created later).\nstatic void inject_libs(lua_State *L)\n{\n    inject_libs_replacements(L);\n    inject_luastatus_module(L);\n}\n\n// Inspects the 'plugin' field of /w/'s /widget/ table; the /widget/ table is assumed to be on top\n// of /w.L/'s stack. The stack itself is not changed by this function.\nstatic bool widget_init_inspect_plugin(Runner *runner)\n{\n    Widget *w = &runner->widget;\n    lua_State *L = w->L;\n    // L: ? widget\n    lua_getfield(L, -1, \"plugin\"); // L: ? widget plugin\n    if (!lua_isstring(L, -1)) {\n        ERRF(runner, \"'widget.plugin': expected string, found %s\", luaL_typename(L, -1));\n        return false;\n    }\n    if (!plugin_load_by_name(runner, &w->plugin, lua_tostring(L, -1))) {\n        ERRF(runner, \"cannot load plugin '%s'\", lua_tostring(L, -1));\n        return false;\n    }\n    lua_pop(L, 1); // L: ? widget\n    return true;\n}\n\n// Inspects the 'cb' field of /w/'s /widget/ table; the /widget/ table is assumed to be on top\n// of /w.L/'s stack. The stack itself is not changed by this function.\nstatic bool widget_init_inspect_cb(Runner *runner)\n{\n    Widget *w = &runner->widget;\n    lua_State *L = w->L;\n    // L: ? widget\n    lua_getfield(L, -1, \"cb\"); // L: ? widget plugin\n    if (!lua_isfunction(L, -1)) {\n        ERRF(runner, \"'widget.cb': expected function, found %s\", luaL_typename(L, -1));\n        return false;\n    }\n    w->lref_cb = luaL_ref(L, LUA_REGISTRYINDEX); // L: ? widget\n    return true;\n}\n\n// Inspects the 'event' field of /w/'s /widget/ table; the /widget/ table is assumed to be on top\n// of /w.L/'s stack. The stack itself is not changed by this function.\nstatic bool widget_init_inspect_event(Runner *runner)\n{\n    Widget *w = &runner->widget;\n    lua_State *L = w->L;\n    // L: ? widget\n    lua_getfield(L, -1, \"event\"); // L: ? widget event\n    switch (lua_type(L, -1)) {\n    case LUA_TNIL:\n    case LUA_TFUNCTION:\n        w->lref_event = luaL_ref(L, LUA_REGISTRYINDEX); // L: ? widget\n        return true;\n    default:\n        ERRF(runner, \"'widget.event': expected function or nil, found %s\", luaL_typename(L, -1));\n        return false;\n    }\n}\n\n// Inspects the 'opts' field of /w/'s /widget/ table; the /widget/ table is assumed to be on top\n// of /w.L/'s stack.\n//\n// Pushes either the value of 'opts', or a new empty table if it is absent, onto the stack.\nstatic bool widget_init_inspect_push_opts(Runner *runner)\n{\n    Widget *w = &runner->widget;\n    lua_State *L = w->L;\n    lua_getfield(L, -1, \"opts\"); // L: ? widget opts\n    switch (lua_type(L, -1)) {\n    case LUA_TTABLE:\n        return true;\n    case LUA_TNIL:\n        lua_pop(L, 1); // L: ? widget\n        lua_newtable(L); // L: ? widget table\n        return true;\n    default:\n        ERRF(runner, \"'widget.opts': expected table or nil, found %s\", luaL_typename(L, -1));\n        return false;\n    }\n}\n\nstatic bool widget_init(Runner *runner, const char *name, const char *code)\n{\n    Widget *w = &runner->widget;\n    w->L = xnew_lua_state();\n    LS_PTH_CHECK(pthread_mutex_init(&w->L_mtx, NULL));\n    w->name = ls_xstrdup(name);\n    bool plugin_loaded = false;\n\n    DEBUGF(runner, \"initializing widget [%s]\", name);\n\n    luaL_openlibs(w->L);\n    // w->L: -\n    inject_libs(w->L); // w->L: -\n    lua_pushcfunction(w->L, l_error_handler); // w->L: l_error_handler\n\n    DEBUGF(runner, \"running widget [%s]\", name);\n\n    char *chunk_name = ls_xallocf(\"data source [%s]\", name);\n    bool loaded_ok = check_lua_call(\n        runner,\n        w->L,\n        luaL_loadbuffer(w->L, code, strlen(code), chunk_name));\n    free(chunk_name);\n    if (!loaded_ok) {\n        goto error;\n    }\n    // w->L: l_error_handler chunk\n    if (!do_lua_call(runner, w->L, 0, 0)) {\n        goto error;\n    }\n    // w->L: l_error_handler\n\n    lua_getglobal(w->L, \"widget\"); // w->L: l_error_handler widget\n    if (!lua_istable(w->L, -1)) {\n        ERRF(runner, \"'widget': expected table, found %s\", luaL_typename(w->L, -1));\n        goto error;\n    }\n\n    if (!widget_init_inspect_plugin(runner)) {\n        goto error;\n    }\n    plugin_loaded = true;\n    if (!widget_init_inspect_cb(runner) ||\n        !widget_init_inspect_event(runner) ||\n        !widget_init_inspect_push_opts(runner))\n    {\n        goto error;\n    }\n    // w->L: l_error_handler widget opts\n\n    w->data = (LuastatusPluginData_v1) {\n        .userdata = runner,\n        .sayf = external_sayf,\n        .map_get = map_get,\n    };\n\n    if (w->plugin.iface.init(&w->data, w->L) == LUASTATUS_ERR) {\n        ERRF(runner, \"plugin's init() failed\");\n        goto error;\n    }\n    LS_ASSERT(lua_gettop(w->L) == 3); // w->L: l_error_handler widget opts\n    lua_pop(w->L, 2); // w->L: l_error_handler\n\n    DEBUGF(runner, \"widget successfully initialized\");\n    return true;\n\nerror:\n    lua_close(w->L);\n    LS_PTH_CHECK(pthread_mutex_destroy(&w->L_mtx));\n    free(w->name);\n    if (plugin_loaded) {\n        plugin_unload(&w->plugin);\n    }\n    return false;\n}\n\nstatic void widget_destroy(Widget *w)\n{\n    w->plugin.iface.destroy(&w->data);\n    plugin_unload(&w->plugin);\n    lua_close(w->L);\n    LS_PTH_CHECK(pthread_mutex_destroy(&w->L_mtx));\n    free(w->name);\n}\n\nstatic void register_funcs(Runner *runner)\n{\n    Widget *w = &runner->widget;\n    lua_State *L = w->L;\n\n    // L: ?\n    lua_getglobal(L, \"luastatus\"); // L: ? luastatus\n\n    if (!lua_istable(L, -1)) {\n        WARNF(\n            runner,\n            \"widget [%s]: 'luastatus' is not a table anymore, will not register \"\n            \"barlib/plugin functions\",\n            w->name);\n        goto done;\n    }\n    if (w->plugin.iface.register_funcs) {\n        lua_newtable(L); // L: ? luastatus table\n\n        int old_top = lua_gettop(L);\n        (void) old_top;\n        w->plugin.iface.register_funcs(&w->data, L); // L: ? luastatus table\n        LS_ASSERT(lua_gettop(L) == old_top);\n\n        lua_setfield(L, -2, \"plugin\"); // L: ? luastatus\n    }\n\ndone:\n    lua_pop(L, 1); // L: ?\n}\n\nstatic lua_State *plugin_call_begin(void *userdata)\n{\n    Runner *runner = userdata;\n    TRACEF(runner, \"plugin_call_begin(userdata=%p)\", userdata);\n\n    Widget *w = &runner->widget;\n    LOCK_L(w);\n\n    lua_State *L = w->L;\n    LS_ASSERT(lua_gettop(L) == 1); // w->L: l_error_handler\n    lua_rawgeti(L, LUA_REGISTRYINDEX, w->lref_cb); // w->L: l_error_handler cb\n    return L;\n}\n\nstatic void plugin_call_end(void *userdata)\n{\n    Runner *runner = userdata;\n    TRACEF(runner, \"plugin_call_end(userdata=%p)\", userdata);\n\n    Widget *w = &runner->widget;\n    lua_State *L = w->L;\n    LS_ASSERT(lua_gettop(L) == 3); // L: l_error_handler cb data\n    bool r = do_lua_call(runner, L, 1, 1);\n\n    if (r) {\n        runner->callback(runner->callback_ud, REASON_NEW_DATA, L);\n    } else {\n        runner->callback(runner->callback_ud, REASON_LUA_ERROR, NULL);\n    }\n\n    lua_settop(L, 1); // L: l_error_handler\n\n    UNLOCK_L(w);\n}\n\nstatic void plugin_call_cancel(void *userdata)\n{\n    Runner *runner = userdata;\n    TRACEF(runner, \"plugin_call_cancel(userdata=%p)\", userdata);\n\n    Widget *w = &runner->widget;\n    lua_settop(w->L, 1); // w->L: l_error_handler\n    UNLOCK_L(w);\n}\n\nlua_State *runner_event_begin(Runner *runner)\n{\n    Widget *w = &runner->widget;\n\n    LOCK_L(w);\n\n    lua_State *L = w->L;\n    LS_ASSERT(lua_gettop(L) == 1); // L: l_error_handler\n    lua_rawgeti(L, LUA_REGISTRYINDEX, w->lref_event); // L: l_error_handler event\n\n    return L;\n}\n\nRunnerEventResult runner_event_end(Runner *runner)\n{\n    RunnerEventResult ret;\n\n    Widget *w = &runner->widget;\n\n    lua_State *L = w->L;\n    LS_ASSERT(lua_gettop(L) == 3); // L: l_error_handler event arg\n\n    if (w->lref_event == LUA_REFNIL) {\n        lua_pop(L, 2); // L: l_error_handler\n        ret = EVT_RESULT_NO_HANDLER;\n    } else {\n        bool is_ok = do_lua_call(runner, L, 1, 0); // L: l_error_handler\n        ret = is_ok ? EVT_RESULT_OK : EVT_RESULT_LUA_ERROR;\n    }\n\n    UNLOCK_L(w);\n\n    return ret;\n}\n\nvoid runner_run(Runner *runner)\n{\n    Widget *w = &runner->widget;\n\n    DEBUGF(runner, \"widget [%s] is now running\", w->name);\n\n    w->plugin.iface.run(&w->data, (LuastatusPluginRunFuncs_v1) {\n        .call_begin  = plugin_call_begin,\n        .call_end    = plugin_call_end,\n        .call_cancel = plugin_call_cancel,\n    });\n    WARNF(runner, \"plugin's run() for widget [%s] has returned\", w->name);\n\n    runner->callback(runner->callback_ud, REASON_PLUGIN_EXITED, NULL);\n}\n\nRunner *runner_new(\n    const char *name,\n    const char *code,\n    ExternalContext ectx,\n    RunnerCallback callback,\n    void *callback_ud)\n{\n    Runner *runner = LS_XNEW(Runner, 1);\n    *runner = (Runner) {\n        .ectx = ectx,\n        .callback = callback,\n        .callback_ud = callback_ud,\n    };\n    if (!widget_init(runner, name, code)) {\n        free(runner);\n        return NULL;\n    }\n    register_funcs(runner);\n    return runner;\n}\n\nvoid runner_destroy(Runner *runner)\n{\n    widget_destroy(&runner->widget);\n    free(runner);\n}\n"
  },
  {
    "path": "plugins/multiplex/runner.h",
    "content": "/*\n * Copyright (C) 2026  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#pragma once\n\n#include <lua.h>\n#include \"external_context.h\"\n\ntypedef enum {\n    REASON_NEW_DATA,\n    REASON_LUA_ERROR,\n    REASON_PLUGIN_EXITED,\n} RunnerCallbackReason;\n\ntypedef void (*RunnerCallback)(\n    void *ud,\n    RunnerCallbackReason reason,\n    lua_State *L);\n\ntypedef enum {\n    EVT_RESULT_OK,\n    EVT_RESULT_LUA_ERROR,\n    EVT_RESULT_NO_HANDLER,\n} RunnerEventResult;\n\nstruct Runner;\ntypedef struct Runner Runner;\n\nRunner *runner_new(\n    const char *name,\n    const char *code,\n    ExternalContext ectx,\n    RunnerCallback callback,\n    void *callback_ud);\n\nvoid runner_run(Runner *R);\n\nlua_State *runner_event_begin(Runner *R);\n\nRunnerEventResult runner_event_end(Runner *R);\n\nvoid runner_destroy(Runner *R);\n"
  },
  {
    "path": "plugins/multiplex/runtime_data.c",
    "content": "/*\n * Copyright (C) 2026  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#include \"runtime_data.h\"\n#include \"conq.h\"\n#include \"external_context.h\"\n#include \"runner.h\"\n#include \"libls/ls_panic.h\"\n#include \"libls/ls_alloc_utils.h\"\n\nvoid runtime_data_init(RuntimeData *x, ExternalContext ectx, size_t n)\n{\n    LS_ASSERT(n != 0);\n\n    x->runners = LS_XNEW(Runner *, n);\n    for (size_t i = 0; i < n; ++i) {\n        x->runners[i] = NULL;\n    }\n\n    x->cq = conq_create(n);\n\n    x->bufs = LS_XNEW(LS_String, n);\n    for (size_t i = 0; i < n; ++i) {\n        x->bufs[i] = ls_string_new_reserve(512);\n    }\n\n    x->states = LS_XNEW(ConqSlotState, n);\n    for (size_t i = 0; i < n; ++i) {\n        x->states[i] = CONQ_SLOT_STATE_EMPTY;\n    }\n\n    x->u_uds = LS_XNEW(UniversalUserdata, n);\n    for (size_t i = 0; i < n; ++i) {\n        x->u_uds[i] = (UniversalUserdata) {\n            .ectx = ectx,\n            .i = i,\n        };\n    }\n\n    x->ectx = ectx;\n}\n"
  },
  {
    "path": "plugins/multiplex/runtime_data.h",
    "content": "/*\n * Copyright (C) 2026  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#pragma once\n\n#include <stddef.h>\n#include \"runner.h\"\n#include \"conq.h\"\n#include \"external_context.h\"\n#include \"libls/ls_string.h\"\n\ntypedef struct {\n    ExternalContext ectx;\n    size_t i;\n} UniversalUserdata;\n\ntypedef struct {\n    Runner **runners;\n    Conq *cq;\n    LS_String *bufs;\n    ConqSlotState *states;\n    UniversalUserdata *u_uds;\n    ExternalContext ectx;\n} RuntimeData;\n\nvoid runtime_data_init(RuntimeData *x, ExternalContext ectx, size_t n);\n"
  },
  {
    "path": "plugins/multiplex/wspec_list.c",
    "content": "/*\n * Copyright (C) 2026  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#include \"wspec_list.h\"\n#include \"libls/ls_alloc_utils.h\"\n#include \"libls/ls_panic.h\"\n#include <stdlib.h>\n#include <string.h>\n\nWspecList wspec_list_new(void)\n{\n    return (WspecList) {0};\n}\n\nvoid wspec_list_add(WspecList *x, const char *name, const char *code)\n{\n    LS_ASSERT(name != NULL);\n    LS_ASSERT(code != NULL);\n\n    Wspec new_elem = (Wspec) {\n        .name = ls_xstrdup(name),\n        .code = ls_xstrdup(code),\n    };\n    if (x->size == x->capacity) {\n        x->data = LS_M_X2REALLOC(x->data, &x->capacity);\n    }\n    x->data[x->size++] = new_elem;\n}\n\nconst char *wspec_list_find_duplicates(WspecList *x)\n{\n    size_t n = x->size;\n    for (size_t i = 0; i < n; ++i) {\n        for (size_t j = 0; j < i; ++j) {\n            const char *s1 = x->data[i].name;\n            const char *s2 = x->data[j].name;\n            if (strcmp(s1, s2) == 0) {\n                return s1;\n            }\n        }\n    }\n    return NULL;\n}\n\nint wspec_list_find(WspecList *x, const char *name)\n{\n    LS_ASSERT(name != NULL);\n\n    size_t n = x->size;\n    for (size_t i = 0; i < n; ++i) {\n        if (strcmp(x->data[i].name, name) == 0) {\n            return i;\n        }\n    }\n    return -1;\n}\n\nvoid wspec_list_get_rid_of_code(WspecList *x, size_t idx)\n{\n    LS_ASSERT(idx < x->size);\n\n    Wspec *elem = &x->data[idx];\n    free(elem->code);\n    elem->code = NULL;\n}\n\nvoid wspec_list_destroy(WspecList *x)\n{\n    size_t n = x->size;\n    for (size_t i = 0; i < n; ++i) {\n        Wspec *elem = &x->data[i];\n        free(elem->name);\n        free(elem->code);\n    }\n    free(x->data);\n}\n"
  },
  {
    "path": "plugins/multiplex/wspec_list.h",
    "content": "/*\n * Copyright (C) 2026  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#pragma once\n\n#include <stddef.h>\n#include \"libls/ls_compdep.h\"\n#include \"libls/ls_panic.h\"\n\ntypedef struct {\n    char *name;\n    char *code;\n} Wspec;\n\ntypedef struct {\n    Wspec *data;\n    size_t size;\n    size_t capacity;\n} WspecList;\n\nWspecList wspec_list_new(void);\n\nLS_INHEADER int wspec_list_size(WspecList *x)\n{\n    return x->size;\n}\n\nLS_INHEADER const char *wspec_list_get_name(WspecList *x, size_t idx)\n{\n    LS_ASSERT(idx < x->size);\n    return x->data[idx].name;\n}\n\nLS_INHEADER const char *wspec_list_get_code(WspecList *x, size_t idx)\n{\n    LS_ASSERT(idx < x->size);\n    return x->data[idx].code;\n}\n\nvoid wspec_list_add(WspecList *x, const char *name, const char *code);\n\nconst char *wspec_list_find_duplicates(WspecList *x);\n\nint wspec_list_find(WspecList *x, const char *name);\n\nvoid wspec_list_get_rid_of_code(WspecList *x, size_t idx);\n\nvoid wspec_list_destroy(WspecList *x);\n"
  },
  {
    "path": "plugins/network-linux/CMakeLists.txt",
    "content": "file (GLOB sources \"*.c\")\nluastatus_add_plugin (plugin-network-linux $<TARGET_OBJECTS:ls> $<TARGET_OBJECTS:moonvisit> ${sources})\n\ntarget_compile_definitions (plugin-network-linux PUBLIC -D_POSIX_C_SOURCE=200809L)\nluastatus_target_compile_with (plugin-network-linux LUA)\ntarget_include_directories (plugin-network-linux PUBLIC \"${PROJECT_SOURCE_DIR}\")\n\nfind_package (PkgConfig REQUIRED)\npkg_check_modules (NL_AND_CO REQUIRED libnl-3.0 libnl-genl-3.0)\nluastatus_target_build_with (plugin-network-linux NL_AND_CO)\n\nluastatus_add_man_page (README.rst luastatus-plugin-network-linux 7)\n"
  },
  {
    "path": "plugins/network-linux/README.rst",
    "content": ".. :X-man-page-only: luastatus-plugin-network-linux\n.. :X-man-page-only: ##############################\n.. :X-man-page-only:\n.. :X-man-page-only: #############################################\n.. :X-man-page-only: Network plugin for luastatus (Linux-specific)\n.. :X-man-page-only: #############################################\n.. :X-man-page-only:\n.. :X-man-page-only: :Copyright: LGPLv3\n.. :X-man-page-only: :Manual section: 7\n\nOverview\n========\nThis plugin monitors network routing and link updates.\nIt can report IP addresses used for outgoing connections by various network interfaces, information\nabout a wireless connection, and the speed of an Ethernet connection.\n\nOptions\n=======\nThe following options are supported:\n\n* ``ip``: boolean\n\n  Whether to report IP addresses used for outgoing connections. Defaults to true.\n\n* ``wireless``: boolean\n\n  Whether to report wireless connection info. Defaults to false.\n\n* ``ethernet``: boolean\n\n  Whether to report Ethernet connection info. Defaults to false.\n\n* ``new_overall_fmt``: boolean\n\n  Whether to use *new overall format* (see documentation below).\n  Defaults to false.\n\n* ``new_ip_fmt``: boolean\n\n  Whether to report IP (``ipv4`` and ``ipv6``) addresses as tables (as opposed to strings).\n  Defaults to false.\n\n* ``timeout``: number\n\n  If specified and not negative, requery information and call ``cb`` every ``timeout`` seconds.\n\n  Note that this is done on any routing/link update anyway, so this is only useful if you want to\n  show the \"volatile\" properties of a wireless connection such as signal level, bitrate, and\n  frequency.\n\n* ``make_self_pipe``: boolean\n\n  If true, the ``wake_up()`` (see the `Functions`_ section) function will be available. Defaults to\n  false.\n\n``cb`` argument\n===============\nIf the list of network interfaces cannot be fetched:\n* if new overall format is used, a table ``{what = \"error\"}``;\n* if old overall format is used, ``nil``.\n\nOtherwise (if there is no error):\n* if new overall format is used, a table ``{what = reason, data = tbl}``, where ``reason`` is a\nstring (see `Fetching the \"what\"`_) and ``tbl`` is the main table;\n* if old overall format is used, the main table itself (but, in this case, this table also has a metatable;\nsee `Fetching the \"what\"`_ section).\n\nThe main table is a table where keys are network interface names (e.g. ``wlan0`` or ``wlp1s0``) and values\nare tables with the following entries (all are optional):\n\n* ``ipv4``, ``ipv6`` (only if the ``ip`` option is enabled)\n\n  - If ``new_ip_fmt`` option was set to true, arrays (tables with numeric keys) of strings;\n    each element corresponds to a local IPv4/IPv6 address of the interface.\n\n    (In an extremely unlikely and probably logically impossible event that the number of addresses\n    exceeds maximum Lua array length, the array will be truncated and the last element will be ``false``,\n    not a string, to indicate truncation.)\n\n  - Otherwise, the value behind ``ipv4``/``ipv6`` key is a string corresponding to *some* local\n    IPv4/IPv6 address of the interface.\n\n* ``wireless``: table with following entries (only if the ``wireless`` option is enabled):\n\n  - ``ssid``: string\n\n    802.11 network service set identifier (also known as the \"network name\").\n\n  - ``signal_dbm``: number\n\n    Signal level, in dBm.\n    Generally, -90 corresponds to the worst quality, and -20 to the best quality.\n\n  - ``frequency``: number\n\n    Radio frequency, in Hz.\n\n  - ``bitrate``: number\n\n    Bitrate, in units of 100 kbit/s.\n\n* ``ethernet``: table with following entries (only if the ``ethernet`` option is enabled):\n\n  - ``speed``: number\n\n    Interface speed, in Mbits/s.\n\nFetching the \"what\"\n================================\n\nIf new overall format is used, and the argument is a table ``t``,\nthen the reason why the callback has been called is simply stored in ``t.what``.\n\nIf old overall format is used, and the argument (the main table) is a table ``t``,\nthen the reason why the callback has been called can be fetched as follows::\n\n    getmetatable(t).what\n\nThis value is either:\n\n* ``\"update\"`` network routing/link update;\n* ``\"timeout\"``: timeout;\n* ``\"self_pipe\"``: the ``luastatus.plugin.wake_up()`` function has been called.\n\nFor old overall format, the reason this shamanistic ritual with a metatable is\nneeded is that the support for any data other than the list of networks has not\nbeen planned for in advance, and ``what`` is a valid network interface name.\n\nFunctions\n=========\nThe following functions are provided:\n\n* ``luastatus.plugin.wake_up()``\n\n  Forces a call to ``cb``.\n\n  Only available if the ``make_self_pipe`` option was set to ``true``; otherwise, it throws an\n  error.\n"
  },
  {
    "path": "plugins/network-linux/ethernet_info.c",
    "content": "/*\n * Copyright (C) 2015-2025  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#define _GNU_SOURCE\n\n#include \"ethernet_info.h\"\n\n#include <linux/ethtool.h>\n#include <linux/sockios.h>\n#include <sys/ioctl.h>\n#include <sys/types.h>\n#include <net/if.h>\n#include <stdio.h>\n#include <string.h>\n#include <stdint.h>\n#include \"libls/ls_panic.h\"\n\nuint32_t get_ethernet_speed(int sockfd, const char *iface)\n{\n    LS_ASSERT(iface != NULL);\n\n    struct ethtool_cmd ecmd = {.cmd = ETHTOOL_GSET};\n    struct ifreq ifr = {.ifr_data = (caddr_t) &ecmd};\n\n    size_t niface = strlen(iface);\n    if (niface + 1 > sizeof(ifr.ifr_name)) {\n        goto fail;\n    }\n    memcpy(ifr.ifr_name, iface, niface + 1);\n\n    if (ioctl(sockfd, SIOCETHTOOL, &ifr) < 0) {\n        goto fail;\n    }\n\n    uint32_t res = ethtool_cmd_speed(&ecmd);\n    if (res == (uint32_t) SPEED_UNKNOWN) {\n        goto fail;\n    }\n    return res;\n\nfail:\n    return 0;\n}\n"
  },
  {
    "path": "plugins/network-linux/ethernet_info.h",
    "content": "/*\n * Copyright (C) 2015-2025  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifndef ethernet_info_h_\n#define ethernet_info_h_\n\n#include <stdint.h>\n\nuint32_t get_ethernet_speed(int sockfd, const char *iface);\n\n#endif\n"
  },
  {
    "path": "plugins/network-linux/iface_type.c",
    "content": "/*\n * Copyright (C) 2015-2025  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#include \"iface_type.h\"\n\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n#include <fcntl.h>\n#include \"libls/ls_io_utils.h\"\n#include \"libls/ls_panic.h\"\n\nbool is_wlan_iface(const char *iface)\n{\n    LS_ASSERT(iface != NULL);\n\n    FILE *f = NULL;\n    int fd = -1;\n    char *line = NULL;\n    size_t nline = 0;\n    bool ret = false;\n\n    char path[256];\n    snprintf(path, sizeof(path), \"/sys/class/net/%s/uevent\", iface);\n    if ((fd = open(path, O_RDONLY | O_CLOEXEC)) < 0) {\n        goto done;\n    }\n    if (!(f = fdopen(fd, \"r\"))) {\n        goto done;\n    }\n    fd = -1;\n    while (getline(&line, &nline, f) > 0) {\n        if (strcmp(line, \"DEVTYPE=wlan\\n\") == 0) {\n            ret = true;\n            goto done;\n        }\n    }\n\ndone:\n    free(line);\n    ls_close(fd);\n    if (f) {\n        fclose(f);\n    }\n    return ret;\n}\n"
  },
  {
    "path": "plugins/network-linux/iface_type.h",
    "content": "/*\n * Copyright (C) 2015-2025  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifndef iface_type_h_\n#define iface_type_h_\n\n#include <stdbool.h>\n\nbool is_wlan_iface(const char *iface);\n\n#endif\n"
  },
  {
    "path": "plugins/network-linux/network.c",
    "content": "/*\n * Copyright (C) 2015-2025  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#include <lua.h>\n#include <sys/types.h>\n#include <sys/socket.h>\n#include <sys/uio.h>\n#include <ifaddrs.h>\n#include <netdb.h>\n#include <linux/netlink.h>\n#include <linux/rtnetlink.h>\n#include <unistd.h>\n#include <stdlib.h>\n#include <stdbool.h>\n#include <stdint.h>\n#include <errno.h>\n#include <poll.h>\n#include <netinet/in.h>\n\n#include \"include/plugin_v1.h\"\n#include \"include/sayf_macros.h\"\n\n#include \"libmoonvisit/moonvisit.h\"\n\n#include \"libls/ls_alloc_utils.h\"\n#include \"libls/ls_io_utils.h\"\n#include \"libls/ls_osdep.h\"\n#include \"libls/ls_tls_ebuf.h\"\n#include \"libls/ls_time_utils.h\"\n#include \"libls/ls_evloop_lfuncs.h\"\n#include \"libls/ls_lua_compat.h\"\n\n#include \"string_set.h\"\n#include \"wireless_info.h\"\n#include \"ethernet_info.h\"\n#include \"iface_type.h\"\n\ntypedef struct {\n    bool report_ip;\n    bool report_wireless;\n    bool report_ethernet;\n    bool new_ip_fmt;\n    bool new_overall_fmt;\n    double tmo;\n    StringSet wlan_ifaces;\n    int eth_sockfd;\n    int pipefds[2];\n} Priv;\n\nstatic void destroy(LuastatusPluginData *pd)\n{\n    Priv *p = pd->priv;\n    string_set_destroy(p->wlan_ifaces);\n    ls_close(p->eth_sockfd);\n    ls_close(p->pipefds[0]);\n    ls_close(p->pipefds[1]);\n    free(p);\n}\n\nstatic int init(LuastatusPluginData *pd, lua_State *L)\n{\n    Priv *p = pd->priv = LS_XNEW(Priv, 1);\n    *p = (Priv) {\n        .report_ip = true,\n        .report_wireless = false,\n        .report_ethernet = false,\n        .new_ip_fmt = false,\n        .new_overall_fmt = false,\n        .tmo = -1,\n        .wlan_ifaces = string_set_new(),\n        .eth_sockfd = -1,\n        .pipefds = {-1, -1},\n    };\n    char errbuf[256];\n    MoonVisit mv = {.L = L, .errbuf = errbuf, .nerrbuf = sizeof(errbuf)};\n\n    // Parse ip\n    if (moon_visit_bool(&mv, -1, \"ip\", &p->report_ip, true) < 0)\n        goto mverror;\n\n    // Parse wireless\n    if (moon_visit_bool(&mv, -1, \"wireless\", &p->report_wireless, true) < 0)\n        goto mverror;\n\n    // Parse ethernet\n    if (moon_visit_bool(&mv, -1, \"ethernet\", &p->report_ethernet, true) < 0)\n        goto mverror;\n\n    // Parse new_ip_fmt\n    if (moon_visit_bool(&mv, -1, \"new_ip_fmt\", &p->new_ip_fmt, true) < 0)\n        goto mverror;\n\n    // Parse new_overall_fmt\n    if (moon_visit_bool(&mv, -1, \"new_overall_fmt\", &p->new_overall_fmt, true) < 0)\n        goto mverror;\n\n    // Parse timeout\n    if (moon_visit_num(&mv, -1, \"timeout\", &p->tmo, true) < 0)\n        goto mverror;\n\n    // Parse make_self_pipe\n    bool make_self_pipe = false;\n    if (moon_visit_bool(&mv, -1, \"make_self_pipe\", &make_self_pipe, true) < 0) {\n        goto mverror;\n    }\n    if (make_self_pipe) {\n        LS_DEBUGF(pd, \"making self-pipe\");\n        if (ls_self_pipe_open(p->pipefds) < 0) {\n            LS_FATALF(pd, \"ls_self_pipe_open: %s\", ls_tls_strerror(errno));\n            goto error;\n        }\n    }\n\n    // Open eth_sockfd if needed.\n    if (p->report_ethernet) {\n        p->eth_sockfd = ls_cloexec_socket(AF_INET, SOCK_DGRAM, 0);\n        if (p->eth_sockfd < 0) {\n            LS_WARNF(pd, \"ls_cloexec_socket: %s\", ls_tls_strerror(errno));\n        }\n    }\n\n    return LUASTATUS_OK;\n\nmverror:\n    LS_FATALF(pd, \"%s\", errbuf);\nerror:\n    destroy(pd);\n    return LUASTATUS_ERR;\n}\n\nstatic void register_funcs(LuastatusPluginData *pd, lua_State *L)\n{\n    Priv *p = pd->priv;\n    // L: table\n    ls_self_pipe_push_luafunc(p->pipefds, L); // L: table func\n    lua_setfield(L, -2, \"wake_up\"); // L: table\n}\n\nstatic void report_error(LuastatusPluginData *pd, LuastatusPluginRunFuncs funcs)\n{\n    Priv *p = pd->priv;\n\n    lua_State *L = funcs.call_begin(pd->userdata);\n    // L: ?\n\n    if (p->new_overall_fmt) {\n        lua_createtable(L, 0, 1); // L: ? table\n        lua_pushstring(L, \"error\"); // L: ? table str\n        lua_setfield(L, -2, \"what\"); // L: ? table\n    } else {\n        lua_pushnil(L); // L: ? nil\n    }\n\n    funcs.call_end(pd->userdata);\n}\n\nstatic void inject_ip_info(lua_State *L, struct ifaddrs *addr, bool new_ip_fmt)\n{\n    // L: ? ifacetbl\n    if (!addr->ifa_addr) {\n        return;\n    }\n    int family = addr->ifa_addr->sa_family;\n    if (family != AF_INET && family != AF_INET6) {\n        return;\n    }\n    char host[1025];\n    int r = getnameinfo(\n        addr->ifa_addr,\n        family == AF_INET ? sizeof(struct sockaddr_in) : sizeof(struct sockaddr_in6),\n        host, sizeof(host),\n        NULL, 0, NI_NUMERICHOST);\n    if (r) {\n        return;\n    }\n\n    const char *k = family == AF_INET ? \"ipv4\" : \"ipv6\";\n    if (new_ip_fmt) {\n        lua_getfield(L, -1, k); // L: ? ifacetbl tbl\n        if (lua_isnil(L, -1)) {\n            lua_pop(L, 1); // L: ? ifacetbl\n            lua_newtable(L); // L: ? ifacetbl tbl\n            lua_pushstring(L, host); // L: ? ifacetbl tbl str\n            lua_rawseti(L, -2, 1); // L: ? ifacetbl tbl\n            lua_setfield(L, -2, k); // L: ? ifacetbl\n        } else {\n            size_t n = ls_lua_array_len(L, -1); // L: ? ifacetbl tbl\n            if (n == (size_t) LS_LUA_MAXI) {\n                lua_pushboolean(L, 0); // L: ? ifacetbl tbl false\n                lua_rawseti(L, -2, n); // L: ? ifacetbl tbl\n            } else {\n                lua_pushstring(L, host); // L: ? ifacetbl tbl str\n                lua_rawseti(L, -2, n + 1); // L: ? ifacetbl tbl\n            }\n            lua_pop(L, 1); // L: ? ifacetbl\n        }\n    } else {\n        lua_pushstring(L, host); // L: ? ifacetbl str\n        lua_setfield(L, -2, k); // L: ? ifacetbl\n    }\n}\n\nstatic void inject_wireless_info(lua_State *L, struct ifaddrs *addr)\n{\n    WirelessInfo info;\n    if (!get_wireless_info(addr->ifa_name, &info)) {\n        return;\n    }\n\n    // L: ? ifacetbl\n    lua_createtable(L, 0, 4); // L: ? ifacetbl table\n    if (info.flags & HAS_ESSID) {\n        lua_pushstring(L, info.essid); // L: ? ifacetbl table str\n        lua_setfield(L, -2, \"ssid\"); // L: ? ifacetbl table\n    }\n    if (info.flags & HAS_SIGNAL_DBM) {\n        lua_pushnumber(L, info.signal_dbm); // L: ? ifacetbl table number\n        lua_setfield(L, -2, \"signal_dbm\"); // L: ? ifacetbl table\n    }\n    if (info.flags & HAS_FREQUENCY) {\n        lua_pushnumber(L, info.frequency); // L: ? ifacetbl table number\n        lua_setfield(L, -2, \"frequency\"); // L: ? ifacetbl table\n    }\n    if (info.flags & HAS_BITRATE) {\n        lua_pushnumber(L, info.bitrate); // L: ? ifacetbl table number\n        lua_setfield(L, -2, \"bitrate\"); // L: ? ifacetbl table\n    }\n    lua_setfield(L, -2, \"wireless\"); // L: ? ifacetbl\n}\n\nstatic void inject_ethernet_info(lua_State *L, struct ifaddrs *addr, int sockfd)\n{\n    if (sockfd < 0) {\n        return;\n    }\n    uint32_t speed = get_ethernet_speed(sockfd, addr->ifa_name);\n    if (!speed) {\n        return;\n    }\n    // L: ? ifacetbl\n    lua_createtable(L, 0, 1); // L: ? ifacetbl table\n\n    lua_pushnumber(L, speed); // L: ? ifacetbl table number\n    lua_setfield(L, -2, \"speed\"); // L: ? ifacetbl table\n\n    lua_setfield(L, -2, \"ethernet\"); // L: ? ifacetbl\n}\n\nstatic void begin_call(Priv *p, lua_State *L)\n{\n    // L: ?\n    if (p->new_overall_fmt) {\n        lua_createtable(L, 0, 2); // L: ? outer_table\n    }\n}\n\nstatic void end_call(Priv *p, lua_State *L, const char *what)\n{\n    if (p->new_overall_fmt) {\n        // L: ? outer_table table\n        lua_setfield(L, -2, \"data\"); // L: ? outer_table\n\n        lua_pushstring(L, what); // L: ? outer_table what\n        lua_setfield(L, -2, \"what\"); // L: ? outer_table\n    } else {\n        // L: ? table\n        lua_createtable(L, 0, 1); // L: ? table mt\n        lua_pushstring(L, what); // L: ? table mt what\n        lua_setfield(L, -2, \"what\"); // L: ? table mt\n        lua_setmetatable(L, -2); // L: ? table\n    }\n}\n\nstatic void make_call(\n        LuastatusPluginData *pd,\n        LuastatusPluginRunFuncs funcs,\n        bool refresh,\n        const char *what)\n{\n    struct ifaddrs *ifaddr;\n    if (getifaddrs(&ifaddr) < 0) {\n        LS_ERRF(pd, \"getifaddrs: %s\", ls_tls_strerror(errno));\n        report_error(pd, funcs);\n        return;\n    }\n\n    Priv *p = pd->priv;\n\n    lua_State *L = funcs.call_begin(pd->userdata);\n\n    // L: ?\n    begin_call(p, L); // L: ?...\n\n    lua_newtable(L); // L: ?... table\n\n    if (refresh) {\n        string_set_reset(&p->wlan_ifaces);\n    }\n\n    for (struct ifaddrs *cur = ifaddr; cur; cur = cur->ifa_next) {\n        lua_getfield(L, -1, cur->ifa_name); // L: ?... table ifacetbl\n        if (lua_isnil(L, -1)) {\n            lua_pop(L, 1); // L: ?... table\n            lua_newtable(L); // L: ?... table ifacetbl\n            lua_pushvalue(L, -1); // L: ?... table ifacetbl ifacetbl\n            lua_setfield(L, -3, cur->ifa_name); // L: ?... table ifacetbl\n\n            if (p->report_wireless) {\n                bool is_wlan;\n                if (refresh) {\n                    is_wlan = is_wlan_iface(cur->ifa_name);\n                    if (is_wlan) {\n                        string_set_add(&p->wlan_ifaces, cur->ifa_name);\n                    }\n                } else {\n                    is_wlan = string_set_contains(p->wlan_ifaces, cur->ifa_name);\n                }\n                if (is_wlan) {\n                    inject_wireless_info(L, cur); // L: ?... table ifacetbl\n                }\n            }\n\n            if (p->report_ethernet) {\n                inject_ethernet_info(L, cur, p->eth_sockfd); // L: ?... table ifacetbl\n            }\n        }\n\n        if (p->report_ip) {\n            inject_ip_info(L, cur, p->new_ip_fmt); // L: ?... table ifacetbl\n        }\n\n        lua_pop(L, 1); // L: ?... table\n    }\n\n    // L: ?... table\n\n    end_call(p, L, what); // L: ? result\n\n    if (refresh) {\n        string_set_freeze(&p->wlan_ifaces);\n    }\n\n    freeifaddrs(ifaddr);\n\n    funcs.call_end(pd->userdata);\n}\n\nstatic ssize_t my_recvmsg(int fd_netlink, struct msghdr *msg, LS_TimeDelta tmo, int fd_extra)\n{\n    struct pollfd pfds[2] = {\n        {.fd = fd_netlink, .events = POLLIN},\n        {.fd = fd_extra,   .events = POLLIN},\n    };\n    int poll_rc = ls_poll(pfds, 2, tmo);\n    if (poll_rc < 0) {\n        // 'poll()' failed somewhy.\n        return -1;\n    }\n    if (poll_rc == 0) {\n        // Timeout reached.\n        errno = EAGAIN;\n        return -1;\n    }\n    if (pfds[1].revents & POLLIN) {\n        // We have some input on /fd_extra/.\n\n        char dummy;\n        ssize_t nread = read(fd_extra, &dummy, 1);\n        (void) nread;\n\n        errno = 0;\n        return -1;\n    }\n    // If 'recvmsg()' below fails with /EAGAIN/ or /EWOULDBLOCK/, we do exactly the right\n    // thing: return -1 and set errno to /EAGAIN/ or /EWOULDBLOCK/, which signals a timeout.\n    return recvmsg(fd_netlink, msg, 0);\n}\n\nstatic bool interpret_nl_msg(LuastatusPluginData *pd, char *msg_buf, size_t len)\n{\n    for (\n        struct nlmsghdr *nh = (struct nlmsghdr *) msg_buf;\n        NLMSG_OK(nh, len);\n        nh = NLMSG_NEXT(nh, len))\n    {\n        if (nh->nlmsg_type == NLMSG_DONE) {\n            // end of multipart message\n            break;\n        }\n        if (nh->nlmsg_type == NLMSG_ERROR) {\n            if (nh->nlmsg_len < NLMSG_LENGTH(sizeof(struct nlmsgerr))) {\n                LS_ERRF(pd, \"netlink error message truncated\");\n                return false;\n            }\n            struct nlmsgerr *e = NLMSG_DATA(nh);\n            int errnum = e->error;\n            if (errnum) {\n                LS_ERRF(pd, \"netlink error: %s\", ls_tls_strerror(-errnum));\n                return false;\n            } else {\n                LS_WARNF(pd, \"unexpected ACK - what's going on?\");\n                continue;\n            }\n        }\n        // we don't care about the message\n    }\n    return true;\n}\n\nstatic bool interact(LuastatusPluginData *pd, LuastatusPluginRunFuncs funcs)\n{\n    Priv *p = pd->priv;\n\n    // We allocate the buffer for netlink messages on the heap rather than on the stack, for two\n    // reasons:\n    //\n    // 1. Alignment. The code in the netlink(7) man page seems to be wrong as it does not use\n    // /__attribute__((aligned(...)))/, which is used, e.g., in the inotify(7) example.\n    //\n    // 2. Stack space is not free, and 8K is quite a large allocation for the stack.\n    //\n    // As for the buffer size, netlink(7) says \"8192 to avoid message truncation on platforms with\n    // page size > 4096\".\n    enum { NBUF = 8192 };\n\n    bool ret = false;\n    char *buf = LS_XNEW(char, NBUF);\n    int fd = -1;\n\n    fd = ls_cloexec_socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);\n    if (fd < 0) {\n        LS_FATALF(pd, \"socket: %s\", ls_tls_strerror(errno));\n        goto error;\n    }\n    ls_make_nonblock(fd);\n\n    struct sockaddr_nl sa = {\n        .nl_family = AF_NETLINK,\n        .nl_groups = RTMGRP_LINK | RTMGRP_IPV4_IFADDR | RTMGRP_IPV6_IFADDR,\n    };\n    if (bind(fd, (struct sockaddr *) &sa, sizeof(sa)) < 0) {\n        LS_FATALF(pd, \"bind: %s\", ls_tls_strerror(errno));\n        goto error;\n    }\n\n    LS_TimeDelta TD = ls_double_to_TD(p->tmo, LS_TD_FOREVER);\n\n    make_call(pd, funcs, true, \"update\");\n\n    while (1) {\n        struct iovec iov = {.iov_base = buf, .iov_len = NBUF};\n        struct msghdr msg = {.msg_iov = &iov, .msg_iovlen = 1};\n        ssize_t len = my_recvmsg(fd, &msg, TD, p->pipefds[0]);\n        if (len < 0) {\n            if (errno == 0) {\n                make_call(pd, funcs, false, \"self_pipe\");\n                continue;\n            } else if (errno == EINTR) {\n                make_call(pd, funcs, true, \"update\");\n                continue;\n            } else if (LS_IS_EAGAIN(errno)) {\n                make_call(pd, funcs, false, \"timeout\");\n                continue;\n            } else if (errno == ENOBUFS) {\n                ret = true;\n                LS_WARNF(pd, \"ENOBUFS - kernel's socket buffer is full\");\n                goto error;\n            } else {\n                LS_FATALF(pd, \"my_recvmsg: %s\", ls_tls_strerror(errno));\n                goto error;\n            }\n        }\n\n        if (!interpret_nl_msg(pd, buf, len)) {\n            ret = true;\n            goto error;\n        }\n\n        make_call(pd, funcs, true, \"update\");\n    }\n\nerror:\n    free(buf);\n    ls_close(fd);\n    return ret;\n}\n\nstatic void run(LuastatusPluginData *pd, LuastatusPluginRunFuncs funcs)\n{\n    while (interact(pd, funcs)) {\n        // a non-fatal error occurred\n        report_error(pd, funcs);\n        ls_sleep_simple(5.0);\n        LS_INFOF(pd, \"resynchronizing\");\n    }\n}\n\nLuastatusPluginIface_v1 luastatus_plugin_iface_v1 = {\n    .init = init,\n    .register_funcs = register_funcs,\n    .run = run,\n    .destroy = destroy,\n};\n"
  },
  {
    "path": "plugins/network-linux/string_set.c",
    "content": "/*\n * Copyright (C) 2015-2025  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#include \"string_set.h\"\n\n#include <string.h>\n#include <stdlib.h>\n#include \"libls/ls_alloc_utils.h\"\n#include \"libls/ls_compdep.h\"\n#include \"libls/ls_freemem.h\"\n#include \"libls/ls_panic.h\"\n\nStringSet string_set_new(void)\n{\n    return (StringSet) {NULL, 0, 0};\n}\n\nvoid string_set_reset(StringSet *s)\n{\n    for (size_t i = 0; i < s->size; ++i) {\n        free(s->data[i]);\n    }\n    s->data = LS_M_FREEMEM(s->data, &s->size, &s->capacity);\n}\n\nvoid string_set_add(StringSet *s, const char *val)\n{\n    LS_ASSERT(val != NULL);\n\n    if (s->size == s->capacity) {\n        s->data = LS_M_X2REALLOC(s->data, &s->capacity);\n    }\n    s->data[s->size++] = ls_xstrdup(val);\n}\n\nstatic int elem_cmp(const void *a, const void *b)\n{\n    return strcmp(* (const char *const *) a,\n                  * (const char *const *) b);\n}\n\nvoid string_set_freeze(StringSet *s)\n{\n    if (s->size) {\n        qsort(s->data, s->size, sizeof(char *), elem_cmp);\n    }\n}\n\nbool string_set_contains(StringSet s, const char *val)\n{\n    LS_ASSERT(val != NULL);\n\n    if (!s.size) {\n        return false;\n    }\n    return bsearch(&val, s.data, s.size, sizeof(char *), elem_cmp) != NULL;\n}\n\nvoid string_set_destroy(StringSet s)\n{\n    for (size_t i = 0; i < s.size; ++i) {\n        free(s.data[i]);\n    }\n    free(s.data);\n}\n"
  },
  {
    "path": "plugins/network-linux/string_set.h",
    "content": "/*\n * Copyright (C) 2015-2025  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifndef string_set_h_\n#define string_set_h_\n\n#include <stdlib.h>\n#include <stdbool.h>\n\ntypedef struct {\n    char **data;\n    size_t size;\n    size_t capacity;\n} StringSet;\n\nStringSet string_set_new(void);\n\n// Clears and unfreezes the set.\nvoid string_set_reset(StringSet *s);\n\nvoid string_set_add(StringSet *s, const char *val);\n\nvoid string_set_freeze(StringSet *s);\n\nbool string_set_contains(StringSet s, const char *val);\n\nvoid string_set_destroy(StringSet s);\n\n#endif\n"
  },
  {
    "path": "plugins/network-linux/wireless_info.c",
    "content": "/*\n * Copyright (C) 2015-2025  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#include \"wireless_info.h\"\n\n#include <stdbool.h>\n#include <stdint.h>\n#include <string.h>\n#include <stdio.h>\n\n#include <net/if.h>\n#include <netlink/netlink.h>\n#include <netlink/genl/genl.h>\n#include <netlink/genl/ctrl.h>\n#include <netlink/attr.h>\n#include <netlink/handlers.h>\n#include <netlink/msg.h>\n#include <netlink/socket.h>\n#include <linux/nl80211.h>\n#include <linux/if_ether.h>\n#include <linux/netlink.h>\n\n// Adapted from i3status/src/print_wireless_info.c\n\nstatic void find_ssid(uint8_t *ies, uint32_t ies_len, uint8_t **ssid, uint32_t *ssid_len)\n{\n    enum { WLAN_EID_SSID = 0 };\n\n    for (;;) {\n        if (ies_len < 2) {\n            goto not_found;\n        }\n\n        uint32_t cur_len = ies[1] + 2;\n        if (cur_len > ies_len) {\n            goto not_found;\n        }\n\n        if (ies[0] == WLAN_EID_SSID) {\n            *ssid = ies + 2;\n            *ssid_len = cur_len - 2;\n            return;\n        }\n\n        ies_len -= cur_len;\n        ies += cur_len;\n    }\n\nnot_found:\n    *ssid = NULL;\n    *ssid_len = 0;\n}\n\nstatic int gwi_sta_cb(struct nl_msg *msg, void *vud)\n{\n    WirelessInfo *info = vud;\n\n    struct nlattr *tb[NL80211_ATTR_MAX + 1];\n    struct genlmsghdr *gnlh = nlmsg_data(nlmsg_hdr(msg));\n    struct nlattr *sinfo[NL80211_STA_INFO_MAX + 1];\n    struct nlattr *rinfo[NL80211_RATE_INFO_MAX + 1];\n    struct nla_policy stats_policy[NL80211_STA_INFO_MAX + 1] = {\n        [NL80211_STA_INFO_RX_BITRATE] = {.type = NLA_NESTED},\n        [NL80211_STA_INFO_SIGNAL] = {.type = NLA_U8},\n    };\n    struct nla_policy rate_policy[NL80211_RATE_INFO_MAX + 1] = {\n        [NL80211_RATE_INFO_BITRATE] = {.type = NLA_U16},\n    };\n\n    if (nla_parse(\n                tb,\n                NL80211_ATTR_MAX,\n                genlmsg_attrdata(gnlh, 0),\n                genlmsg_attrlen(gnlh, 0),\n                NULL)\n            < 0)\n    {\n        return NL_SKIP;\n    }\n\n    if (!tb[NL80211_ATTR_STA_INFO])\n        return NL_SKIP;\n\n    if (nla_parse_nested(\n                sinfo,\n                NL80211_STA_INFO_MAX,\n                tb[NL80211_ATTR_STA_INFO],\n                stats_policy)\n            < 0)\n    {\n        return NL_SKIP;\n    }\n\n    if (sinfo[NL80211_STA_INFO_SIGNAL]) {\n        info->flags |= HAS_SIGNAL_DBM;\n        info->signal_dbm = (int8_t) nla_get_u8(sinfo[NL80211_STA_INFO_SIGNAL]);\n    }\n\n    if (!sinfo[NL80211_STA_INFO_RX_BITRATE])\n        return NL_SKIP;\n\n    if (nla_parse_nested(\n                rinfo,\n                NL80211_RATE_INFO_MAX,\n                sinfo[NL80211_STA_INFO_RX_BITRATE],\n                rate_policy)\n            < 0)\n    {\n        return NL_SKIP;\n    }\n\n    if (!rinfo[NL80211_RATE_INFO_BITRATE])\n        return NL_SKIP;\n\n    info->flags |= HAS_BITRATE;\n    info->bitrate = nla_get_u16(rinfo[NL80211_RATE_INFO_BITRATE]);\n\n    return NL_SKIP;\n}\n\nstatic int gwi_scan_cb(struct nl_msg *msg, void *vud)\n{\n    WirelessInfo *info = vud;\n    struct genlmsghdr *gnlh = nlmsg_data(nlmsg_hdr(msg));\n    struct nlattr *tb[NL80211_ATTR_MAX + 1];\n    struct nlattr *bss[NL80211_BSS_MAX + 1];\n    struct nla_policy bss_policy[NL80211_BSS_MAX + 1] = {\n        [NL80211_BSS_FREQUENCY] = {.type = NLA_U32},\n        [NL80211_BSS_BSSID] = {.type = NLA_UNSPEC},\n        [NL80211_BSS_INFORMATION_ELEMENTS] = {.type = NLA_UNSPEC},\n        [NL80211_BSS_STATUS] = {.type = NLA_U32},\n    };\n\n    if (nla_parse(\n                tb,\n                NL80211_ATTR_MAX,\n                genlmsg_attrdata(gnlh, 0),\n                genlmsg_attrlen(gnlh, 0),\n                NULL)\n            < 0)\n    {\n        return NL_SKIP;\n    }\n\n    if (!tb[NL80211_ATTR_BSS])\n        return NL_SKIP;\n\n    if (nla_parse_nested(\n                bss,\n                NL80211_BSS_MAX,\n                tb[NL80211_ATTR_BSS],\n                bss_policy)\n            < 0)\n    {\n        return NL_SKIP;\n    }\n\n    if (!bss[NL80211_BSS_STATUS])\n        return NL_SKIP;\n\n    uint32_t status = nla_get_u32(bss[NL80211_BSS_STATUS]);\n\n    if (status != NL80211_BSS_STATUS_ASSOCIATED &&\n        status != NL80211_BSS_STATUS_IBSS_JOINED)\n    {\n        return NL_SKIP;\n    }\n\n    if (!bss[NL80211_BSS_BSSID])\n        return NL_SKIP;\n\n    memcpy(info->bssid, nla_data(bss[NL80211_BSS_BSSID]), ETH_ALEN);\n\n    if (bss[NL80211_BSS_FREQUENCY]) {\n        info->flags |= HAS_FREQUENCY;\n        info->frequency = ((double) nla_get_u32(bss[NL80211_BSS_FREQUENCY])) * 1e6;\n    }\n\n    if (bss[NL80211_BSS_INFORMATION_ELEMENTS]) {\n        uint8_t *ssid;\n        uint32_t ssid_len;\n\n        find_ssid(\n            nla_data(bss[NL80211_BSS_INFORMATION_ELEMENTS]),\n            nla_len(bss[NL80211_BSS_INFORMATION_ELEMENTS]),\n            &ssid,\n            &ssid_len);\n\n        if (ssid && ssid_len) {\n            info->flags |= HAS_ESSID;\n            snprintf(info->essid, sizeof(info->essid), \"%.*s\", (int) ssid_len, ssid);\n        }\n    }\n\n    return NL_SKIP;\n}\n\nbool get_wireless_info(const char *iface, WirelessInfo *info)\n{\n    memset(info, 0, sizeof(WirelessInfo));\n    bool ok = false;\n    struct nl_sock *sk = NULL;\n    struct nl_msg *msg = NULL;\n    int r;\n\n    if (!(sk = nl_socket_alloc()))\n        goto done;\n\n    if (genl_connect(sk) != 0)\n        goto done;\n\n    if (nl_socket_modify_cb(sk, NL_CB_VALID, NL_CB_CUSTOM, gwi_scan_cb, info) < 0)\n        goto done;\n\n    int nl80211_id = genl_ctrl_resolve(sk, \"nl80211\");\n    if (nl80211_id < 0)\n        goto done;\n\n\n    unsigned ifidx = if_nametoindex(iface);\n    if (ifidx == 0)\n        goto done;\n\n    if (!(msg = nlmsg_alloc()))\n        goto done;\n\n    if (!genlmsg_put(\n            msg,\n            NL_AUTO_PORT,\n            NL_AUTO_SEQ,\n            nl80211_id,\n            0,\n            NLM_F_DUMP,\n            NL80211_CMD_GET_SCAN,\n            0)\n        )\n    {\n        goto done;\n    }\n    if (nla_put_u32(msg, NL80211_ATTR_IFINDEX, ifidx) < 0)\n        goto done;\n\n    r = nl_send_sync(sk, msg);\n    msg = NULL;\n    if (r < 0)\n        goto done;\n\n    if (nl_socket_modify_cb(sk, NL_CB_VALID, NL_CB_CUSTOM, gwi_sta_cb, info) < 0)\n        goto done;\n\n    if (!(msg = nlmsg_alloc()))\n        goto done;\n\n    if (!genlmsg_put(\n            msg,\n            NL_AUTO_PORT,\n            NL_AUTO_SEQ,\n            nl80211_id,\n            0,\n            NLM_F_DUMP,\n            NL80211_CMD_GET_STATION,\n            0)\n        )\n    {\n        goto done;\n    }\n\n    if (nla_put_u32(msg, NL80211_ATTR_IFINDEX, ifidx) < 0)\n        goto done;\n\n    if (nla_put(msg, NL80211_ATTR_MAC, 6, info->bssid) < 0)\n        goto done;\n\n    r = nl_send_sync(sk, msg);\n    msg = NULL;\n    if (r < 0)\n        goto done;\n\n    ok = true;\ndone:\n    if (msg)\n        nlmsg_free(msg);\n    if (sk)\n        nl_socket_free(sk);\n    return ok;\n}\n"
  },
  {
    "path": "plugins/network-linux/wireless_info.h",
    "content": "/*\n * Copyright (C) 2015-2025  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifndef wireless_info_h_\n#define wireless_info_h_\n\n#include <stdint.h>\n#include <stdbool.h>\n#include <linux/if_ether.h>\n\nenum { ESSID_MAX = 32 };\n\nenum {\n    HAS_ESSID         = 1 << 0,\n    HAS_SIGNAL_DBM    = 1 << 1,\n    HAS_BITRATE       = 1 << 2,\n    HAS_FREQUENCY     = 1 << 3,\n};\n\ntypedef struct {\n    int      flags;\n    char     essid[ESSID_MAX + 1];\n    uint8_t  bssid[ETH_ALEN];\n    int      signal_dbm;\n    unsigned bitrate;         // in units of 100 kbit/s\n    double   frequency;\n} WirelessInfo;\n\nbool get_wireless_info(const char *iface, WirelessInfo *info);\n\n#endif\n"
  },
  {
    "path": "plugins/network-rate-linux/CMakeLists.txt",
    "content": "install (FILES network-rate-linux.lua DESTINATION ${LUA_PLUGINS_DIR})\n\nluastatus_add_man_page (README.rst luastatus-plugin-network-rate-linux 7)\n"
  },
  {
    "path": "plugins/network-rate-linux/README.rst",
    "content": ".. :X-man-page-only: luastatus-plugin-network-rate-linux\n.. :X-man-page-only: ###################################\n.. :X-man-page-only:\n.. :X-man-page-only: ################################################\n.. :X-man-page-only: Linux-specific network rate plugin for luastatus\n.. :X-man-page-only: ################################################\n.. :X-man-page-only:\n.. :X-man-page-only: :Copyright: LGPLv3\n.. :X-man-page-only: :Manual section: 7\n\nOverview\n========\nThis derived plugin periodically polls Linux ``procfs`` for the network receive/send rate (traffic\nusage per unit of time).\n\nFunctions\n=========\nThe following functions are provided:\n\n* ``reader_make([iface_filter])``\n\n  Creates a *reader* that can be used to query network rates for network interfaces that match\n  the filter.\n\n  If specified, ``iface_filter`` must be a function that takes a string (interface name) and\n  return ``true`` if the reader should should report network rates for this interface,\n  ``false`` or ``nil`` otherwise. If ``iface_filter`` is not specified the reader will report\n  network rates for all interfaces.\n\n* ``reader_read(reader, divisor[, in_array_form])``\n\n  Queries network rates using the given reader. ``reader`` must be a reader instance returned\n  by ``reader_make``.\n\n  For a given interface, we define a *datum* as a table of form with keys ``\"R\"`` and ``\"S\"``;\n  the ``\"R\"``/``\"S\"`` fields contain the increase in the number of bytes received/sent,\n  correspondingly, with this interface, since the last query with this reader, divided by\n  ``divisor``.\n\n  Normally, ``divisor`` should be the period with which you query the network rates with this\n  reader; if not applicable, set ``divisor`` to 1 and interpret the values yourself.\n\n  If ``in_array_form`` is ``true``, the result will be an array (a table with numeric keys) of\n  ``{iface_name, datum}`` entries, in the same order as reported by the kernel. Otherwise (if\n  ``in_array_form`` is not specified or ``false``), the result will be a dictionary (a table\n  with string keys) with interface names as keys and datums as values.\n\n* ``widget(tbl)``\n\n  Constructs a ``widget`` table required by luastatus. ``tbl`` is a table with the following\n  fields:\n\n  **(required)**\n\n  - ``cb``: a function\n\n    The callback to call each ``period`` (see below) seconds with the network rate data.\n    It is called with a single argument that is equivalent to ``reader_read`` return\n    value (please refer to the description of that function above).\n\n  **(optional)**\n\n  Note: ``iface_filter``, ``iface_only`` and ``iface_except`` are mutually exclusive.\n  If neither is specified, network rates for all interfaces will be reported.\n\n  - ``iface_filter``: a function\n\n    If specified, this must be a function that takes a string (interface name) and return\n    ``true`` if we are interested in network rates for this interface, ``false`` or ``nil``\n    otherwise.\n\n  - ``iface_only``/``iface_except``: a string or a table\n\n    These options can be used to specify a whitelist/blacklist, correspondingly, for network\n    interfaces that we are interested in.\n\n    If it is a string, it will be interpreted as an interface name; only interface with this\n    name will be whitelisted/blacklisted.\n\n    If it is a table with numeric keys, it will be interpreted as a list of interface names\n    that should be whitelisted/blacklisted.\n\n    If it is a table with string keys, it will be interpreted as a lookup table:\n    interface names that correspond to a truth-y (anything except ``false`` and ``nil``)\n    value in this table will be whitelisted/blacklisted.\n\n    If it is an empty table, no interfaces will be whitelisted/blacklisted.\n\n  - ``in_array_form``: a boolean\n\n    Please refer to the description of ``in_array_form`` argument to ``reader_read``\n    function above.\n\n    Defaults to ``false``.\n\n  - ``period``: a number\n\n    The period, in seconds, with which the callback function will be called.\n\n    This will also be a divisor (all rates will be divided by this value\n    so that the rates be per ``period`` seconds instead of 1 second). Please\n    refer to the description of ``divisor`` argument to ``reader_read`` function\n    above for more information.\n\n    Defaults to 1.\n\n  - ``event``\n\n    The ``event`` entry of the resulting table (see ``luastatus`` documentation for the\n    description of ``widget.event`` field).\n"
  },
  {
    "path": "plugins/network-rate-linux/network-rate-linux.lua",
    "content": "--[[\n  Copyright (C) 2015-2025  luastatus developers\n\n  This file is part of luastatus.\n\n  luastatus is free software: you can redistribute it and/or modify\n  it under the terms of the GNU Lesser General Public License as published by\n  the Free Software Foundation, either version 3 of the License, or\n  (at your option) any later version.\n\n  luastatus is distributed in the hope that it will be useful,\n  but WITHOUT ANY WARRANTY; without even the implied warranty of\n  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n  GNU Lesser General Public License for more details.\n\n  You should have received a copy of the GNU Lesser General Public License\n  along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n--]]\n\nlocal P = {}\n\nlocal DEFAULT_PROCPATH = '/proc'\n\nlocal ACCEPT_ANY_IFACE_FILTER = function(_)\n    return true\nend\n\n--[[ /proc/net/dev looks like this:\n\nInter-|   Receive                                                |  Transmit\n face |bytes    packets errs drop fifo frame compressed multicast|bytes    packets errs drop fifo colls carrier compressed\ndocker0:       0       0    0    0    0     0          0         0        0       0    0    0    0     0       0          0\nwlp2s0: 39893402   38711    0    0    0     0          0         0  3676924   27205    0    0    0     0       0          0\n    lo:       0       0    0    0    0     0          0         0        0       0    0    0    0     0       0          0\n--]]\n\nlocal LINE_PATTERN = '^%s*(%S+):' .. ('%s+(%d+)' .. ('%s+%d+'):rep(7)):rep(2) .. '%s*$'\n\nfunction P.reader_new(iface_filter)\n    return {\n        iface_filter = iface_filter or ACCEPT_ANY_IFACE_FILTER,\n        _procpath = DEFAULT_PROCPATH,\n        last_recv = {},\n        last_sent = {},\n    }\nend\n\nlocal function parse_line(line, reader, divisor)\n    local iface, recv_str, sent_str = line:match(LINE_PATTERN)\n    if not (iface and recv_str and sent_str) then\n        return nil, nil\n    end\n\n    if not reader.iface_filter(iface) then\n        return nil, nil\n    end\n\n    local recv = assert(tonumber(recv_str))\n    local sent = assert(tonumber(sent_str))\n\n    local prev_recv, prev_sent = reader.last_recv[iface], reader.last_sent[iface]\n    local datum = nil\n    if prev_recv and prev_sent then\n        local delta_recv = recv - prev_recv\n        local delta_sent = sent - prev_sent\n        if (delta_recv >= 0 and delta_sent >= 0) and (recv > 0 and sent > 0) then\n            datum = {R = delta_recv / divisor, S = delta_sent / divisor}\n        end\n    end\n    reader.last_recv[iface] = recv\n    reader.last_sent[iface] = sent\n    return datum, iface\nend\n\nlocal function do_with_file(f, callback)\n    local is_ok, err = pcall(callback)\n    f:close()\n    if not is_ok then\n        error(err)\n    end\nend\n\nfunction P.reader_read(reader, divisor, in_array_form)\n    local f = assert(io.open(reader._procpath .. '/net/dev', 'r'))\n    local res = {}\n    do_with_file(f, function()\n        for line in f:lines() do\n            if not line:find('|') then -- skip the \"header\" lines\n                local datum, iface = parse_line(line, reader, divisor)\n                if datum then\n                    if in_array_form then\n                        res[#res + 1] = {iface, datum}\n                    else\n                        res[iface] = datum\n                    end\n                end\n            end\n        end\n    end)\n    return res\nend\n\nlocal function mkfilter(x, negate)\n    local match_table = {}\n\n    if type(x) == 'string' then\n        match_table[x] = true\n\n    elseif type(x) == 'table' then\n        if type(next(x)) == 'number' then\n            -- x is an \"array\"\n            for _, v in ipairs(x) do\n                match_table[v] = true\n            end\n        else\n            -- x is a \"dict\" (or empty)\n            for k, v in pairs(x) do\n                if v then\n                    match_table[k] = true\n                end\n            end\n        end\n\n    else\n        error('invalid iface_only/iface_except value (expected string or table)')\n    end\n\n    if negate then\n        return function(iface)\n            return not match_table[iface]\n        end\n    else\n        return function(iface)\n            return match_table[iface]\n        end\n    end\nend\n\nfunction P.widget(tbl)\n    local iface_filter\n    if tbl.iface_only then\n        iface_filter = mkfilter(tbl.iface_only, false)\n    elseif tbl.iface_except then\n        iface_filter = mkfilter(tbl.iface_except, true)\n    else\n        iface_filter = tbl.iface_filter\n    end\n\n    local period = tbl.period or 1\n\n    local reader = P.reader_new(iface_filter)\n    reader._procpath = tbl._procpath or DEFAULT_PROCPATH\n\n    return {\n        plugin = 'timer',\n        opts = {\n            period = period,\n        },\n        cb = function(_)\n            return tbl.cb(P.reader_read(reader, period, tbl.in_array_form))\n        end,\n        event = tbl.event,\n    }\nend\n\nreturn P\n"
  },
  {
    "path": "plugins/pipe/CMakeLists.txt",
    "content": "install (FILES pipe.lua DESTINATION ${LUA_PLUGINS_DIR})\n\nluastatus_add_man_page (README.rst luastatus-plugin-pipe 7)\n"
  },
  {
    "path": "plugins/pipe/README.rst",
    "content": ".. :X-man-page-only: luastatus-plugin-pipe\n.. :X-man-page-only: #####################\n.. :X-man-page-only:\n.. :X-man-page-only: ##########################################\n.. :X-man-page-only: process output reader plugin for luastatus\n.. :X-man-page-only: ##########################################\n.. :X-man-page-only:\n.. :X-man-page-only: :Copyright: LGPLv3\n.. :X-man-page-only: :Manual section: 7\n\n\nOverview\n========\n**NOTE**: this is a legacy plugin. Please use ``pipev2`` plugin instead.\n\nThis derived plugin monitors the output of a process and calls the callback function whenever it\nproduces a line.\n\nFunctions\n=========\nThe following functions are provided:\n\n* ``shell_escape(x)``\n\n  If ``x`` is a string, escapes it as a shell argument; if ``x`` is an array of strings, escapes\n  them and then joins by space.\n\n* ``widget(tbl)``\n\n  Constructs a ``widget`` table required by luastatus. ``tbl`` is a table with the following\n  fields:\n\n  **(required)**\n\n  - ``command``: string\n\n    ``/bin/sh`` command to spawn.\n\n  - ``cb``: function\n\n    The callback that will be called with a line produced by the spawned process each time one\n    is available.\n\n  **(optional)**\n\n  - ``on_eof``: function\n\n    Callback to be called when the spawned process closes its stdout. Default is to simply\n    hang to prevent busy-looping forever.\n\n  - ``event``\n\n    The ``event`` entry of the resulting table (see ``luastatus`` documentation for the\n    description of ``widget.event`` field).\n"
  },
  {
    "path": "plugins/pipe/pipe.lua",
    "content": "--[[\n  Copyright (C) 2015-2025  luastatus developers\n\n  This file is part of luastatus.\n\n  luastatus is free software: you can redistribute it and/or modify\n  it under the terms of the GNU Lesser General Public License as published by\n  the Free Software Foundation, either version 3 of the License, or\n  (at your option) any later version.\n\n  luastatus is distributed in the hope that it will be useful,\n  but WITHOUT ANY WARRANTY; without even the implied warranty of\n  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n  GNU Lesser General Public License for more details.\n\n  You should have received a copy of the GNU Lesser General Public License\n  along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n--]]\n\nlocal P = {}\n\nfunction P.shell_escape(x)\n    if type(x) == 'string' then\n        if x:find('\\0') then\n            error('shell argument contains NUL character')\n        end\n        return \"'\" .. x:gsub(\"'\", \"'\\\\''\") .. \"'\"\n    elseif type(x) == 'table' then\n        local t = {}\n        for _, arg in ipairs(x) do\n            t[#t + 1] = P.shell_escape(arg)\n        end\n        return table.concat(t, ' ')\n    else\n        error('argument type is neither string nor table')\n    end\nend\n\nfunction P.widget(tbl)\n    local f = assert(io.popen(tbl.command, 'r'))\n    return {\n        plugin = 'timer',\n        opts = {period = 0},\n        cb = function()\n            local r = f:read('*line')\n            if r ~= nil then\n                return tbl.cb(r)\n            end\n            if tbl.on_eof ~= nil then\n                tbl.on_eof()\n            else\n                luastatus.plugin.push_period(3600)\n                error('child process has closed its stdout')\n            end\n        end,\n        event = tbl.event,\n    }\nend\n\nreturn P\n"
  },
  {
    "path": "plugins/pipev2/CMakeLists.txt",
    "content": "file (GLOB sources \"*.c\")\nluastatus_add_plugin (\n    plugin-pipev2\n    $<TARGET_OBJECTS:ls>\n    $<TARGET_OBJECTS:moonvisit>\n    ${sources}\n)\n\ntarget_compile_definitions (plugin-pipev2 PUBLIC -D_POSIX_C_SOURCE=200809L)\nluastatus_target_compile_with (plugin-pipev2 LUA)\ntarget_include_directories (plugin-pipev2 PUBLIC \"${PROJECT_SOURCE_DIR}\")\n\n# find pthreads\nset (CMAKE_THREAD_PREFER_PTHREAD TRUE)\nset (THREADS_PREFER_PTHREAD_FLAG TRUE)\nfind_package (Threads REQUIRED)\n# link aginst pthread\ntarget_link_libraries (plugin-pipev2 PUBLIC Threads::Threads)\n\nluastatus_add_man_page (README.rst luastatus-plugin-pipev2 7)\n"
  },
  {
    "path": "plugins/pipev2/README.rst",
    "content": ".. :X-man-page-only: luastatus-plugin-pipev2\n.. :X-man-page-only: #######################\n.. :X-man-page-only:\n.. :X-man-page-only: ##############################\n.. :X-man-page-only: pipe plugin for luastatus (v2)\n.. :X-man-page-only: ##############################\n.. :X-man-page-only:\n.. :X-man-page-only: :Copyright: LGPLv3\n.. :X-man-page-only: :Manual section: 7\n\nOverview\n========\nThis plugin spawns a process and then reads lines (or chunks separated by some other configured delimiter) from its stdout.\n\nIt can optionally redirect the child process' stdin as well.\nThis way, a widget is able to write data into its stdin, thus establishing bidirectional communication.\n\nIt can also send signals to the child process.\n\nOptions\n=======\nThe following options are supported:\n\n* ``argv``: array of string (**required**)\n\n  Program name or path, followed by the arguments.\n\n* ``delimiter``: string\n\n  Delimiter to use for breaking stream of bytes into chunks.\n  Defaults to ``\\n``, which means report text lines.\n  If specified, must be exactly one byte long.\n\n* ``pipe_stdin``: boolean\n\n  Whether or not to rediect the child process' stdin.\n  If enabled, the widget will be able to write data into its stdin, thus establishing bidirectional communication.\n  Defaults to ``false``.\n\n* ``greet``: boolean\n\n  Whether or not to call the callback in the beginning with ``what=\"hello\"``.\n\n* ``bye``: boolean\n\n  Whether or not to call the callback in the end (after the process has died) with ``what=\"bye\"``.\n\n``cb`` argument\n===============\nA table with ``what`` key:\n\n* If it's ``\"hello\"``, then the ``greet`` option was enabled, and the callback is called before any other calls.\n\n* If it's ``\"line\"``, then the child process has just produced a line (or, if a custom delimiter is used, a chunk separated by the delimiter).\n  The ``line`` field of the table contains the line itself.\n\n* If it's ``\"bye\"``, then the ``bye`` option was enabled, and the child process has just died.\n\nFunctions\n=========\nThis plugin provides the following functions:\n\n* ``luastatus.plugin.write_to_stdin(bytes)``\n\n  Writes ``bytes``, which must be a string, into the child process' stdin.\n  Only available if ``pipe_stdin`` option was enabled.\n\n  On success, it returns ``true``.\n  On failure, it returns ``false, err_msg``, where ``err_msg`` is the error message,\n  but see `Notes`_.\n\n* ``luastatus.plugin.kill([signal])``\n\n  Sends a signal to the child process.\n  If ``signal`` is not passed or is ``nil``, SIGTERM is implied.\n  ``signal`` can be either a string with the spelling of a signal (e.g. ``\"SIGUSR1\"``)\n  or a signal number (e.g. 9).\n\n  On success, it returns ``true``.\n  On failure, it returns ``false, err_msg``, where ``err_msg`` is the error message,\n  but see `Notes`_.\n\n  For the list of supported signal spellings, see `Supported signal names`_.\n\n* ``luastatus.plugin.get_sigrt_bounds()``\n\n  Returns ``SIGRTMIN, SIGRTMAX``. Might be useful if you want to send real-time signals\n  and don't want to depend on OS/architecture.\n\nSupported signal names\n----------------------\nAll signals defined by POSIX.1-2008 (without extensions) are supported:\nSIGABRT, SIGALRM, SIGBUS, SIGCHLD, SIGCONT, SIGFPE, SIGHUP, SIGILL, SIGINT, SIGKILL, SIGPIPE,\nSIGQUIT, SIGSEGV, SIGSTOP, SIGTERM, SIGTSTP, SIGTTIN, SIGTTOU, SIGUSR1, SIGUSR2, SIGURG.\n\nNotes\n-----\nIf the child process has not been spawned yet, ``write_to_stdin`` and ``kill`` throw an error\ninstead of returning ``false, err_msg``.\nThis is only possible if the function is being invoked from an event handler.\nSo, if you use this function from an event handler, you should consider this possibility.\n"
  },
  {
    "path": "plugins/pipev2/launch.c",
    "content": "/*\n * Copyright (C) 2026  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#include \"launch.h\"\n#include <stddef.h>\n#include <stdbool.h>\n#include <sys/types.h>\n#include <fcntl.h>\n#include <unistd.h>\n#include <errno.h>\n#include <spawn.h>\n\n#include \"libls/ls_panic.h\"\n#include \"libls/ls_osdep.h\"\n\nstatic inline void unmake_cloexec(int fd)\n{\n    int flags = fcntl(fd, F_GETFD);\n    if (flags < 0) {\n        LS_PANIC_WITH_ERRNUM(\"fcntl (F_GETFD) failed\", errno);\n    }\n    flags &= ~FD_CLOEXEC;\n    if (fcntl(fd, F_SETFD, flags) < 0) {\n        LS_PANIC_WITH_ERRNUM(\"fcntl (F_SETFD) failed\", errno);\n    }\n}\n\ntypedef struct {\n    int fds[2];\n    int their_end;\n} Pipe;\n\n#define PIPE_INIT_EMPTY {.fds = {-1, -1}, .their_end = -1}\n\nstatic inline bool pipe_is_empty(Pipe *p)\n{\n    return p->their_end < 0;\n}\n\nstatic inline int pipe_make(Pipe *p, int their_end)\n{\n    if (ls_cloexec_pipe(p->fds) < 0) {\n        return -1;\n    }\n    p->their_end = their_end;\n    unmake_cloexec(p->fds[their_end]);\n    return 0;\n}\n\nstatic inline int pipe_finalize(Pipe *p)\n{\n    if (pipe_is_empty(p)) {\n        return -1;\n    }\n    close(p->fds[p->their_end]);\n\n    // The following line sets /our_end/ to:\n    //   * 1 if /p->their_end/ is 0;\n    //   * 0 if /p->their_end/ is 1.\n    int our_end = 1 ^ p->their_end;\n\n    return p->fds[our_end];\n}\n\nstatic inline void pipe_destroy(Pipe *p)\n{\n    if (pipe_is_empty(p)) {\n        return;\n    }\n    close(p->fds[0]);\n    close(p->fds[1]);\n}\n\nstatic inline void add_redir(posix_spawn_file_actions_t *fa, Pipe *p)\n{\n    if (pipe_is_empty(p)) {\n        return;\n    }\n\n    int fd_old = p->fds[p->their_end];\n    int fd_new = p->their_end;\n\n    if (fd_old == fd_new) {\n        return;\n    }\n    LS_PTH_CHECK(posix_spawn_file_actions_adddup2(fa, fd_old, fd_new));\n    LS_PTH_CHECK(posix_spawn_file_actions_addclose(fa, fd_old));\n}\n\nextern char **environ;\n\nint launch(const LaunchArg *argv, bool redir_stdin, LaunchResult *res, LaunchError *err)\n{\n    LS_ASSERT(argv != NULL);\n    LS_ASSERT(argv[0] != NULL);\n\n    Pipe p_stdin = PIPE_INIT_EMPTY;\n    Pipe p_stdout = PIPE_INIT_EMPTY;\n\n    if (redir_stdin) {\n        if (pipe_make(&p_stdin, /*their_end=*/ 0) < 0) {\n            err->where = \"pipe\";\n            err->errnum = errno;\n            goto error;\n        }\n    }\n    if (pipe_make(&p_stdout, /*their_end=*/ 1) < 0) {\n        err->where = \"pipe\";\n        err->errnum = errno;\n        goto error;\n    }\n\n    posix_spawn_file_actions_t fa;\n    LS_PTH_CHECK(posix_spawn_file_actions_init(&fa));\n\n    add_redir(&fa, &p_stdin);\n    add_redir(&fa, &p_stdout);\n\n    pid_t pid;\n    int rc = posix_spawnp(\n        /*pid=*/ &pid,\n        /*file=*/ argv[0],\n        /*file_actions=*/ &fa,\n        /*attrp=*/ NULL,\n        /*argv=*/ argv,\n        /*envp=*/ environ\n    );\n\n    LS_PTH_CHECK(posix_spawn_file_actions_destroy(&fa));\n\n    if (rc != 0) {\n        err->where = \"posix_spawnp\";\n        err->errnum = rc;\n        goto error;\n    }\n\n    int fd_stdin = pipe_finalize(&p_stdin);\n    int fd_stdout = pipe_finalize(&p_stdout);\n\n    *res = (LaunchResult) {\n        .pid = pid,\n        .fd_stdin = fd_stdin,\n        .fd_stdout = fd_stdout,\n    };\n    return 0;\n\nerror:\n    pipe_destroy(&p_stdin);\n    pipe_destroy(&p_stdout);\n    return -1;\n}\n"
  },
  {
    "path": "plugins/pipev2/launch.h",
    "content": "/*\n * Copyright (C) 2026  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#pragma once\n\n#include <stdbool.h>\n#include <sys/types.h>\n\ntypedef char *LaunchArg;\n\ntypedef struct {\n    pid_t pid;\n    int fd_stdin;\n    int fd_stdout;\n} LaunchResult;\n\ntypedef struct {\n    const char *where;\n    int errnum;\n} LaunchError;\n\nint launch(const LaunchArg *argv, bool redir_stdin, LaunchResult *res, LaunchError *err);\n"
  },
  {
    "path": "plugins/pipev2/pipev2.c",
    "content": "/*\n * Copyright (C) 2015-2026  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#include <stdlib.h>\n#include <stdio.h>\n#include <stdbool.h>\n#include <signal.h>\n#include <errno.h>\n#include <unistd.h>\n#include <pthread.h>\n#include <sys/types.h>\n#include <lua.h>\n#include <lauxlib.h>\n\n#include \"libls/ls_alloc_utils.h\"\n#include \"libls/ls_panic.h\"\n#include \"libls/ls_io_utils.h\"\n#include \"libls/ls_tls_ebuf.h\"\n\n#include \"include/plugin_v1.h\"\n#include \"include/sayf_macros.h\"\n\n#include \"libmoonvisit/moonvisit.h\"\n\n#include \"launch.h\"\n#include \"utils.h\"\n#include \"sigdb.h\"\n\nenum {\n    PID_NOT_SPAWNED_YET = 0,\n    PID_ALREADY_TERMINATED = -1,\n};\n\ntypedef struct {\n    LaunchArg *data;\n    size_t size;\n    size_t capacity;\n} ArgsList;\n\ntypedef struct {\n    ArgsList argv;\n\n    // The /getdelim()/ function requires the /int delim/ parameter to be \"representable\n    // as an unsigned char\" (i.e. to be non-negative).\n    //\n    // Since the signedness of /char/ depends on the implementation, we store the\n    // delimiter as an /unsigned char/. Then we can pass it to /getdelim()/ without cast.\n    unsigned char delimiter;\n\n    bool pipe_stdin;\n    bool greet;\n    bool bye;\n\n    pthread_mutex_t child_mtx;\n    int child_stdin_fd;\n    pid_t child_pid;\n} Priv;\n\nstatic void args_list_add(ArgsList *x, const char *s)\n{\n    char *new_elem = s ? ls_xstrdup(s) : NULL;\n\n    if (x->size == x->capacity) {\n        x->data = LS_M_X2REALLOC(x->data, &x->capacity);\n    }\n    x->data[x->size++] = new_elem;\n}\n\nstatic void args_list_destroy(ArgsList *x)\n{\n    for (size_t i = 0; i < x->size; ++i) {\n        free(x->data[i]);\n    }\n    free(x->data);\n}\n\nstatic void destroy(LuastatusPluginData *pd)\n{\n    Priv *p = pd->priv;\n\n    args_list_destroy(&p->argv);\n\n    LS_PTH_CHECK(pthread_mutex_destroy(&p->child_mtx));\n\n    ls_close(p->child_stdin_fd);\n\n    free(p);\n}\n\nstatic int visit_argv_elem(MoonVisit *mv, void *ud, int kpos, int vpos)\n{\n    (void) kpos;\n    mv->where = \"'argv' element\";\n    Priv *p = ud;\n\n    if (moon_visit_checktype_at(mv, NULL, vpos, LUA_TSTRING) < 0) {\n        return -1;\n    }\n    const char *s = lua_tostring(mv->L, vpos);\n    args_list_add(&p->argv, s);\n    return 0;\n}\n\nstatic int visit_delimiter(MoonVisit *mv, void *ud, const char *s, size_t ns)\n{\n    Priv *p = ud;\n\n    if (ns != 1) {\n        moon_visit_err(mv, \"length of delimiter: expected 1, found %zu\", ns);\n        return -1;\n    }\n\n    p->delimiter = s[0];\n    return 0;\n}\n\nstatic int init(LuastatusPluginData *pd, lua_State *L)\n{\n    Priv *p = pd->priv = LS_XNEW(Priv, 1);\n    *p = (Priv) {\n        .argv = {0},\n        .delimiter = '\\n',\n        .pipe_stdin = false,\n        .greet = false,\n        .bye = false,\n        .child_stdin_fd = -1,\n        .child_pid = PID_NOT_SPAWNED_YET,\n    };\n    LS_PTH_CHECK(pthread_mutex_init(&p->child_mtx, NULL));\n\n    char errbuf[256];\n    MoonVisit mv = {.L = L, .errbuf = errbuf, .nerrbuf = sizeof(errbuf)};\n\n    // Parse argv\n    if (moon_visit_table_f(&mv, -1, \"argv\", visit_argv_elem, p, false) < 0) {\n        goto mverror;\n    }\n    if (!p->argv.size) {\n        snprintf(errbuf, sizeof(errbuf), \"'argv' array is empty\");\n        goto error;\n    }\n\n    // Parse delimiter\n    if (moon_visit_str_f(&mv, -1, \"delimiter\", visit_delimiter, p, true) < 0) {\n        goto mverror;\n    }\n\n    // Parse pipe_stdin\n    if (moon_visit_bool(&mv, -1, \"pipe_stdin\", &p->pipe_stdin, true) < 0) {\n        goto mverror;\n    }\n\n    // Parse greet\n    if (moon_visit_bool(&mv, -1, \"greet\", &p->greet, true) < 0) {\n        goto mverror;\n    }\n\n    // Parse bye\n    if (moon_visit_bool(&mv, -1, \"bye\", &p->bye, true) < 0) {\n        goto mverror;\n    }\n\n    return LUASTATUS_OK;\n\nmverror:\n    LS_FATALF(pd, \"%s\", errbuf);\nerror:\n    destroy(pd);\n    return LUASTATUS_ERR;\n}\n\nstatic int l_write_to_stdin(lua_State *L)\n{\n    Priv *p = lua_touserdata(L, lua_upvalueindex(1));\n\n    if (!p->pipe_stdin) {\n        return luaL_error(L, \"'pipe_stdin' option was not enabled\");\n    }\n\n    size_t ndata;\n    const char *data = luaL_checklstring(L, 1, &ndata);\n\n    LS_PTH_CHECK(pthread_mutex_lock(&p->child_mtx));\n    int fd = p->child_stdin_fd;\n    LS_PTH_CHECK(pthread_mutex_unlock(&p->child_mtx));\n\n    if (fd < 0) {\n        return luaL_error(L, \"process have not been created yet\");\n    }\n\n    if (utils_full_write(fd, data, ndata) >= 0) {\n        lua_pushboolean(L, 1);\n        return 1;\n    } else {\n        const char *err_descr = ls_tls_strerror(errno);\n        lua_pushboolean(L, 0);\n        lua_pushstring(L, err_descr);\n        return 2;\n    }\n}\n\nstatic int fetch_sig_num(lua_State *L)\n{\n    if (lua_isnoneornil(L, 1)) {\n        return SIGTERM;\n    }\n\n    if (lua_isnumber(L, 1)) {\n        int res = lua_tointeger(L, 1);\n        if (res < 0) {\n            return luaL_argerror(L, 1, \"number is negative or out of range\");\n        }\n        return res;\n\n    } else if (lua_isstring(L, 1)) {\n        const char *sig_name = lua_tostring(L, 1);\n        int res = sigdb_lookup_num_by_name(sig_name);\n        if (res < 0) {\n            return luaL_argerror(L, 1, \"unknown signal name\");\n        }\n        return res;\n\n    } else {\n        return luaL_error(\n            L, \"expected number of string as argument, found %s\",\n            luaL_typename(L, 1));\n    }\n}\n\nstatic int l_kill(lua_State *L)\n{\n    Priv *p = lua_touserdata(L, lua_upvalueindex(1));\n\n    int sig_num = fetch_sig_num(L);\n\n    // If /is_ok/ == 1: killed successfully.\n    // If /is_ok/ == 0: error, error number in /err_num/.\n    // If /is_ok/ == -1: process have not been created yet.\n    int is_ok;\n    int err_num;\n\n    LS_PTH_CHECK(pthread_mutex_lock(&p->child_mtx));\n    pid_t pid = p->child_pid;\n    if (pid > 0) {\n        if (kill(pid, sig_num) < 0) {\n            is_ok = 0;\n        } else {\n            is_ok = 1;\n        }\n        err_num = errno;\n    } else {\n        if (pid == PID_NOT_SPAWNED_YET) {\n            is_ok = -1;\n        } else {\n            is_ok = 0;\n        }\n        err_num = ESRCH;\n    }\n    LS_PTH_CHECK(pthread_mutex_unlock(&p->child_mtx));\n\n    if (is_ok < 0) {\n        return luaL_error(L, \"process have not been created yet\");\n    }\n\n    if (is_ok) {\n        lua_pushboolean(L, 1);\n        return 1;\n    } else {\n        const char *err_descr = ls_tls_strerror(err_num);\n        lua_pushboolean(L, 0);\n        lua_pushstring(L, err_descr);\n        return 2;\n    }\n}\n\nstatic int l_get_sigrt_bounds(lua_State *L)\n{\n    lua_pushinteger(L, SIGRTMIN);\n    lua_pushinteger(L, SIGRTMAX);\n    return 2;\n}\n\nstatic void register_funcs(LuastatusPluginData *pd, lua_State *L)\n{\n    Priv *p = pd->priv;\n\n    // L: table\n\n    lua_pushlightuserdata(L, p); // L: table ud\n    lua_pushcclosure(L, l_write_to_stdin, 1); // L: table func\n    lua_setfield(L, -2, \"write_to_stdin\"); // L: table\n\n    lua_pushlightuserdata(L, p); // L: table ud\n    lua_pushcclosure(L, l_kill, 1); // L: table func\n    lua_setfield(L, -2, \"kill\"); // L: table\n\n    lua_pushcfunction(L, l_get_sigrt_bounds); // L: table func\n    lua_setfield(L, -2, \"get_sigrt_bounds\"); // L: table\n}\n\nstatic void report_reason_of_death(\n    LuastatusPluginData *pd,\n    int wait_rc,\n    int wait_status,\n    int wait_errno)\n{\n    if (wait_rc < 0) {\n        LS_INFOF(pd, \"waitpid: %s\", ls_tls_strerror(wait_errno));\n    } else {\n        if (WIFEXITED(wait_status)) {\n            int exit_code = WEXITSTATUS(wait_status);\n            LS_INFOF(pd, \"child process exited with code %d\", exit_code);\n        } else if (WIFSIGNALED(wait_status)) {\n            int term_sig = WTERMSIG(wait_status);\n            LS_INFOF(pd, \"child process was killed with signal %d\", term_sig);\n        } else {\n            LS_INFOF(pd, \"child process terminated in an unexpected way (%d)\", wait_status);\n        }\n    }\n}\n\nstatic void do_cleanup_leftover(LuastatusPluginData *pd, LaunchResult leftover)\n{\n    LS_INFOF(pd, \"killing the spawned process with SIGKILL and waiting for it to terminate\");\n\n    (void) kill(leftover.pid, SIGKILL);\n\n    int wait_status = 0;\n    int wait_rc = utils_waitpid(leftover.pid, &wait_status);\n    int wait_errno = errno;\n\n    report_reason_of_death(pd, wait_rc, wait_status, wait_errno);\n\n    ls_close(leftover.fd_stdin);\n    ls_close(leftover.fd_stdout);\n}\n\nstatic bool do_spawn(LuastatusPluginData *pd, FILE **out_f)\n{\n    Priv *p = pd->priv;\n\n    args_list_add(&p->argv, NULL);\n\n    LaunchResult res;\n    LaunchError err;\n\n    if (launch(p->argv.data, p->pipe_stdin, &res, &err) < 0) {\n        LS_FATALF(pd, \"cannot spawn process: %s: %s\", err.where, ls_tls_strerror(err.errnum));\n        return false;\n    }\n\n    FILE *f = fdopen(res.fd_stdout, \"r\");\n    if (!f) {\n        LS_FATALF(pd, \"fdopen: %s\", ls_tls_strerror(errno));\n        do_cleanup_leftover(pd, res);\n        return false;\n    }\n    *out_f = f;\n\n    LS_PTH_CHECK(pthread_mutex_lock(&p->child_mtx));\n    p->child_stdin_fd = res.fd_stdin;\n    p->child_pid = res.pid;\n    LS_PTH_CHECK(pthread_mutex_unlock(&p->child_mtx));\n\n    return true;\n}\n\nstatic void do_wait(LuastatusPluginData *pd)\n{\n    Priv *p = pd->priv;\n\n    LS_PTH_CHECK(pthread_mutex_lock(&p->child_mtx));\n\n    int wait_status = 0;\n    int wait_rc = utils_waitpid(p->child_pid, &wait_status);\n    int wait_errno = errno;\n\n    p->child_pid = PID_ALREADY_TERMINATED;\n\n    LS_PTH_CHECK(pthread_mutex_unlock(&p->child_mtx));\n\n    report_reason_of_death(pd, wait_rc, wait_status, wait_errno);\n}\n\nstatic void make_call_simple(\n    LuastatusPluginData *pd,\n    LuastatusPluginRunFuncs funcs,\n    const char *what)\n{\n    lua_State *L = funcs.call_begin(pd->userdata);\n    // L: ?\n    lua_createtable(L, 0, 1); // L: ? table\n    lua_pushstring(L, what); // L: ? table str\n    lua_setfield(L, -2, \"what\"); // L: ? table\n\n    funcs.call_end(pd->userdata);\n}\n\nstatic void make_call_line(\n    LuastatusPluginData *pd,\n    LuastatusPluginRunFuncs funcs,\n    const char *line,\n    size_t nline)\n{\n    lua_State *L = funcs.call_begin(pd->userdata);\n    // L: ?\n    lua_createtable(L, 0, 2); // L: ? table\n\n    lua_pushstring(L, \"line\"); // L: ? table str\n    lua_setfield(L, -2, \"what\"); // L: ? table\n\n    lua_pushlstring(L, line, nline); // L: ? table str\n    lua_setfield(L, -2, \"line\"); // L: ? table\n\n    funcs.call_end(pd->userdata);\n}\n\nstatic void run(LuastatusPluginData *pd, LuastatusPluginRunFuncs funcs)\n{\n    Priv *p = pd->priv;\n\n    FILE *f;\n    if (!do_spawn(pd, &f)) {\n        return;\n    }\n\n    if (p->greet) {\n        make_call_simple(pd, funcs, \"hello\");\n    }\n\n    char *buf = NULL;\n    size_t nbuf = 512;\n\n    for (;;) {\n        ssize_t r = getdelim(&buf, &nbuf, p->delimiter, f);\n        if (r <= 0) {\n            break;\n        }\n        make_call_line(pd, funcs, buf, r - 1);\n    }\n\n    if (feof(f)) {\n        LS_ERRF(pd, \"child process closed its stdout\");\n    } else {\n        LS_ERRF(pd, \"read error: %s\", ls_tls_strerror(errno));\n    }\n\n    fclose(f);\n\n    do_wait(pd);\n\n    if (p->bye) {\n        make_call_simple(pd, funcs, \"bye\");\n        for (;;) {\n            pause();\n        }\n    }\n}\n\nLuastatusPluginIface luastatus_plugin_iface_v1 = {\n    .init = init,\n    .register_funcs = register_funcs,\n    .run = run,\n    .destroy = destroy,\n};\n"
  },
  {
    "path": "plugins/pipev2/sigdb.c",
    "content": "/*\n * Copyright (C) 2026  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#include \"sigdb.h\"\n#include <stddef.h>\n#include <string.h>\n#include <signal.h>\n#include \"libls/ls_panic.h\"\n\ntypedef struct {\n    const char *sig_name;\n    int sig_num;\n} SigSpec;\n\nstatic const SigSpec SIG_SPECS[] = {\n    {\"SIGABRT\", SIGABRT},\n    {\"SIGALRM\", SIGALRM},\n    {\"SIGBUS\",  SIGBUS},\n    {\"SIGCHLD\", SIGCHLD},\n    {\"SIGCONT\", SIGCONT},\n    {\"SIGFPE\",  SIGFPE},\n    {\"SIGHUP\",  SIGHUP},\n    {\"SIGILL\",  SIGILL},\n    {\"SIGINT\",  SIGINT},\n    {\"SIGKILL\", SIGKILL},\n    {\"SIGPIPE\", SIGPIPE},\n    {\"SIGQUIT\", SIGQUIT},\n    {\"SIGSEGV\", SIGSEGV},\n    {\"SIGSTOP\", SIGSTOP},\n    {\"SIGTERM\", SIGTERM},\n    {\"SIGTSTP\", SIGTSTP},\n    {\"SIGTTIN\", SIGTTIN},\n    {\"SIGTTOU\", SIGTTOU},\n    {\"SIGUSR1\", SIGUSR1},\n    {\"SIGUSR2\", SIGUSR2},\n    {\"SIGURG\",  SIGURG},\n    {0},\n};\n\nint sigdb_lookup_num_by_name(const char *sig_name)\n{\n    LS_ASSERT(sig_name != NULL);\n\n    for (const SigSpec *p = SIG_SPECS; p->sig_name; ++p) {\n        if (strcmp(sig_name, p->sig_name) == 0) {\n            return p->sig_num;\n        }\n    }\n    return -1;\n}\n"
  },
  {
    "path": "plugins/pipev2/sigdb.h",
    "content": "/*\n * Copyright (C) 2026  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#pragma once\n\nint sigdb_lookup_num_by_name(const char *sig_name);\n"
  },
  {
    "path": "plugins/pipev2/utils.c",
    "content": "/*\n * Copyright (C) 2026  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#include \"utils.h\"\n#include <stddef.h>\n#include <sys/types.h>\n#include <sys/wait.h>\n#include <unistd.h>\n#include <errno.h>\n#include \"libls/ls_panic.h\"\n\nint utils_full_write(int fd, const char *data, size_t ndata)\n{\n    size_t nwritten = 0;\n    while (nwritten < ndata) {\n        ssize_t w = write(fd, data + nwritten, ndata - nwritten);\n        if (w < 0) {\n            if (errno == EINTR) {\n                continue;\n            }\n            return -1;\n        }\n        nwritten += w;\n    }\n    return 0;\n}\n\nint utils_waitpid(pid_t pid, int *out_status)\n{\n    LS_ASSERT(pid > 0);\n\n    pid_t r;\n    while ((r = waitpid(pid, out_status, 0)) < 0 && errno == EINTR) {\n        // do nothing\n    }\n    return r < 0 ? -1 : 0;\n}\n"
  },
  {
    "path": "plugins/pipev2/utils.h",
    "content": "/*\n * Copyright (C) 2026  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#pragma once\n\n#include <stddef.h>\n#include <sys/types.h>\n\nint utils_full_write(int fd, const char *data, size_t ndata);\n\nint utils_waitpid(pid_t pid, int *out_status);\n"
  },
  {
    "path": "plugins/pulse/CMakeLists.txt",
    "content": "file (GLOB sources \"*.c\")\nluastatus_add_plugin (plugin-pulse $<TARGET_OBJECTS:ls> $<TARGET_OBJECTS:moonvisit> ${sources})\n\ntarget_compile_definitions (plugin-pulse PUBLIC -D_POSIX_C_SOURCE=200809L)\nluastatus_target_compile_with (plugin-pulse LUA)\ntarget_include_directories (plugin-pulse PUBLIC \"${PROJECT_SOURCE_DIR}\")\n\nfind_package (PkgConfig REQUIRED)\npkg_check_modules (PULSE REQUIRED libpulse)\nluastatus_target_build_with (plugin-pulse PULSE)\n\nluastatus_add_man_page (README.rst luastatus-plugin-pulse 7)\n"
  },
  {
    "path": "plugins/pulse/README.rst",
    "content": ".. :X-man-page-only: luastatus-plugin-pulse\n.. :X-man-page-only: ######################\n.. :X-man-page-only:\n.. :X-man-page-only: ###############################\n.. :X-man-page-only: PulseAudio plugin for luastatus\n.. :X-man-page-only: ###############################\n.. :X-man-page-only:\n.. :X-man-page-only: :Copyright: LGPLv3\n.. :X-man-page-only: :Manual section: 7\n\nOverview\n========\nThis plugin monitors the volume and mute status of a PulseAudio sink.\n\nOptions\n=======\nThe following options are supported:\n\n* ``sink``: string\n\n  Sink name; default is ``\"@DEFAULT_SINK@\"``.\n\n* ``make_self_pipe``: boolean\n\n  If ``true``, the ``wake_up()`` function (see `Functions`_) will be available.\n\n``cb`` argument\n===============\n\n* If the ``make_self_pipe`` option is set to ``true``, and the callback is invoked because of the\n  call to ``wake_up()``, then the argument is ``nil``;\n\n* otherwise, the argument is a table with the following entries:\n\n  - ``cur``: integer\n\n    Current volume level.\n\n  - ``norm``: integer\n\n    \"Normal\" (corresponding to 100%) volume level.\n\n  - ``mute``: boolean\n\n    Whether the sink is mute.\n\n  - ``index``: integer\n\n    Index of the default sink.\n\n  - ``name``: string (**optional**, may be ``nil``)\n\n    Name of the default sink.\n\n  - ``desc``: string (**optional**, may be ``nil``)\n\n    Description of the default sink.\n\n  - ``port``: table with following entries, or ``nil`` if there is no active port:\n\n    - ``name``: string (**optional**, may be ``nil``)\n\n      Name of the default sink's active port.\n\n    - ``desc``: string (**optional**, may be ``nil``)\n\n      Description of the default sink's active port.\n\n    - ``type``: string\n\n      Type of the default sink's active port.\n      Possible values are:\n\n      + ``\"unknown\"``\n\n      + ``\"aux\"``\n\n      + ``\"speaker\"``\n\n      + ``\"headphones\"``\n\n      + ``\"line\"``\n\n      + ``\"mic\"``\n\n      + ``\"headset\"``\n\n      + ``\"handset\"``\n\n      + ``\"earpiece\"``\n\n      + ``\"spdif\"``\n\n      + ``\"hdmi\"``\n\n      + ``\"tv\"``\n\n      + ``\"radio\"``\n\n      + ``\"video\"``\n\n      + ``\"usb\"``\n\n      + ``\"bluetooth\"``\n\n      + ``\"portable\"``\n\n      + ``\"handsfree\"``\n\n      + ``\"car\"``\n\n      + ``\"hifi\"``\n\n      + ``\"phone\"``\n\n      + ``\"network\"``\n\n      + ``\"analog\"``\n\nFunctions\n=========\nThe following functions are provided:\n\n* ``luastatus.plugin.wake_up()``\n\n  Forces a call to ``cb`` (with ``nil`` argument).\n\n  Only available if the ``make_self_pipe`` option was set to ``true``; otherwise, it throws an\n  error.\n"
  },
  {
    "path": "plugins/pulse/pulse.c",
    "content": "/*\n * Copyright (C) 2015-2025  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#include <lua.h>\n#include <stdlib.h>\n#include <errno.h>\n#include <stdint.h>\n#include <unistd.h>\n#include <sys/types.h>\n#include <stdbool.h>\n#include <pulse/pulseaudio.h>\n#include <pulse/version.h>\n\n#include \"include/plugin_v1.h\"\n#include \"include/sayf_macros.h\"\n\n#include \"libmoonvisit/moonvisit.h\"\n\n#include \"libls/ls_alloc_utils.h\"\n#include \"libls/ls_tls_ebuf.h\"\n#include \"libls/ls_evloop_lfuncs.h\"\n#include \"libls/ls_time_utils.h\"\n#include \"libls/ls_io_utils.h\"\n#include \"libls/ls_panic.h\"\n\n#ifdef PA_CHECK_VERSION\n# define MY_CHECK_VERSION(A_, B_, C_) PA_CHECK_VERSION(A_, B_, C_)\n#else\n# define MY_CHECK_VERSION(A_, B_, C_) 0\n#endif\n\n// Note: some parts of this file are stolen from i3status' src/pulse.c.\n// This is fine since the BSD 3-Clause licence, under which it is licenced, is compatible with\n// LGPL-3.0.\n\ntypedef struct {\n    char *sink_name;\n    int pipefds[2];\n} Priv;\n\nstatic void destroy(LuastatusPluginData *pd)\n{\n    Priv *p = pd->priv;\n    free(p->sink_name);\n    ls_close(p->pipefds[0]);\n    ls_close(p->pipefds[1]);\n    free(p);\n}\n\nstatic int init(LuastatusPluginData *pd, lua_State *L)\n{\n    Priv *p = pd->priv = LS_XNEW(Priv, 1);\n    *p = (Priv) {\n        .sink_name = NULL,\n        .pipefds = {-1, -1},\n    };\n    char errbuf[256];\n    MoonVisit mv = {.L = L, .errbuf = errbuf, .nerrbuf = sizeof(errbuf)};\n\n    // Parse sink\n    if (moon_visit_str(&mv, -1, \"sink\", &p->sink_name, NULL, true) < 0)\n        goto mverror;\n    if (!p->sink_name)\n        p->sink_name = ls_xstrdup(\"@DEFAULT_SINK@\");\n\n    // Parse make_self_pipe\n    bool mkpipe = false;\n    if (moon_visit_bool(&mv, -1, \"make_self_pipe\", &mkpipe, true) < 0)\n        goto mverror;\n    if (mkpipe) {\n        if (ls_self_pipe_open(p->pipefds) < 0) {\n            LS_FATALF(pd, \"ls_self_pipe_open: %s\", ls_tls_strerror(errno));\n            goto error;\n        }\n    }\n\n    return LUASTATUS_OK;\n\nmverror:\n    LS_FATALF(pd, \"%s\", errbuf);\nerror:\n    destroy(pd);\n    return LUASTATUS_ERR;\n}\n\nstatic void register_funcs(LuastatusPluginData *pd, lua_State *L)\n{\n    Priv *p = pd->priv;\n    // L: table\n    ls_self_pipe_push_luafunc(p->pipefds, L); // L: table func\n    lua_setfield(L, -2, \"wake_up\"); // L: table\n}\n\ntypedef struct {\n    LuastatusPluginData *pd;\n    LuastatusPluginRunFuncs funcs;\n    pa_mainloop *ml;\n    uint32_t sink_idx;\n} UserData;\n\nstatic void self_pipe_cb(\n        pa_mainloop_api *api,\n        pa_io_event *e,\n        int fd,\n        pa_io_event_flags_t events,\n        void *vud)\n{\n    (void) api;\n    (void) e;\n    (void) events;\n\n    char c;\n    ssize_t unused = read(fd, &c, 1);\n    (void) unused;\n\n    UserData *ud = vud;\n    lua_State *L = ud->funcs.call_begin(ud->pd->userdata);\n    lua_pushnil(L);\n    ud->funcs.call_end(ud->pd->userdata);\n}\n\n#if MY_CHECK_VERSION(14, 0, 0)\nstatic void push_port_type(lua_State *L, int type)\n{\n    switch (type) {\n    default:                              lua_pushstring(L, \"unknown\"); break;\n    case PA_DEVICE_PORT_TYPE_UNKNOWN:     lua_pushstring(L, \"unknown\"); break;\n    case PA_DEVICE_PORT_TYPE_AUX:         lua_pushstring(L, \"aux\"); break;\n    case PA_DEVICE_PORT_TYPE_SPEAKER:     lua_pushstring(L, \"speaker\"); break;\n    case PA_DEVICE_PORT_TYPE_HEADPHONES:  lua_pushstring(L, \"headphones\"); break;\n    case PA_DEVICE_PORT_TYPE_LINE:        lua_pushstring(L, \"line\"); break;\n    case PA_DEVICE_PORT_TYPE_MIC:         lua_pushstring(L, \"mic\"); break;\n    case PA_DEVICE_PORT_TYPE_HEADSET:     lua_pushstring(L, \"headset\"); break;\n    case PA_DEVICE_PORT_TYPE_HANDSET:     lua_pushstring(L, \"handset\"); break;\n    case PA_DEVICE_PORT_TYPE_EARPIECE:    lua_pushstring(L, \"earpiece\"); break;\n    case PA_DEVICE_PORT_TYPE_SPDIF:       lua_pushstring(L, \"spdif\"); break;\n    case PA_DEVICE_PORT_TYPE_HDMI:        lua_pushstring(L, \"hdmi\"); break;\n    case PA_DEVICE_PORT_TYPE_TV:          lua_pushstring(L, \"tv\"); break;\n    case PA_DEVICE_PORT_TYPE_RADIO:       lua_pushstring(L, \"radio\"); break;\n    case PA_DEVICE_PORT_TYPE_VIDEO:       lua_pushstring(L, \"video\"); break;\n    case PA_DEVICE_PORT_TYPE_USB:         lua_pushstring(L, \"usb\"); break;\n    case PA_DEVICE_PORT_TYPE_BLUETOOTH:   lua_pushstring(L, \"bluetooth\"); break;\n    case PA_DEVICE_PORT_TYPE_PORTABLE:    lua_pushstring(L, \"portable\"); break;\n    case PA_DEVICE_PORT_TYPE_HANDSFREE:   lua_pushstring(L, \"handsfree\"); break;\n    case PA_DEVICE_PORT_TYPE_CAR:         lua_pushstring(L, \"car\"); break;\n    case PA_DEVICE_PORT_TYPE_HIFI:        lua_pushstring(L, \"hifi\"); break;\n    case PA_DEVICE_PORT_TYPE_PHONE:       lua_pushstring(L, \"phone\"); break;\n    case PA_DEVICE_PORT_TYPE_NETWORK:     lua_pushstring(L, \"network\"); break;\n    case PA_DEVICE_PORT_TYPE_ANALOG:      lua_pushstring(L, \"analog\"); break;\n    }\n}\n#endif\n\nstatic inline void push_str_or_nil(lua_State *L, const char *s)\n{\n    if (s) {\n        lua_pushstring(L, s);\n    } else {\n        lua_pushnil(L);\n    }\n}\n\nstatic void store_volume_from_sink_cb(\n        pa_context *c,\n        const pa_sink_info *info,\n        int eol,\n        void *vud)\n{\n    UserData *ud = vud;\n    if (eol < 0) {\n        if (pa_context_errno(c) == PA_ERR_NOENTITY) {\n            return;\n        }\n        LS_ERRF(ud->pd, \"PulseAudio error: %s\", pa_strerror(pa_context_errno(c)));\n    } else if (eol == 0) {\n        if (info->index == ud->sink_idx) {\n            lua_State *L = ud->funcs.call_begin(ud->pd->userdata);\n            // L: ?\n            lua_createtable(L, 0, 7); // L: ? table\n            lua_pushinteger(L, pa_cvolume_avg(&info->volume)); // L: ? table integer\n            lua_setfield(L, -2, \"cur\"); // L: ? table\n            lua_pushinteger(L, PA_VOLUME_NORM); // L: ? table integer\n            lua_setfield(L, -2, \"norm\"); // L: ? table\n            lua_pushboolean(L, !!info->mute); // L: ? table boolean\n            lua_setfield(L, -2, \"mute\"); // L: ? table\n            lua_pushinteger(L, info->index); // L: ? table integer\n            lua_setfield(L, -2, \"index\"); // L: ? table\n            push_str_or_nil(L, info->name); // L: ? table string\n            lua_setfield(L, -2, \"name\"); // L: ? table\n            push_str_or_nil(L, info->description); // L: ? table string\n            lua_setfield(L, -2, \"desc\"); // L: ? table\n\n#if MY_CHECK_VERSION(0, 9, 16)\n            if (!info->active_port) {\n                lua_pushnil(L); // L: ? table nil\n            } else {\n                lua_createtable(L, 0, 3); // L: ? table table\n                push_str_or_nil(L, info->active_port->name); // L: ? table table string\n                lua_setfield(L, -2, \"name\"); // L: ? table table\n                push_str_or_nil(L, info->active_port->description); // L: ? table table string\n                lua_setfield(L, -2, \"desc\"); // L: ? table table\n# if MY_CHECK_VERSION(14, 0, 0)\n                push_port_type(L, info->active_port->type); // L: ? table table string\n                lua_setfield(L, -2, \"type\"); // L: ? table table\n# endif\n            }\n            lua_setfield(L, -2, \"port\"); // L: ? table\n#endif\n\n            ud->funcs.call_end(ud->pd->userdata);\n        }\n    }\n}\n\nstatic void store_sink_cb(\n        pa_context *c,\n        const pa_sink_info *info,\n        int eol,\n        void *vud)\n{\n    UserData *ud = vud;\n    if (info) {\n        if (ud->sink_idx != info->index) {\n            // sink has changed?\n            ud->sink_idx = info->index;\n            store_volume_from_sink_cb(c, info, eol, vud);\n        }\n    }\n}\n\nstatic void update_sink(pa_context *c, void *vud)\n{\n    UserData *ud = vud;\n    Priv *p = ud->pd->priv;\n\n    pa_operation *o = pa_context_get_sink_info_by_name(c, p->sink_name, store_sink_cb, vud);\n    if (o) {\n        pa_operation_unref(o);\n    } else {\n        LS_ERRF(ud->pd, \"pa_context_get_sink_info_by_name: %s\", pa_strerror(pa_context_errno(c)));\n    }\n}\n\nstatic void subscribe_cb(\n        pa_context *c,\n        pa_subscription_event_type_t t,\n        uint32_t idx,\n        void *vud)\n{\n    UserData *ud = vud;\n\n    if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) != PA_SUBSCRIPTION_EVENT_CHANGE) {\n        return;\n    }\n    pa_subscription_event_type_t facility = t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK;\n    switch (facility) {\n    case PA_SUBSCRIPTION_EVENT_SERVER:\n        // server change event, see if the sink has changed\n        update_sink(c, vud);\n        break;\n    case PA_SUBSCRIPTION_EVENT_SINK:\n        {\n            pa_operation *o = pa_context_get_sink_info_by_index(\n                c, idx, store_volume_from_sink_cb, vud);\n\n            if (o) {\n                pa_operation_unref(o);\n            } else {\n                LS_ERRF(ud->pd, \"pa_context_get_sink_info_by_index: %s\",\n                        pa_strerror(pa_context_errno(c)));\n            }\n        }\n        break;\n    default:\n        break;\n    }\n}\n\nstatic void context_state_cb(pa_context *c, void *vud)\n{\n    UserData *ud = vud;\n    switch (pa_context_get_state(c)) {\n    case PA_CONTEXT_UNCONNECTED:\n    case PA_CONTEXT_CONNECTING:\n    case PA_CONTEXT_AUTHORIZING:\n    case PA_CONTEXT_SETTING_NAME:\n    case PA_CONTEXT_TERMINATED:\n    default:\n        break;\n\n    case PA_CONTEXT_READY:\n        {\n            pa_context_set_subscribe_callback(c, subscribe_cb, vud);\n            update_sink(c, vud);\n            pa_operation *o = pa_context_subscribe(\n                c, PA_SUBSCRIPTION_MASK_SINK | PA_SUBSCRIPTION_MASK_SERVER, NULL, NULL);\n\n            if (o) {\n                pa_operation_unref(o);\n            } else {\n                LS_ERRF(ud->pd, \"pa_context_subscribe: %s\", pa_strerror(pa_context_errno(c)));\n            }\n        }\n        break;\n\n    case PA_CONTEXT_FAILED:\n        // server disconnected us\n        pa_mainloop_quit(ud->ml, 1);\n        break;\n    }\n}\n\nstatic bool iteration(LuastatusPluginData *pd, LuastatusPluginRunFuncs funcs)\n{\n    bool ret = false;\n    Priv *p = pd->priv;\n    UserData ud = {.pd = pd, .funcs = funcs, .sink_idx = UINT32_MAX};\n    pa_mainloop_api *api = NULL;\n    pa_context *ctx = NULL;\n    pa_io_event *pipe_ev = NULL;\n\n    if (!(ud.ml = pa_mainloop_new())) {\n        LS_FATALF(pd, \"pa_mainloop_new() failed\");\n        goto error;\n    }\n    if (!(api = pa_mainloop_get_api(ud.ml))) {\n        LS_FATALF(pd, \"pa_mainloop_get_api() failed\");\n        goto error;\n    }\n    pa_proplist *proplist = pa_proplist_new();\n    pa_proplist_sets(proplist, PA_PROP_APPLICATION_NAME, \"luastatus-plugin-pulse\");\n    pa_proplist_sets(proplist, PA_PROP_APPLICATION_ID, \"io.github.shdown.luastatus\");\n    pa_proplist_sets(proplist, PA_PROP_APPLICATION_VERSION, \"0.0.1\");\n    ctx = pa_context_new_with_proplist(api, \"luastatus-plugin-pulse\", proplist);\n    pa_proplist_free(proplist);\n    if (!ctx) {\n        LS_FATALF(pd, \"pa_context_new_with_proplist() failed\");\n        goto error;\n    }\n\n    pa_context_set_state_callback(ctx, context_state_cb, &ud);\n    if (pa_context_connect(ctx, NULL, PA_CONTEXT_NOFAIL | PA_CONTEXT_NOAUTOSPAWN, NULL) < 0) {\n        LS_FATALF(pd, \"pa_context_connect: %s\", pa_strerror(pa_context_errno(ctx)));\n        goto error;\n    }\n\n    if (p->pipefds[0] >= 0) {\n        pipe_ev = api->io_new(api, p->pipefds[0], PA_IO_EVENT_INPUT, self_pipe_cb, &ud);\n        if (!pipe_ev) {\n            LS_FATALF(pd, \"io_new() failed\");\n            goto error;\n        }\n    }\n\n    ret = true;\n\n    int ignored;\n    if (pa_mainloop_run(ud.ml, &ignored) < 0) {\n        LS_FATALF(pd, \"pa_mainloop_run: %s\", pa_strerror(pa_context_errno(ctx)));\n        goto error;\n    }\n\nerror:\n    if (pipe_ev) {\n        LS_ASSERT(api != NULL);\n        api->io_free(pipe_ev);\n    }\n    if (ctx) {\n        pa_context_unref(ctx);\n    }\n    if (ud.ml) {\n        pa_mainloop_free(ud.ml);\n    }\n    return ret;\n}\n\nstatic void run(LuastatusPluginData *pd, LuastatusPluginRunFuncs funcs)\n{\n    for (;;) {\n        if (!iteration(pd, funcs)) {\n            ls_sleep_simple(5.0);\n        }\n    }\n}\n\nLuastatusPluginIface luastatus_plugin_iface_v1 = {\n    .init = init,\n    .register_funcs = register_funcs,\n    .run = run,\n    .destroy = destroy,\n};\n"
  },
  {
    "path": "plugins/systemd-unit/CMakeLists.txt",
    "content": "install (FILES systemd-unit.lua DESTINATION ${LUA_PLUGINS_DIR})\n\nluastatus_add_man_page (README.rst luastatus-plugin-systemd-unit 7)\n"
  },
  {
    "path": "plugins/systemd-unit/README.rst",
    "content": ".. :X-man-page-only: luastatus-plugin-systemd-unit\n.. :X-man-page-only: #############################\n.. :X-man-page-only:\n.. :X-man-page-only: ##################################################\n.. :X-man-page-only: Systemd unit state monitoring plugin for luastatus\n.. :X-man-page-only: ##################################################\n.. :X-man-page-only:\n.. :X-man-page-only: :Copyright: LGPLv3\n.. :X-man-page-only: :Manual section: 7\n\nOverview\n========\n\nThis derived plugin monitors the state of a systemd unit.\n\nFunctions\n=========\n\nThe following functions are provided:\n\n* ``subscribe()``\n\n  Subscribes to the systemd D-Bus manager to receive unit state change notifications.\n\n* ``get_unit_path_by_unit_name(unit_name)``\n\n  Returns the D-Bus object path corresponding to the specified systemd unit name.\n\n* ``get_state_by_unit_path(unit_path)``\n\n  Returns the ``ActiveState`` property of the unit located at the given D-Bus object path.\n\n* ``get_state_by_unit_name(unit_name)``\n\n  Returns the current ``ActiveState`` propety for the specified systemd unit name.\n  This is just a combination of ``get_state_by_unit_path`` and ``get_unit_path_by_unit_name``.\n\n* ``widget(tbl)``\n\n  Constructs a ``widget`` table required by luastatus. ``tbl`` must be a table with the following fields:\n\n  **(required)**\n\n  - ``unit_name``: string\n\n    The name of the systemd unit to monitor (e.g., ``\"tor.service\"``).\n\n  - ``cb``: function\n\n    The callback function that will be called with the current unit state as a string.\n\n  **(optional)**\n\n  - ``no_throw``: boolean\n\n    If set to ``true``, errors encountered while fetching the unit state will be caught,\n    a warning will be printed to the console, and ``cb`` will be called with ``nil``\n    instead of propagating the error. Defaults to ``false``.\n\n  - ``forced_refresh_interval``: number\n\n    The timeout interval in seconds for forced state checks when D-Bus signals are not\n    received. Defaults to ``30``.\n\n  - ``event``\n\n    The ``event`` entry of the resulting table (see ``luastatus`` documentation for the\n    description of ``widget.event`` field).\n"
  },
  {
    "path": "plugins/systemd-unit/systemd-unit.lua",
    "content": "--[[\n  Copyright (C) 2026  luastatus developers\n\n  This file is part of luastatus.\n\n  luastatus is free software: you can redistribute it and/or modify\n  it under the terms of the GNU Lesser General Public License as published by\n  the Free Software Foundation, either version 3 of the License, or\n  (at your option) any later version.\n\n  luastatus is distributed in the hope that it will be useful,\n  but WITHOUT ANY WARRANTY; without even the implied warranty of\n  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n  GNU Lesser General Public License for more details.\n\n  You should have received a copy of the GNU Lesser General Public License\n  along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n--]]\n\nlocal function unwrap1_into_str(x)\n    assert(type(x) == 'table')\n    assert(#x == 1)\n    local res = x[1]\n    assert(type(res) == 'string')\n    return res\nend\n\nlocal P = {}\n\nfunction P.subscribe()\n    local is_ok, res = luastatus.plugin.call_method_str({\n        bus = 'system',\n        dest = 'org.freedesktop.systemd1',\n        object_path = '/org/freedesktop/systemd1',\n        interface = 'org.freedesktop.systemd1.Manager',\n        method = 'Subscribe',\n        -- no \"arg_str\" parameter\n    })\n    assert(is_ok, res)\nend\n\nfunction P.get_unit_path_by_unit_name(unit_name)\n    local is_ok, res = luastatus.plugin.call_method_str({\n        bus = 'system',\n        dest = 'org.freedesktop.systemd1',\n        object_path = '/org/freedesktop/systemd1',\n        interface = 'org.freedesktop.systemd1.Manager',\n        method = 'GetUnit',\n        arg_str = unit_name,\n    })\n    assert(is_ok, res)\n    return unwrap1_into_str(res)\nend\n\nfunction P.get_state_by_unit_path(unit_path)\n    local is_ok, res = luastatus.plugin.get_property({\n        bus = 'system',\n        dest = 'org.freedesktop.systemd1',\n        object_path = unit_path,\n        interface = 'org.freedesktop.systemd1.Unit',\n        property_name = 'ActiveState',\n    })\n    assert(is_ok, res)\n    return unwrap1_into_str(res)\nend\n\nfunction P.get_state_by_unit_name(unit_name)\n    local unit_path = P.get_unit_path_by_unit_name(unit_name)\n    return P.get_state_by_unit_path(unit_path)\nend\n\nlocal function print_warning(unit_name, err)\n    print(string.format(\n        'WARNING: luastatus: systemd-unit plugin: unable to get state of unit \"%s\": %s',\n        unit_name, tostring(err)\n    ))\nend\n\nlocal is_subscribed = false\n\nfunction P.widget(tbl)\n    assert(type(tbl.unit_name) == 'string')\n\n    return {\n        plugin = 'dbus',\n        opts = {\n            signals = {\n                {\n                    bus = 'system',\n                    interface = 'org.freedesktop.DBus.Properties',\n                    signal = 'PropertiesChanged',\n                    arg0 = 'org.freedesktop.systemd1.Unit',\n                },\n                {\n                    bus = 'system',\n                    interface = 'org.freedesktop.systemd1.Manager',\n                    signal = 'UnitFilesChanged',\n                    arg0 = 'org.freedesktop.systemd1.Unit',\n                },\n            },\n            timeout = tbl.forced_refresh_interval or 30,\n            report_when_ready = true,\n        },\n        cb = function(_)\n            if not is_subscribed then\n                P.subscribe()\n                is_subscribed = true\n            end\n\n            if tbl.no_throw then\n                local is_ok, res_or_err = pcall(P.get_state_by_unit_name, tbl.unit_name)\n                if is_ok then\n                    return tbl.cb(res_or_err)\n                else\n                    print_warning(tbl.unit_name, res_or_err)\n                    return tbl.cb(nil)\n                end\n            else\n                return tbl.cb(P.get_state_by_unit_name(tbl.unit_name))\n            end\n        end,\n    }\nend\n\nreturn P\n"
  },
  {
    "path": "plugins/temperature-linux/CMakeLists.txt",
    "content": "install (FILES temperature-linux.lua DESTINATION ${LUA_PLUGINS_DIR})\n\nluastatus_add_man_page (README.rst luastatus-plugin-temperature-linux 7)\n"
  },
  {
    "path": "plugins/temperature-linux/README.rst",
    "content": ".. :X-man-page-only: luastatus-plugin-temperature-linux\n.. :X-man-page-only: ##################################\n.. :X-man-page-only:\n.. :X-man-page-only: ######################################################\n.. :X-man-page-only: Linux-specific temperature sensor plugin for luastatus\n.. :X-man-page-only: ######################################################\n.. :X-man-page-only:\n.. :X-man-page-only: :Copyright: LGPLv3\n.. :X-man-page-only: :Manual section: 7\n\nOverview\n========\nThis derived plugin periodically polls Linux ``sysfs`` for the current\nreadings of various temperature sensors.\n\nFunctions\n=========\nThe following functions are provided:\n\n* ``get_temps(data)``\n\n  Returns either an array or nil.\n  If the result is nil, no data is currently available (the most likely reason for this is\n  that the set of temperature sensors has changed; if so, the subsequent calls will\n  return non-nil).\n\n  Each array entry corresponds to a subsequent temperature sensor; it is a table with the following fields:\n\n  * ``kind`` (string): kind of this temperature sensor; either ``\"thermal\"`` (``/sys/class/thermal/thermal_zone*``\n    sensors) or ``\"hwmon\"`` (``/sys/class/hwmon/*`` sensors).\n  * ``name`` (string): name of this temperature sensor; it is either:\n    - for ``\"thermal\"`` kind: ``\"thermal_zone${i}\"``, where ``${i}`` is the index of the thermal zone, or;\n    - for ``\"hwmon\"`` kind: the contents of ``/sys/class/hwmon/hwmon${i}/name`` file, where ``${i}`` is the index of hwmon sensor.\n  * ``path`` (string): full path to the sensor readings file.\n  * ``value`` (number): current temperature, in Celsius degrees.\n\n  The array is sorted first by kind (``\"thermal\"`` entries go first), then by sensor index (``${i}`` above).\n\n  In order to use this function, you are expected to maintain a table ``data``, initially empty,\n  and pass it to ``get_temps`` each time.\n\n  The only things the caller is allowed to do with this table (either before the first call or after a call) are:\n\n  - set ``please_reload`` field to ``true``; if ``please_reload`` field is true when\n    this function is called, the function will forcefully reload all information and reset\n    ``please_reload`` field to ``nil``.\n\n  - set ``filter_func`` field to a function that takes two string arguments (kind and name), and returns\n    a boolean indicating whether this sensor should be monitored. If not set, all sensors will be monitored.\n\n* ``widget(tbl[, data])``\n\n  Constructs a ``widget`` table required by luastatus.\n\n  If ``data`` is specified, it must be an empty table; you can set ``please_reload`` field\n  in this table to force a full reload. On each reported event, before a call to ``tbl.cb``,\n  this field is reset to ``nil``.\n\n  It is technically possible to set ``filter_func`` field of the ``data`` table to a filter function (see above),\n  but it is recommended to set it via ``tbl.filter_func`` field (instead of ``data.filter_func``).\n\n  ``tbl`` is a table with the following fields:\n\n  **(required)**\n\n  - ``cb``: a function\n\n    The callback function that will be called with current readings of temperature sensors, or with ``nil``.\n    The argument is the same as the return value of ``get_temps`` (see above for description\n    of ``get_temps`` function for more information).\n\n  **(optional)**\n\n  - ``filter_func``: function\n\n    This must be a function that takes two string arguments (kind and name; see the description of the\n    ``get_temps`` function above for what they mean), and returns a boolean indicating whether this sensor\n    should be monitored. If not specified, all sensors will be monitored.\n\n  - ``timer_opts``: table\n\n    Options for the underlying ``timer`` plugin.\n\n  - ``event``\n\n    The ``event`` entry of the resulting table (see ``luastatus`` documentation for the\n    description of ``widget.event`` field).\n"
  },
  {
    "path": "plugins/temperature-linux/temperature-linux.lua",
    "content": "-- ======================================================\n-- ================= sensor_list stuff  =================\n-- ======================================================\n\nlocal function sensor_list_new(kind)\n    return {kind = kind, entries = nil}\nend\n\nlocal function sensor_list_is_null(SL)\n    return SL.entries == nil\nend\n\nlocal function sensor_list_nullify(SL)\n    SL.entries = nil\nend\n\nlocal function do_with_file(f, callback)\n    local is_ok, err = pcall(callback)\n    f:close()\n    if not is_ok then\n        error(err)\n    end\nend\n\nlocal function sensor_list_realize(SL, filter_func, cmd)\n    local f = assert(io.popen(cmd))\n    local entries = {}\n    do_with_file(f, function()\n        for line in f:lines() do\n            local name, sort_by, path = line:match('^(%S+)\\t(%S+)\\t(%S+)$')\n            if name then\n                if (not filter_func) or filter_func(SL.kind, name) then\n                    entries[#entries + 1] = {name = name, sort_by = sort_by, path = path}\n                end\n            end\n        end\n    end)\n\n    -- numeric sort\n    local function extract_first_number(s)\n        return tonumber(s:match('[0-9]+') or '-1')\n    end\n    table.sort(entries, function(a, b)\n        return extract_first_number(a.sort_by) < extract_first_number(b.sort_by)\n    end)\n\n    SL.entries = entries\nend\n\nlocal function sensor_list_fetch_data(SL, into)\n    assert(SL.entries)\n    for _, entry in ipairs(SL.entries) do\n        local f = io.open(entry.path, 'r')\n        if not f then\n            return false\n        end\n        local value = f:read('*number')\n        f:close()\n        if not value then\n            return false\n        end\n        value = value / 1000\n        into[#into + 1] = {\n            kind  = SL.kind,\n            name  = entry.name,\n            path  = entry.path,\n            value = value,\n        }\n    end\n    return true\nend\n\n-- ======================================================\n-- ================= data stuff  ========================\n-- ======================================================\n\nlocal function escape_or_return_default(custom, default)\n    if not custom then\n        return default\n    end\n    if custom:find('\\0') then\n        error('custom path contains NUL character')\n    end\n    return string.format([['%s']], custom:gsub([[']], [['\\'']]))\nend\n\nlocal function data_realize_sensor_lists(data)\n    sensor_list_nullify(data.SL_thermal)\n    sensor_list_nullify(data.SL_hwmon)\n\n    local thermal_path_escaped = escape_or_return_default(data._thermal_path, '/sys/class/thermal')\n    local hwmon_path_escaped = escape_or_return_default(data._hwmon_path, '/sys/class/hwmon')\n\n    sensor_list_realize(data.SL_thermal, data.filter_func, [[\nD=]] .. thermal_path_escaped .. [[;\ncd -- \"$D\" || exit $?\nfor dir in thermal_zone*; do\n    [ -e \"$dir\"/temp ] || continue\n    printf \"%s\\t%s\\t%s\\n\" \"$dir\" \"$dir\" \"$D/$dir/temp\"\ndone\n]])\n\n    sensor_list_realize(data.SL_hwmon, data.filter_func, [[\nD=]] .. hwmon_path_escaped .. [[;\ncd -- \"$D\" || exit $?\nfor dir in *; do\n    [ -e \"$dir\"/name ] || continue\n    IFS= read -r monitor_name < \"$dir\"/name || continue\n    for f in \"$dir\"/temp*_input; do\n        [ -e \"$f\" ] || continue\n        printf \"%s\\t%s\\t%s\\n\" \"$monitor_name\" \"${f##*/}\" \"$D/$f\"\n    done\ndone\n]])\nend\n\nlocal function data_is_ready(data)\n    if sensor_list_is_null(data.SL_thermal) then\n        return false\n    end\n    if sensor_list_is_null(data.SL_hwmon) then\n        return false\n    end\n    return true\nend\n\nlocal function data_fetch_temps(data)\n    local r = {}\n    if not sensor_list_fetch_data(data.SL_thermal, r) then\n        return nil\n    end\n    if not sensor_list_fetch_data(data.SL_hwmon, r) then\n        return nil\n    end\n    return r\nend\n\n-- ======================================================\n-- ================= plugin interface ===================\n-- ======================================================\n\nlocal P = {}\n\nfunction P.get_temps(data)\n    if not data.SL_thermal then\n        data.SL_thermal = sensor_list_new('thermal')\n        data.SL_hwmon = sensor_list_new('hwmon')\n    end\n\n    if data.please_reload or (not data_is_ready(data)) then\n        data_realize_sensor_lists(data)\n        data.please_reload = nil\n    end\n\n    local r = data_fetch_temps(data)\n    if not r then\n        data_realize_sensor_lists(data)\n        return nil\n    end\n    return r\nend\n\nfunction P.widget(tbl, data)\n    data = data or {}\n\n    if tbl.filter_func then\n        data.filter_func = tbl.filter_func\n    end\n\n    return {\n        plugin = 'timer',\n        opts = tbl.timer_opts,\n        cb = function()\n            return tbl.cb(P.get_temps(data))\n        end,\n        event = tbl.event,\n    }\nend\n\nreturn P\n"
  },
  {
    "path": "plugins/timer/CMakeLists.txt",
    "content": "file (GLOB sources \"*.c\")\nluastatus_add_plugin (\n    plugin-timer\n    $<TARGET_OBJECTS:ls>\n    $<TARGET_OBJECTS:moonvisit>\n    $<TARGET_OBJECTS:procalive>\n    ${sources}\n)\n\ntarget_compile_definitions (plugin-timer PUBLIC -D_POSIX_C_SOURCE=200809L)\nluastatus_target_compile_with (plugin-timer LUA)\ntarget_include_directories (plugin-timer PUBLIC \"${PROJECT_SOURCE_DIR}\")\n\nluastatus_add_man_page (README.rst luastatus-plugin-timer 7)\n"
  },
  {
    "path": "plugins/timer/README.rst",
    "content": ".. :X-man-page-only: luastatus-plugin-timer\n.. :X-man-page-only: ######################\n.. :X-man-page-only:\n.. :X-man-page-only: ##########################\n.. :X-man-page-only: timer plugin for luastatus\n.. :X-man-page-only: ##########################\n.. :X-man-page-only:\n.. :X-man-page-only: :Copyright: LGPLv3\n.. :X-man-page-only: :Manual section: 7\n\nOverview\n========\nThis plugin is a simple timer, plus a wake-up FIFO can be specified.\n\nOptions\n=======\nThe following options are supported:\n\n* ``period``: number\n\n  A number of seconds to sleep before calling ``cb`` again. May be fractional. Defaults to 1.\n\n* ``fifo``: string\n\n  Path to an existent FIFO. The plugin does not create FIFO itself. To force a wake-up,\n  ``touch(1)`` the FIFO, that is, open it for writing and then close.\n\n* ``make_self_pipe``: boolean\n\n  If true, the ``wake_up()`` (see the `Functions`_ section) function will be available. Defaults to\n  false.\n\n``cb`` argument\n===============\nA string describing why the function is called:\n\n* if it is ``\"hello\"``, the function is called for the first time;\n\n* if it is ``\"timeout\"``, the function hasn't been called for ``period`` seconds;\n\n* if it is ``\"fifo\"``, the FIFO has been touched;\n\n* if it is ``\"self_pipe\"``, the ``luastatus.plugin.wake_up()`` function has been called.\n\nFunctions\n=========\nThe following functions are provided:\n\n* ``luastatus.plugin.push_period(seconds)``\n\n  Changes the timer period for one iteration.\n\n* ``luastatus.plugin.wake_up()``\n\n  Forces a call to ``cb``.\n\n  Only available if the ``make_self_pipe`` option was set to ``true``; otherwise, it throws an\n  error.\n\nThe following functions are provided as a part of \"procalive\" function set.\nThese functions are available in plugins, including this one, that can be used\nto watch the state of some process(es):\n\n* ``is_ok, err_msg = luastatus.plugin.access(path)``\n\n  Checks if a given path exists, as if with ``access(path, F_OK)``.\n  If it does exist, returns ``true, nil``. If it does not, returns\n  ``false, nil``. If an error occurs, returns ``false, err_msg``.\n\n* ``file_type, err_msg = luastatus.plugin.stat(path)``\n\n  Tries to get the type of the file at the given path. On success returns\n  either of: ``\"regular\"``, ``\"dir\"`` (directory), ``\"chardev\"`` (character device),\n  ``\"blockdev\"`` (block device), ``\"fifo\"``, ``\"symlink\"``, ``\"socket\"``, ``\"other\"``.\n  On failure returns ``nil, err_msg``.\n\n* ``arr, err_msg = luastatus.plugin.glob(pattern)``\n\n  Performs glob expansion of ``pattern``.\n  A glob is a wildcard pattern like ``/tmp/*.txt`` that can be applied as\n  a filter to a list of existing file names. Supported expansions are\n  ``*``, ``?`` and ``[...]``. Please refer to ``glob(7)`` for more information\n  on wildcard patterns.\n\n  Note also that the globbing is performed with ``GLOB_MARK`` flag, so that\n  in output, directories have trailing slash appended to their name.\n\n  Returns ``arr, nil`` on success, where ``arr`` is an array of strings; these\n  are existing file names that matched the given pattern. The order is arbitrary.\n  On failure, returns ``nil, err_msg``.\n\n* ``is_alive = is_process_alive(pid)``\n\n  Checks if a process with PID ``pid`` is currently alive. ``pid`` must be either\n  a number or a string.\n  Returns a boolean that indicates whether the process is alive.\n"
  },
  {
    "path": "plugins/timer/timer.c",
    "content": "/*\n * Copyright (C) 2015-2025  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#include <lua.h>\n#include <errno.h>\n#include <stdlib.h>\n#include <unistd.h>\n#include <stdbool.h>\n#include <poll.h>\n#include <sys/types.h>\n\n#include \"include/plugin_v1.h\"\n#include \"include/sayf_macros.h\"\n\n#include \"libmoonvisit/moonvisit.h\"\n\n#include \"libls/ls_alloc_utils.h\"\n#include \"libls/ls_tls_ebuf.h\"\n#include \"libls/ls_fifo_device.h\"\n#include \"libls/ls_evloop_lfuncs.h\"\n#include \"libls/ls_io_utils.h\"\n#include \"libls/ls_time_utils.h\"\n#include \"libprocalive/procalive_lfuncs.h\"\n\ntypedef struct {\n    double period;\n    char *fifo;\n    LS_PushedTimeout pushed_tmo;\n    int self_pipe[2];\n} Priv;\n\nstatic void destroy(LuastatusPluginData *pd)\n{\n    Priv *p = pd->priv;\n\n    free(p->fifo);\n\n    ls_pushed_timeout_destroy(&p->pushed_tmo);\n\n    ls_close(p->self_pipe[0]);\n    ls_close(p->self_pipe[1]);\n\n    free(p);\n}\n\nstatic int init(LuastatusPluginData *pd, lua_State *L)\n{\n    Priv *p = pd->priv = LS_XNEW(Priv, 1);\n    *p = (Priv) {\n        .period = 1.0,\n        .fifo = NULL,\n        .self_pipe = {-1, -1},\n    };\n    ls_pushed_timeout_init(&p->pushed_tmo);\n\n    char errbuf[256];\n    MoonVisit mv = {.L = L, .errbuf = errbuf, .nerrbuf = sizeof(errbuf)};\n\n    // Parse period\n    if (moon_visit_num(&mv, -1, \"period\", &p->period, true) < 0) {\n        goto mverror;\n    }\n    if (!ls_double_is_good_time_delta(p->period)) {\n        LS_FATALF(pd, \"period is invalid\");\n        goto error;\n    }\n\n    // Parse fifo\n    if (moon_visit_str(&mv, -1, \"fifo\", &p->fifo, NULL, true) < 0) {\n        goto mverror;\n    }\n\n    // Parse make_self_pipe\n    bool mkpipe = false;\n    if (moon_visit_bool(&mv, -1, \"make_self_pipe\", &mkpipe, true) < 0) {\n        goto mverror;\n    }\n    if (mkpipe) {\n        if (ls_self_pipe_open(p->self_pipe) < 0) {\n            LS_FATALF(pd, \"ls_self_pipe_open: %s\", ls_tls_strerror(errno));\n            goto error;\n        }\n    }\n\n    return LUASTATUS_OK;\n\nmverror:\n    LS_FATALF(pd, \"%s\", errbuf);\nerror:\n    destroy(pd);\n    return LUASTATUS_ERR;\n}\n\nstatic void register_funcs(LuastatusPluginData *pd, lua_State *L)\n{\n    // L: table\n    procalive_lfuncs_register_all(L); // L: table\n\n    Priv *p = pd->priv;\n    ls_pushed_timeout_push_luafunc(&p->pushed_tmo, L); // L: table func\n    lua_setfield(L, -2, \"push_period\"); // L: table\n\n    ls_self_pipe_push_luafunc(p->self_pipe, L); // L: table func\n    lua_setfield(L, -2, \"wake_up\"); // L: table\n}\n\nstatic inline void make_call(\n    LuastatusPluginData *pd,\n    LuastatusPluginRunFuncs funcs,\n    const char *what)\n{\n    lua_State *L = funcs.call_begin(pd->userdata);\n    lua_pushstring(L, what);\n    funcs.call_end(pd->userdata);\n}\n\nstatic void run(LuastatusPluginData *pd, LuastatusPluginRunFuncs funcs)\n{\n    Priv *p = pd->priv;\n\n    LS_FifoDevice dev = ls_fifo_device_new();\n\n    LS_TimeDelta default_tmo = ls_double_to_TD_or_die(p->period);\n\n    make_call(pd, funcs, \"hello\");\n\n    for (;;) {\n        if (ls_fifo_device_open(&dev, p->fifo) < 0) {\n            LS_WARNF(pd, \"ls_fifo_device_open: %s: %s\", p->fifo, ls_tls_strerror(errno));\n        }\n        LS_TimeDelta TD = ls_pushed_timeout_fetch(&p->pushed_tmo, default_tmo);\n\n        struct pollfd pfds[2] = {\n            {.fd = dev.fd,          .events = POLLIN},\n            {.fd = p->self_pipe[0], .events = POLLIN},\n        };\n        int r = ls_poll(pfds, 2, TD);\n        if (r < 0) {\n            LS_FATALF(pd, \"ls_poll: %s\", ls_tls_strerror(errno));\n            goto error;\n        } else if (r == 0) {\n            make_call(pd, funcs, \"timeout\");\n        } else {\n            if (pfds[0].revents) {\n                make_call(pd, funcs, \"fifo\");\n                ls_fifo_device_reset(&dev);\n\n            } else if (pfds[1].revents) {\n                char dummy;\n                ssize_t nread = read(p->self_pipe[0], &dummy, 1);\n                (void) nread;\n\n                make_call(pd, funcs, \"self_pipe\");\n            }\n        }\n    }\n\nerror:\n    ls_fifo_device_close(&dev);\n}\n\nLuastatusPluginIface luastatus_plugin_iface_v1 = {\n    .init = init,\n    .register_funcs = register_funcs,\n    .run = run,\n    .destroy = destroy,\n};\n"
  },
  {
    "path": "plugins/udev/CMakeLists.txt",
    "content": "file (GLOB sources \"*.c\")\nluastatus_add_plugin (plugin-udev $<TARGET_OBJECTS:ls> $<TARGET_OBJECTS:moonvisit> ${sources})\n\ntarget_compile_definitions (plugin-udev PUBLIC -D_POSIX_C_SOURCE=200809L)\nluastatus_target_compile_with (plugin-udev LUA)\ntarget_include_directories (plugin-udev PUBLIC \"${PROJECT_SOURCE_DIR}\")\n\nfind_package (PkgConfig REQUIRED)\npkg_check_modules (UDEV REQUIRED libudev)\nluastatus_target_build_with (plugin-udev UDEV)\n\nluastatus_add_man_page (README.rst luastatus-plugin-udev 7)\n"
  },
  {
    "path": "plugins/udev/README.rst",
    "content": ".. :X-man-page-only: luastatus-plugin-udev\n.. :X-man-page-only: #####################\n.. :X-man-page-only:\n.. :X-man-page-only: #########################\n.. :X-man-page-only: udev plugin for luastatus\n.. :X-man-page-only: #########################\n.. :X-man-page-only:\n.. :X-man-page-only: :Copyright: LGPLv3\n.. :X-man-page-only: :Manual section: 7\n\nOverview\n========\nThis plugin monitors udev events.\n\nOptions\n=======\nThe following options are supported:\n\n* ``subsystem``: string\n\n  Subsystem to monitor events from. Optional.\n\n* ``devtype``: string\n\n  Only report events with this device type. Optional.\n\n* ``tag``: string\n\n  Only report events with this tag. Optional.\n\n* ``kernel_events``: boolean\n\n  Monitor kernel uevents, not udev ones. Defaults to false.\n\n* ``greet``: boolean\n\n  Whether or not to call ``cb`` with ``what=\"hello\"`` as soon as the widget starts. Defaults to\n  false.\n\n* ``timeout``: number\n\n  If specified and not negative, this plugin calls ``cb`` with ``what=\"timeout\"`` if no event has\n  occurred in ``timeout`` seconds.\n\n``cb`` argument\n===============\nA table with ``what`` entry:\n\n* if it is ``\"hello\"``, the function is being called for the first time (only if the ``greet``\n  option was set to ``true``);\n\n* if it is ``\"timeout\"``, the function has not been called for the number of seconds specified as\n  the ``timeout`` option;\n\n* if it is ``\"event\"``, a udev event has occurred; in this case, the table has the following\n  additional entries (all are optional strings):\n\n  - ``syspath``;\n\n  - ``sysname``;\n\n  - ``sysnum``;\n\n  - ``devpath``;\n\n  - ``devnode``;\n\n  - ``devtype``;\n\n  - ``subsystem``;\n\n  - ``driver``;\n\n  - ``action``.\n\nFunctions\n=========\nThe following functions are provided:\n\n* ``luastatus.plugin.push_timeout(seconds)``\n\n  Changes the timeout for one iteration.\n"
  },
  {
    "path": "plugins/udev/udev.c",
    "content": "/*\n * Copyright (C) 2015-2025  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#include <lua.h>\n#include <errno.h>\n#include <stdbool.h>\n#include <stdlib.h>\n#include <libudev.h>\n\n#include \"include/plugin_v1.h\"\n#include \"include/sayf_macros.h\"\n\n#include \"libmoonvisit/moonvisit.h\"\n\n#include \"libls/ls_alloc_utils.h\"\n#include \"libls/ls_tls_ebuf.h\"\n#include \"libls/ls_io_utils.h\"\n#include \"libls/ls_evloop_lfuncs.h\"\n#include \"libls/ls_time_utils.h\"\n\ntypedef struct {\n    char *subsystem;\n    char *devtype;\n    char *tag;\n    bool kernel_ev;\n    bool greet;\n    double tmo;\n    LS_PushedTimeout pushed_tmo;\n} Priv;\n\nstatic void destroy(LuastatusPluginData *pd)\n{\n    Priv *p = pd->priv;\n    free(p->subsystem);\n    free(p->devtype);\n    free(p->tag);\n    ls_pushed_timeout_destroy(&p->pushed_tmo);\n    free(p);\n}\n\nstatic int init(LuastatusPluginData *pd, lua_State *L)\n{\n    Priv *p = pd->priv = LS_XNEW(Priv, 1);\n    *p = (Priv) {\n        .subsystem = NULL,\n        .devtype = NULL,\n        .tag = NULL,\n        .kernel_ev = false,\n        .greet = false,\n        .tmo = -1,\n    };\n    ls_pushed_timeout_init(&p->pushed_tmo);\n\n    char errbuf[256];\n    MoonVisit mv = {.L = L, .errbuf = errbuf, .nerrbuf = sizeof(errbuf)};\n\n    // Parse subsystem\n    if (moon_visit_str(&mv, -1, \"subsystem\", &p->subsystem, NULL, true) < 0)\n        goto mverror;\n\n    // Parse devtype\n    if (moon_visit_str(&mv, -1, \"devtype\", &p->devtype, NULL, true) < 0)\n        goto mverror;\n\n    // Parse tag\n    if (moon_visit_str(&mv, -1, \"tag\", &p->tag, NULL, true) < 0)\n        goto mverror;\n\n    // Parse kernel_events\n    if (moon_visit_bool(&mv, -1, \"kernel_events\", &p->kernel_ev, true) < 0)\n        goto mverror;\n\n    // Parse timeout\n    if (moon_visit_num(&mv, -1, \"timeout\", &p->tmo, true) < 0)\n        goto mverror;\n\n    // Parse greet\n    if (moon_visit_bool(&mv, -1, \"greet\", &p->greet, true) < 0)\n        goto mverror;\n\n    return LUASTATUS_OK;\n\nmverror:\n    LS_FATALF(pd, \"%s\", errbuf);\n//error:\n    destroy(pd);\n    return LUASTATUS_ERR;\n}\n\nstatic void register_funcs(LuastatusPluginData *pd, lua_State *L)\n{\n    Priv *p = pd->priv;\n    // L: table\n    ls_pushed_timeout_push_luafunc(&p->pushed_tmo, L); // L: table func\n    lua_setfield(L, -2, \"push_timeout\"); // L: table\n}\n\nstatic inline void report_status(\n        LuastatusPluginData *pd,\n        LuastatusPluginRunFuncs funcs,\n        const char *status)\n{\n    lua_State *L = funcs.call_begin(pd->userdata);\n    lua_createtable(L, 0, 1); // L: table\n    lua_pushstring(L, status); // L: table string\n    lua_setfield(L, -2, \"what\"); // L: table\n    funcs.call_end(pd->userdata);\n}\n\nstatic void report_event(\n        LuastatusPluginData *pd,\n        LuastatusPluginRunFuncs funcs,\n        struct udev_device *dev)\n{\n    typedef struct {\n        const char *key;\n        const char * (*func)(struct udev_device *);\n    } Property;\n\n    static const Property proplist[] = {\n        {\"syspath\", udev_device_get_syspath},\n        {\"sysname\", udev_device_get_sysname},\n        {\"sysnum\", udev_device_get_sysnum},\n        {\"devpath\", udev_device_get_devpath},\n        {\"devnode\", udev_device_get_devnode},\n        {\"devtype\", udev_device_get_devtype},\n        {\"subsystem\", udev_device_get_subsystem},\n        {\"driver\", udev_device_get_driver},\n        {\"action\", udev_device_get_action},\n        {0},\n    };\n\n    lua_State *L = funcs.call_begin(pd->userdata);\n    lua_createtable(L, 0, 4); // L: table\n\n    lua_pushstring(L, \"event\"); // L: table string\n    lua_setfield(L, -2, \"what\"); // L: table\n\n    for (const Property *p = proplist; p->key; ++p) {\n        const char *val = p->func(dev);\n        if (val) {\n            lua_pushstring(L, val); // L: table value\n            lua_setfield(L, -2, p->key); // L: table\n        }\n    }\n\n    funcs.call_end(pd->userdata);\n}\n\nstatic void run(LuastatusPluginData *pd, LuastatusPluginRunFuncs funcs)\n{\n    Priv *p = pd->priv;\n\n    struct udev *udev = udev_new();\n    if (!udev) {\n        LS_FATALF(pd, \"udev_new() failed\");\n        return;\n    }\n\n    struct udev_monitor *mon = udev_monitor_new_from_netlink(\n        udev,\n        p->kernel_ev ? \"kernel\" : \"udev\");\n    if (!mon) {\n        LS_FATALF(pd, \"udev_monitor_new_from_netlink() failed\");\n        goto error;\n    }\n\n    int rc;\n\n    if (p->subsystem) {\n        // udev_monitor_filter_add_match_subsystem_devtype() allows the second argument\n        // to be NULL, but not the first one.\n        rc = udev_monitor_filter_add_match_subsystem_devtype(mon, p->subsystem, p->devtype);\n        if (rc < 0) {\n            LS_FATALF(pd, \"udev_monitor_filter_add_match_subsystem_devtype() failed (%d)\", rc);\n            goto error;\n        }\n    }\n\n    if (p->tag) {\n        rc = udev_monitor_filter_add_match_tag(mon, p->tag);\n        if (rc < 0) {\n            LS_FATALF(pd, \"udev_monitor_filter_add_match_tag() failed (%d)\", rc);\n            goto error;\n        }\n    }\n\n    rc = udev_monitor_enable_receiving(mon);\n    if (rc < 0) {\n        LS_FATALF(pd, \"udev_monitor_enable_receiving() failed (%d)\", rc);\n        goto error;\n    }\n\n    int fd = udev_monitor_get_fd(mon);\n    if (fd < 0) {\n        LS_FATALF(pd, \"udev_monitor_get_fd() failed (%d)\", fd);\n        goto error;\n    }\n\n    if (p->greet) {\n        report_status(pd, funcs, \"hello\");\n    }\n\n    LS_TimeDelta default_tmo = ls_double_to_TD(p->tmo, LS_TD_FOREVER);\n    while (1) {\n        LS_TimeDelta tmo = ls_pushed_timeout_fetch(&p->pushed_tmo, default_tmo);\n        int r = ls_wait_input_on_fd(fd, tmo);\n        if (r < 0) {\n            LS_FATALF(pd, \"ls_wait_input_on_fd: %s\", ls_tls_strerror(errno));\n            goto error;\n        } else if (r == 0) {\n            report_status(pd, funcs, \"timeout\");\n        } else {\n            struct udev_device *dev = udev_monitor_receive_device(mon);\n            if (!dev) {\n                // what the...?\n                continue;\n            }\n            report_event(pd, funcs, dev);\n            udev_device_unref(dev);\n        }\n    }\nerror:\n    if (mon) {\n        udev_monitor_unref(mon);\n    }\n    udev_unref(udev);\n}\n\nLuastatusPluginIface luastatus_plugin_iface_v1 = {\n    .init = init,\n    .register_funcs = register_funcs,\n    .run = run,\n    .destroy = destroy,\n};\n"
  },
  {
    "path": "plugins/unixsock/CMakeLists.txt",
    "content": "file (GLOB sources \"*.c\")\nluastatus_add_plugin (plugin-unixsock $<TARGET_OBJECTS:ls> $<TARGET_OBJECTS:moonvisit> ${sources})\n\ninclude (CheckSymbolExists)\nset (CMAKE_REQUIRED_DEFINITIONS \"-D_GNU_SOURCE\")\ncheck_symbol_exists (accept4 \"sys/socket.h\" HAVE_GNU_ACCEPT4)\nconfigure_file (\"probes.in.h\" \"probes.generated.h\")\n\ntarget_compile_definitions (plugin-unixsock PUBLIC -D_POSIX_C_SOURCE=200809L)\nluastatus_target_compile_with (plugin-unixsock LUA)\ntarget_include_directories (plugin-unixsock PUBLIC \"${PROJECT_SOURCE_DIR}\" \"${CMAKE_CURRENT_BINARY_DIR}\")\n\nluastatus_add_man_page (README.rst luastatus-plugin-unixsock 7)\n"
  },
  {
    "path": "plugins/unixsock/README.rst",
    "content": ".. :X-man-page-only: luastatus-plugin-unixsock\n.. :X-man-page-only: #########################\n.. :X-man-page-only:\n.. :X-man-page-only: #######################################\n.. :X-man-page-only: UNIX domain socket plugin for luastatus\n.. :X-man-page-only: #######################################\n.. :X-man-page-only:\n.. :X-man-page-only: :Copyright: LGPLv3\n.. :X-man-page-only: :Manual section: 7\n\nOverview\n========\nThis plugin acts as a UNIX domain socket server (STREAM, not DGRAM or SEQPACKET), accepting\nincoming connections and making a call when a client produces a line.\n\nIn order to have it make a call, connect to the UNIX socket and send a line to it.\nFrom a command line, this can be done with, e.g.::\n\n    echo some_data | socat stdio UNIX-CONNECT:/tmp/my-unix-socket\n\n(This is an experimental plugin; it is not enabled by default.)\n\nOptions\n=======\nThe following options are supported:\n\n* ``path``: string\n\n  Filesystem path to the UNIX domain socket. This option is required.\n\n* ``try_unlink``: boolean\n\n  Whether or not to try to unlink (remove) the socket file before creating a server.\n  Defaults to true.\n\n* ``timeout``: number\n\n  If specified and not negative, this plugin calls ``cb`` with ``what=\"timeout\"`` if no client\n  has produced a line in ``timeout`` seconds.\n\n* ``greet``: boolean\n\n  Whether or not to call ``cb`` with ``what=\"hello\"`` as soon as the plugin starts. Defaults to\n  false.\n\n* ``max_concur_conns``: number\n\n  Specifies the maximum number of concurrent connections: once the number of connected clients\n  reaches this value, others will be forced to wait in a queue.\n\n  Please do not use this option unless you know what you are doing: too many concurrent connections\n  will eat up file descriptors from the whole luastatus process.\n\n  Defaults to 5, which is more than enough if the clients are not buggy.\n\n``cb`` argument\n===============\nA table with ``what`` entry:\n\n* if it is ``\"hello\"``, the function is being called for the first time (only if the ``greet``\n  option was set to ``true``);\n\n* if it is ``\"timeout\"``, the function has not been called for the number of seconds specified as\n  the ``timeout`` option;\n\n* if it is ``\"line\"``, a client has produced a line; in this case, the table also has ``line``\n  entry with string value.\n\nFunctions\n=========\nThe following functions are provided:\n\n* ``luastatus.plugin.push_timeout(seconds)``\n\n  Changes the timeout for one iteration.\n"
  },
  {
    "path": "plugins/unixsock/cloexec_accept.c",
    "content": "/*\n * Copyright (C) 2015-2025  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#define _GNU_SOURCE\n\n#include \"cloexec_accept.h\"\n#include \"probes.generated.h\"\n\n#include \"libls/ls_io_utils.h\"\n\n#include <sys/socket.h>\n#include <stddef.h>\n\nint cloexec_accept(int sockfd)\n{\n#if HAVE_GNU_ACCEPT4\n    return accept4(sockfd, NULL, NULL, SOCK_CLOEXEC);\n#else\n    int fd = accept(sockfd, NULL, NULL);\n    if (fd >= 0) {\n        ls_make_cloexec(fd);\n    }\n    return fd;\n#endif\n}\n"
  },
  {
    "path": "plugins/unixsock/cloexec_accept.h",
    "content": "/*\n * Copyright (C) 2015-2025  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifndef cloexec_accept_h_\n#define cloexec_accept_h_\n\nint cloexec_accept(int sockfd);\n\n#endif\n"
  },
  {
    "path": "plugins/unixsock/probes.in.h",
    "content": "#ifndef luastatus_plugin_unixsock_probes_h_\n#define luastatus_plugin_unixsock_probes_h_\n\n#cmakedefine01 HAVE_GNU_ACCEPT4\n\n#endif\n"
  },
  {
    "path": "plugins/unixsock/server.c",
    "content": "/*\n * Copyright (C) 2015-2026  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#include \"server.h\"\n\n#include <stdlib.h>\n#include <stdbool.h>\n#include <string.h>\n#include <poll.h>\n#include <errno.h>\n#include <unistd.h>\n#include <sys/types.h>\n\n#include \"libls/ls_alloc_utils.h\"\n#include \"libls/ls_panic.h\"\n#include \"libls/ls_io_utils.h\"\n#include \"libls/ls_time_utils.h\"\n#include \"libls/ls_string.h\"\n\n#include \"cloexec_accept.h\"\n\nenum { NCHUNK = 1024 };\n\ntypedef struct {\n    int fd;\n    LS_String buf;\n} Client;\n\nstruct Server {\n    int srv_fd;\n    size_t max_clients;\n\n    size_t nclients;\n    struct pollfd *pfds;\n    Client *clients;\n};\n\nServer *server_new(\n        int fd,\n        size_t max_clients)\n{\n    LS_ASSERT(fd >= 0);\n    LS_ASSERT(max_clients <= SERVER_MAX_CLIENTS_LIMIT);\n\n    ls_make_nonblock(fd);\n\n    Server *S = LS_XNEW(Server, 1);\n    *S = (Server) {\n        .srv_fd = fd,\n        .max_clients = max_clients,\n        .nclients = 0,\n        .pfds = LS_XNEW(struct pollfd, 1),\n        .clients = NULL,\n    };\n    return S;\n}\n\nstatic inline bool is_full(Server *S)\n{\n    LS_ASSERT(S->nclients <= S->max_clients);\n\n    return S->nclients == S->max_clients;\n}\n\nint server_poll(\n        Server *S,\n        LS_TimeDelta timeout,\n        struct pollfd **out,\n        size_t *out_n,\n        bool *out_can_accept)\n{\n    struct pollfd *pfds = S->pfds;\n\n    pfds[0] = (struct pollfd) {\n        .fd = is_full(S) ? -1 : S->srv_fd,\n        .events = POLLIN,\n    };\n\n    int rc = ls_poll(pfds, S->nclients + 1, timeout);\n    if (rc < 0) {\n        return -1;\n    }\n\n    *out = pfds + 1;\n    *out_n = S->nclients;\n    *out_can_accept = (pfds[0].revents != 0);\n    return rc == 0 ? 0 : 1;\n}\n\nstatic inline bool is_dropped(Client *c)\n{\n    return c->fd < 0;\n}\n\nstatic void drop_client(Client *c)\n{\n    LS_ASSERT(!is_dropped(c));\n\n    close(c->fd);\n    c->fd = -1;\n\n    ls_string_free(c->buf);\n}\n\nstatic inline bool find_newline(Client *c, size_t num_last_read)\n{\n    LS_ASSERT(num_last_read != 0);\n    LS_ASSERT(num_last_read <= c->buf.size);\n\n    const char *new_chunk = c->buf.data + c->buf.size - num_last_read;\n    return memchr(new_chunk, '\\n', num_last_read) != NULL;\n}\n\nint server_read_from_client(\n        Server *S,\n        size_t idx)\n{\n    LS_ASSERT(idx < S->nclients);\n    Client *c = &S->clients[idx];\n    struct pollfd *pfd = &S->pfds[idx + 1];\n\n    LS_ASSERT(!is_dropped(c));\n\n    if (!pfd->revents) {\n        return 0;\n    }\n\n    ls_string_ensure_avail(&c->buf, NCHUNK);\n    ssize_t r = read(c->fd, c->buf.data + c->buf.size, NCHUNK);\n    if (r < 0) {\n        if (LS_IS_EAGAIN(errno)) {\n            return 0;\n        }\n        return -1;\n\n    } else if (r == 0) {\n        errno = 0;\n        return -1;\n    }\n\n    c->buf.size += r;\n\n    if (find_newline(c, r)) {\n        return 1;\n    }\n    return 0;\n}\n\nconst char *server_get_full_line(\n        Server *S,\n        size_t idx,\n        size_t *out_len)\n{\n    LS_ASSERT(idx < S->nclients);\n\n    Client *c = &S->clients[idx];\n\n    const char *data = c->buf.data;\n    size_t ndata = c->buf.size;\n\n    LS_ASSERT(ndata != 0);\n\n    const char *newline = memchr(data, '\\n', ndata);\n    LS_ASSERT(newline != NULL);\n\n    *out_len = newline - data;\n    return data;\n}\n\nvoid server_drop_client(\n        Server *S,\n        size_t idx)\n{\n    LS_ASSERT(idx < S->nclients);\n\n    Client *c = &S->clients[idx];\n    drop_client(c);\n}\n\nstatic void set_nclients(Server *S, size_t new_n)\n{\n    if (S->nclients == new_n) {\n        return;\n    }\n    S->clients = LS_M_XREALLOC(S->clients, new_n);\n    S->pfds = LS_M_XREALLOC(S->pfds, new_n + 1);\n    S->nclients = new_n;\n}\n\nstatic void update_pfds(Server *S)\n{\n    for (size_t i = 0; i < S->nclients; ++i) {\n        Client *c = &S->clients[i];\n        LS_ASSERT(!is_dropped(c));\n\n        S->pfds[i + 1] = (struct pollfd) {\n            .fd = c->fd,\n            .events = POLLIN,\n        };\n    }\n}\n\nstatic void add_client(Server *S, int fd)\n{\n    set_nclients(S, S->nclients + 1);\n    S->clients[S->nclients - 1] = (Client) {\n        .fd = fd,\n        .buf = ls_string_new_reserve(NCHUNK),\n    };\n    update_pfds(S);\n}\n\nint server_accept_new_client(Server *S)\n{\n    LS_ASSERT(S->pfds[0].revents != 0);\n\n    int client_fd = cloexec_accept(S->srv_fd);\n    if (client_fd < 0) {\n        if (LS_IS_EAGAIN(errno) || errno == ECONNABORTED) {\n            return 0;\n        }\n        return -1;\n    }\n\n    ls_make_nonblock(client_fd);\n\n    add_client(S, client_fd);\n    return 1;\n}\n\nvoid server_compact(Server *S)\n{\n    size_t j = 0;\n    for (size_t i = 0; i < S->nclients; ++i) {\n        if (!is_dropped(&S->clients[i])) {\n            S->clients[j] = S->clients[i];\n            ++j;\n        }\n    }\n\n    set_nclients(S, j);\n    update_pfds(S);\n}\n\nvoid server_destroy(Server *S)\n{\n    close(S->srv_fd);\n\n    for (size_t i = 0; i < S->nclients; ++i) {\n        if (!is_dropped(&S->clients[i])) {\n            drop_client(&S->clients[i]);\n        }\n    }\n\n    free(S->clients);\n    free(S->pfds);\n    free(S);\n}\n"
  },
  {
    "path": "plugins/unixsock/server.h",
    "content": "/*\n * Copyright (C) 2026  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifndef server_h_\n#define server_h_\n\n#include <stddef.h>\n#include <stdbool.h>\n#include <poll.h>\n\n#include \"libls/ls_time_utils.h\"\n\nenum {\n    SERVER_MAX_CLIENTS_LIMIT = 1024 * 1024 * 1024,\n};\n\nstruct Server;\ntypedef struct Server Server;\n\nServer *server_new(\n        int fd,\n        size_t max_clients);\n\nint server_poll(\n        Server *S,\n        LS_TimeDelta timeout,\n        struct pollfd **out,\n        size_t *out_n,\n        bool *out_can_accept);\n\nint server_read_from_client(\n        Server *S,\n        size_t idx);\n\nconst char *server_get_full_line(\n        Server *S,\n        size_t idx,\n        size_t *out_len);\n\nvoid server_drop_client(\n        Server *S,\n        size_t idx);\n\n// After this call, pollfd's returned from /server_poll/ are invalidated; it is\n// invalid to use them anymore.\nint server_accept_new_client(Server *S);\n\nvoid server_compact(Server *S);\n\nvoid server_destroy(Server *S);\n\n#endif\n"
  },
  {
    "path": "plugins/unixsock/unixsock.c",
    "content": "/*\n * Copyright (C) 2015-2025  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#include <lua.h>\n#include <stdlib.h>\n#include <string.h>\n#include <stdbool.h>\n#include <stdint.h>\n#include <errno.h>\n#include <unistd.h>\n#include <poll.h>\n#include <sys/socket.h>\n#include <sys/un.h>\n\n#include \"include/plugin_v1.h\"\n#include \"include/sayf_macros.h\"\n\n#include \"libmoonvisit/moonvisit.h\"\n\n#include \"libls/ls_alloc_utils.h\"\n#include \"libls/ls_evloop_lfuncs.h\"\n#include \"libls/ls_tls_ebuf.h\"\n#include \"libls/ls_time_utils.h\"\n#include \"libls/ls_io_utils.h\"\n#include \"libls/ls_osdep.h\"\n\n#include \"server.h\"\n\ntypedef struct {\n    char *path;\n    bool try_unlink;\n    bool greet;\n    uint64_t max_clients;\n    double tmo;\n    LS_TimeDelta tmo_as_TD;\n    LS_PushedTimeout pushed_tmo;\n} Priv;\n\nstatic void destroy(LuastatusPluginData *pd)\n{\n    Priv *p = pd->priv;\n    free(p->path);\n    ls_pushed_timeout_destroy(&p->pushed_tmo);\n    free(p);\n}\n\nstatic int init(LuastatusPluginData *pd, lua_State *L)\n{\n    Priv *p = pd->priv = LS_XNEW(Priv, 1);\n    *p = (Priv) {\n        .path = NULL,\n        .try_unlink = true,\n        .greet = false,\n        .max_clients = 5,\n        .tmo = -1,\n    };\n    ls_pushed_timeout_init(&p->pushed_tmo);\n\n    char errbuf[256];\n    MoonVisit mv = {.L = L, .errbuf = errbuf, .nerrbuf = sizeof(errbuf)};\n\n    // Parse path\n    if (moon_visit_str(&mv, -1, \"path\", &p->path, NULL, false) < 0)\n        goto mverror;\n    if (p->path[0] == '\\0') {\n        LS_FATALF(pd, \"path is empty\");\n        goto error;\n    }\n\n    // Parse try_unlink\n    if (moon_visit_bool(&mv, -1, \"try_unlink\", &p->try_unlink, true) < 0)\n        goto mverror;\n\n    // Parse greet\n    if (moon_visit_bool(&mv, -1, \"greet\", &p->greet, true) < 0)\n        goto mverror;\n\n    // Parse max_concur_conns\n    if (moon_visit_uint(&mv, -1, \"max_concur_conns\", &p->max_clients, true) < 0) {\n        goto mverror;\n    }\n    if (!p->max_clients) {\n        LS_FATALF(pd, \"max_concur_conns is zero\");\n        goto error;\n    }\n    if (p->max_clients > SERVER_MAX_CLIENTS_LIMIT) {\n        LS_FATALF(pd, \"max_concur_conns is too big (limit is %d)\", (int) SERVER_MAX_CLIENTS_LIMIT);\n        goto error;\n    }\n\n    // Parse timeout\n    if (moon_visit_num(&mv, -1, \"timeout\", &p->tmo, true) < 0) {\n        goto mverror;\n    }\n    p->tmo_as_TD = ls_double_to_TD(p->tmo, LS_TD_FOREVER);\n\n    return LUASTATUS_OK;\n\nmverror:\n    LS_FATALF(pd, \"%s\", errbuf);\nerror:\n    destroy(pd);\n    return LUASTATUS_ERR;\n}\n\nstatic void register_funcs(LuastatusPluginData *pd, lua_State *L)\n{\n    Priv *p = pd->priv;\n    // L: table\n    ls_pushed_timeout_push_luafunc(&p->pushed_tmo, L); // L: table func\n    lua_setfield(L, -2, \"push_timeout\"); // L: table\n}\n\nstatic inline LS_TimeStamp new_deadline(Priv *p)\n{\n    LS_TimeDelta tmo = ls_pushed_timeout_fetch(&p->pushed_tmo, p->tmo_as_TD);\n    if (ls_TD_is_forever(tmo)) {\n        return LS_TS_BAD;\n    }\n    return ls_TS_plus_TD(ls_now(), tmo);\n}\n\nstatic inline LS_TimeDelta get_time_until_TS(LS_TimeStamp TS)\n{\n    if (ls_TS_is_bad(TS)) {\n        return LS_TD_FOREVER;\n    }\n    return ls_TS_minus_TS_nonneg(TS, ls_now());\n}\n\nstatic void report_status(\n        LuastatusPluginData *pd,\n        LuastatusPluginRunFuncs funcs,\n        const char *what)\n{\n    lua_State *L = funcs.call_begin(pd->userdata);\n    lua_createtable(L, 0, 1); // L: table\n    lua_pushstring(L, what); // L: table what\n    lua_setfield(L, -2, \"what\"); // L: table\n    funcs.call_end(pd->userdata);\n}\n\nstatic void report_line(\n        LuastatusPluginData *pd,\n        LuastatusPluginRunFuncs funcs,\n        const char *line,\n        size_t nline)\n{\n    lua_State *L = funcs.call_begin(pd->userdata);\n    lua_createtable(L, 0, 2); // L: table\n    lua_pushstring(L, \"line\"); // L: table str\n    lua_setfield(L, -2, \"what\"); // L: table\n    lua_pushlstring(L, line, nline); // L: table str\n    lua_setfield(L, -2, \"line\"); // L: table\n    funcs.call_end(pd->userdata);\n}\n\nstatic int mk_server(LuastatusPluginData *pd)\n{\n    Priv *p = pd->priv;\n    int fd = ls_cloexec_socket(AF_UNIX, SOCK_STREAM, 0);\n\n    if (fd < 0) {\n        LS_FATALF(pd, \"ls_cloexec_socket: %s\", ls_tls_strerror(errno));\n        goto error;\n    }\n    struct sockaddr_un saun = {.sun_family = AF_UNIX};\n\n    if (p->try_unlink) {\n        if (unlink(p->path) < 0 && errno != ENOENT) {\n            LS_WARNF(pd, \"unlink: %s: %s\", p->path, ls_tls_strerror(errno));\n        }\n    }\n\n    size_t npath = strlen(p->path);\n    if (npath + 1 > sizeof(saun.sun_path)) {\n        LS_FATALF(pd, \"socket path is too long\");\n        goto error;\n    }\n    memcpy(saun.sun_path, p->path, npath + 1);\n\n    if (bind(fd, (struct sockaddr *) &saun, sizeof(saun)) < 0) {\n        LS_FATALF(pd, \"bind: %s\", ls_tls_strerror(errno));\n        goto error;\n    }\n\n    if (listen(fd, SOMAXCONN) < 0) {\n        LS_FATALF(pd, \"listen: %s\", ls_tls_strerror(errno));\n        goto error;\n    }\n\n    return fd;\n\nerror:\n    ls_close(fd);\n    return -1;\n}\n\nstatic void run(LuastatusPluginData *pd, LuastatusPluginRunFuncs funcs)\n{\n    Priv *p = pd->priv;\n\n    Server *S = NULL;\n    int srv_fd = mk_server(pd);\n\n    if (srv_fd < 0) {\n        goto error;\n    }\n    S = server_new(srv_fd, p->max_clients);\n\n    LS_TimeStamp deadline = new_deadline(p);\n\n    if (p->greet) {\n        report_status(pd, funcs, \"hello\");\n        deadline = new_deadline(p);\n    }\n\n    for (;;) {\n        LS_TimeDelta tmo = get_time_until_TS(deadline);\n\n        struct pollfd *pfds;\n        size_t pfds_num;\n        bool can_accept;\n\n        int poll_rc = server_poll(S, tmo, &pfds, &pfds_num, &can_accept);\n        if (poll_rc < 0) {\n            LS_FATALF(pd, \"poll: %s\", ls_tls_strerror(errno));\n            goto error;\n        }\n\n        if (poll_rc == 0) {\n            report_status(pd, funcs, \"timeout\");\n            deadline = new_deadline(p);\n        }\n\n        for (size_t i = 0; i < pfds_num; ++i) {\n            if (!pfds[i].revents) {\n                continue;\n            }\n            int read_rc = server_read_from_client(S, i);\n            if (read_rc < 0) {\n                if (errno == 0) {\n                    LS_DEBUGF(pd, \"clent disconnected before sending a full line\");\n                } else {\n                    LS_WARNF(pd, \"read: %s\", ls_tls_strerror(errno));\n                }\n                server_drop_client(S, i);\n                continue;\n\n            } else if (read_rc > 0) {\n                size_t nline;\n                const char *line = server_get_full_line(S, i, &nline);\n\n                report_line(pd, funcs, line, nline);\n                server_drop_client(S, i);\n\n                deadline = new_deadline(p);\n            }\n        }\n\n        if (can_accept) {\n            int accept_rc = server_accept_new_client(S);\n            if (accept_rc < 0) {\n                LS_FATALF(pd, \"accept: %s\", ls_tls_strerror(errno));\n                goto error;\n\n            } else if (accept_rc > 0) {\n                LS_DEBUGF(pd, \"accepted a new client\");\n            }\n        }\n\n        server_compact(S);\n    }\n\nerror:\n    ls_close(srv_fd);\n    if (S) {\n        server_destroy(S);\n    }\n}\n\nLuastatusPluginIface luastatus_plugin_iface_v1 = {\n    .init = init,\n    .register_funcs = register_funcs,\n    .run = run,\n    .destroy = destroy,\n};\n"
  },
  {
    "path": "plugins/web/CMakeLists.txt",
    "content": "file (GLOB sources \"*.c\" \"mod_json/*.c\" \"mod_urlencode/*.c\")\nluastatus_add_plugin (\n    plugin-web\n    $<TARGET_OBJECTS:ls>\n    $<TARGET_OBJECTS:moonvisit>\n    $<TARGET_OBJECTS:safe>\n    ${sources}\n)\n\ntarget_compile_definitions (plugin-web PUBLIC -D_POSIX_C_SOURCE=200809L)\nluastatus_target_compile_with (plugin-web LUA)\ntarget_include_directories (plugin-web PUBLIC \"${PROJECT_SOURCE_DIR}\" \"${CMAKE_CURRENT_BINARY_DIR}\")\n\nfind_library (MATH_LIBRARY m)\nif (MATH_LIBRARY)\n    target_link_libraries (plugin-web PUBLIC ${MATH_LIBRARY})\nendif ()\n\nfind_package (PkgConfig REQUIRED)\npkg_check_modules (CURL_STUFF REQUIRED libcurl)\nluastatus_target_build_with (plugin-web CURL_STUFF)\n\npkg_check_modules (CJSON_STUFF libcjson)\nif (CJSON_STUFF_FOUND)\n    luastatus_target_build_with (plugin-web CJSON_STUFF)\nelse ()\n    message (WARNING \"libcjson was not found by pkg-config; will try to simply link with -lcjson\")\n    target_link_libraries (plugin-web PUBLIC -lcjson)\nendif ()\n\nset (CJSON_FOUND_BY_PKG_CONFIG \"${CJSON_STUFF_FOUND}\")\nconfigure_file (\"json_config.in.h\" \"json_config.generated.h\")\n\nluastatus_add_man_page (README.rst luastatus-plugin-web 7)\n"
  },
  {
    "path": "plugins/web/README.rst",
    "content": ".. :X-man-page-only: luastatus-plugin-web\n.. :X-man-page-only: ####################\n.. :X-man-page-only:\n.. :X-man-page-only: ########################\n.. :X-man-page-only: Web plugin for luastatus\n.. :X-man-page-only: ########################\n.. :X-man-page-only:\n.. :X-man-page-only: :Copyright: LGPLv3\n.. :X-man-page-only: :Manual section: 7\n\nOverview\n========\n\nThis plugin performs HTTP requests using libcurl. It is designed around a coroutine-based\n\"planner\" mechanism that allows sequencing requests, sleeps, and callback updates.\nIt also provides utility functions for JSON encoding/decoding and URL encoding/decoding.\n\nOptions\n=======\n\nThe following options are supported at plugin initialization:\n\n* ``planner``: function (**required**)\n\n  A Lua function that acts as a coroutine planner. It should yield tables describing\n  the next action (see `Planner Actions`_).\n\n  The most common planner would be make-request-and-sleep::\n\n    function()\n        while true do\n            coroutine.yield({what = 'request', params = {...}})\n            coroutine.yield({what = 'sleep', period = 5.0})\n        end\n    end\n\n* ``with_headers_global``: boolean\n\n  Whether to include HTTP response headers in all response callbacks. Defaults to false.\n\n* ``debug_global``: boolean\n\n  Whether to enable verbose libcurl debug logging globally. Defaults to false.\n\n* ``make_self_pipe``: boolean\n\n  If true, the ``wake_up()`` (see the `Functions`_ section) function will be available. Defaults to\n  false.\n\nPlanner Actions\n===============\n\nThe ``planner`` function should yield a table with an ``action`` key. The following actions are\nsupported:\n\n* ``{action = \"request\", params = {...}}``\n\n  Performs an HTTP request. The ``params`` table contains request-specific options (see\n  `Request Options`_). Upon completion, the plugin calls ``cb`` with the response data.\n\n* ``{action = \"sleep\", period = <number>}``\n\n  Sleeps for ``period`` seconds. Upon completion, the coroutine is resumed with a boolean\n  indicating whether it was woken up via ``wake_up()`` (true) or timed out (false).\n  Note that this action doesn't call ``cb``.\n\n* ``{action = \"call_cb\", what = <string>}``\n\n  Immediately calls ``cb`` with a table containing only the ``what`` field, set to the\n  specified string.\n\nRequest Options\n===============\n\nThe ``params`` table in a ``request`` action supports the following options:\n\n* ``url``: string (**required**)\n\n  The URL to request.\n\n* ``headers``: table\n\n  An array of strings representing HTTP headers to send.\n\n* ``timeout``: number\n\n  Maximum time, in seconds, for the request. Zero means no timeout (this is the default).\n\n* ``max_file_size``: integer\n\n  Maximum file size to download. Zero means no limit (this is the default).\n  Must be less than 2 Gb.\n\n* ``auto_referer``: boolean\n\n  Automatically set the Referer header when following Location headers.\n\n* ``custom_request``: string\n\n  Custom HTTP request method (e.g. DELETE).\n\n* ``follow_location``: boolean\n\n  Follow HTTP redirects.\n\n* ``interface``: string\n\n  Local interface to use for the connection.\n\n* ``max_redirs``: integer\n\n  Maximum number of redirects to follow.\n  The value of -1 means unlimited number of redirects.\n  The default depends on libcurl version; either unlimited or 30.\n\n* ``tcp_keepalive``: boolean\n\n  Enable TCP keepalive.\n\n* ``proxy``, ``proxy_username``, ``proxy_password``: strings\n\n  Proxy configuration.\n\n* ``post_fields``: string\n\n  Data to send in a POST (or POST-like) request.\n\n* ``with_headers``: boolean\n\n  Whether to include HTTP response headers. If either this option or the global\n  ``with_headers_global`` option is enabled, headers are included.\n\n* ``debug``: boolean\n\n  Whether to enable verbose logging for this specific request. The logic is the same\n  as with ``with_headers`` (see above).\n\ncb argument\n===========\n\nThe callback function ``cb`` is called with a table argument. The structure depends on the\naction completed. Note that the ``sleep`` action doesn't call ``cb``.\n\n* For ``request`` actions (HTTP requests has been done, regardless of status):\n\n  A table ``{what = \"response\", status = <integer>, body = <string>, headers = <table (optional)>}``.\n\n* For ``request`` actions (HTTP requests has **not** been done):\n\n  A table ``{what = \"response\", status = 0, body = \"\", headers = {}, error = <string>}``.\n\n* For ``call_cb`` actions:\n\n  A table ``{what = <string>}``, where ``string`` is the value provided in the action.\n\nFunctions\n=========\n\nThe following functions are provided in the plugin table:\n\n* ``luastatus.plugin.wake_up()``\n\n  Writes to the self-pipe. If the plugin is currently executing a ``sleep`` action, this\n  interrupts the sleep and resumes the coroutine (``coroutine.yield`` then returns true).\n  It doesn't directly call ``cb``.\n\n  Waking up in the middle of a request is not supported; if a request is in flight,\n  the wake-up will happen only after the request is done.\n\n  Only available if the ``make_self_pipe`` option was set to ``true``; otherwise, it throws an\n  error.\n\n* ``luastatus.plugin.time_now()``\n\n  Returns the current timestamp, as a floating-point number in seconds.\n  If the system supports it, uses monotonic clock; otherwise, uses wall-clock.\n  It may be helpful in case of a sleep interrupted by a wake-up.\n\n* ``luastatus.plugin.get_supported_opts()``\n\n  Returns a table listing all supported request options (keys are option names, values are ``true``).\n\n* ``luastatus.plugin.json_decode(input, mark_arrays_vs_dicts, mark_nulls)``\n\n  Decodes a JSON string into a Lua table. See the `JSON Decoding`_ section for details on the parameters.\n\n* ``luastatus.plugin.json_encode_str(str)``\n\n  Encodes a string into a JSON-safe string.\n  Please note that it does **not** enclose the output in double quotes.\n\n* ``luastatus.plugin.json_encode_num(num)``\n\n  Converts a number into a JSON-conforming string representation.\n\n* ``luastatus.plugin.urlencode(str[, plus_notation])``\n\n  URL-encodes a string.\n  ``plus_notation`` is an optional boolean parameter; if set to true, spaces will be replaced\n  with ``+``, not ``%20``. ``plus_notation`` defaults to false.\n\n* ``luastatus.plugin.urldecode(str)``\n  URL-decodes a string. Returns nil on failure.\n\n\nJSON Decoding\n=============\n\nThe ``json_decode`` function provides options to handle nuances between JSON and Lua data structures:\n\n* ``mark_arrays_vs_dicts``: boolean\n\n  Lua tables are used for both JSON arrays and objects (dicts), making them indistinguishable\n  by default. If this parameter is ``true``, the decoded tables will have a metatable set:\n\n  + JSON arrays will have a metatable with ``is_array = true``.\n\n  + JSON objects will have a metatable with ``is_dict = true``.\n\n  This allows you to distinguish them using ``getmetatable(<table>).is_array`` or ``is_dict``.\n\n* ``mark_nulls``: boolean\n\n  In JSON, ``null`` is a valid value distinct from a missing key. In Lua, assigning ``nil`` to a\n  table key removes the key. If this parameter is ``true``, JSON ``null`` values are decoded as\n  a special light userdata object instead of Lua ``nil``. This allows you to distinguish between\n  a key that is missing and a key that is explicitly set to ``null``. You can check for this\n  special value using ``type(<value>) == \"userdata\"``.\n\nReturn value\n------------\nOn success, returns the decoded Lua table. On failure, returns ``nil, err_msg``.\n\nLimitations\n-----------\nThe maximum nesting depth that the current implementation supports is 100.\nIf this limit is exceeded, the function fails and returns ``nil, \"depth limit exceeded\"``.\n"
  },
  {
    "path": "plugins/web/compat_lua_resume.c",
    "content": "/*\n * Copyright (C) 2026  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#include \"compat_lua_resume.h\"\n#include <lua.h>\n\n#if LUA_VERSION_NUM >= 504\nstatic int do_resume(lua_State *L, lua_State *from, int nargs)\n{\n    int unused;\n    return lua_resume(L, from, nargs, &unused);\n}\n\n#elif LUA_VERSION_NUM >= 502\nstatic int do_resume(lua_State *L, lua_State *from, int nargs)\n{\n    return lua_resume(L, from, nargs);\n}\n\n#else\nstatic int do_resume(lua_State *L, lua_State *from, int nargs)\n{\n    (void) from;\n    return lua_resume(L, nargs);\n}\n\n#endif\n\nint compat_lua_resume(lua_State *L, lua_State *from, int nargs, int *nresults)\n{\n    int old_top = lua_gettop(L);\n    int rc = do_resume(L, from, nargs);\n    if (rc == LUA_YIELD || rc == 0) {\n        *nresults = lua_gettop(L) - (old_top - (nargs + 1));\n    }\n    return rc;\n}\n"
  },
  {
    "path": "plugins/web/compat_lua_resume.h",
    "content": "/*\n * Copyright (C) 2026  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#pragma once\n\n#include <lua.h>\n\nint compat_lua_resume(lua_State *L, lua_State *from, int nargs, int *nresults);\n"
  },
  {
    "path": "plugins/web/fuzz_esc_json/.gitignore",
    "content": "harness\nfindings\n"
  },
  {
    "path": "plugins/web/fuzz_esc_json/build.sh",
    "content": "#!/bin/sh\n\nif [ -z \"$CC\" ]; then\n    echo >&2 \"You must set the 'CC' environment variable.\"\n    echo >&2 \"Hint: you probably want to set 'CC' to 'some-directory/afl-gcc'.\"\n    exit 1\nfi\n\ncd -- \"$(dirname \"$(readlink \"$0\" || printf '%s\\n' \"$0\")\")\"\n\nluastatus_root=../../..\n\n$CC -Wall -Wextra -O3 -fsanitize=undefined -std=c99 -D_POSIX_C_SOURCE=200809L \\\n    -I\"$luastatus_root\" \\\n    ./harness.c \\\n    ../mod_json/json_encode_str.c \\\n    \"$luastatus_root\"/libls/ls_string.c \\\n    \"$luastatus_root\"/libls/ls_alloc_utils.c \\\n    \"$luastatus_root\"/libls/ls_panic.c \\\n    \"$luastatus_root\"/libls/ls_cstring_utils.c \\\n    \"$luastatus_root\"/libsafe/*.c \\\n    -o harness\n"
  },
  {
    "path": "plugins/web/fuzz_esc_json/clear.sh",
    "content": "#!/bin/sh\n\nset -e\n\ncd -- \"$(dirname \"$(readlink \"$0\" || printf '%s\\n' \"$0\")\")\"\n\nrm -rf ./findings\n"
  },
  {
    "path": "plugins/web/fuzz_esc_json/fuzz.sh",
    "content": "#!/bin/sh\n\nset -e\n\nif [ -z \"$XXX_AFL_DIR\" ]; then\n    echo >&2 \"You must set the 'XXX_AFL_DIR' environment variable.\"\n    exit 1\nfi\n\ncd -- \"$(dirname \"$(readlink \"$0\" || printf '%s\\n' \"$0\")\")\"\n\nmkdir -p ./findings\n\nexport UBSAN_OPTIONS=halt_on_error=1\n\nexport AFL_EXIT_WHEN_DONE=1\n\n# We also set AFL_NO_ARITH=1 because it's a text-based format.\n# This potentially speeds up fuzzing.\nexport AFL_NO_ARITH=1\n\n\"$XXX_AFL_DIR\"/afl-fuzz -i testcases -o findings -t 5 ./harness @@\n"
  },
  {
    "path": "plugins/web/fuzz_esc_json/gen_testcases.sh",
    "content": "#!/bin/sh\n\nset -e\n\ncd -- \"$(dirname \"$(readlink \"$0\" || printf '%s\\n' \"$0\")\")\"\n\nluastatus_root=../../..\n\n\"$luastatus_root\"/fuzz_utils/gen_testcases/gen_testcases.py \\\n    ./testcases \\\n    --a=1:'\\\"/' \\\n    --a-range=h:1:0-31 \\\n    --b=1:abc \\\n    --b-range=h:1:127-255 \\\n    --a-is-important \\\n    --length=5-20 \\\n    --num-files=10 \\\n    --random-seed=123\n"
  },
  {
    "path": "plugins/web/fuzz_esc_json/harness.c",
    "content": "/*\n * Copyright (C) 2025  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#include <stdio.h>\n#include <stdlib.h>\n#include <unistd.h>\n#include <fcntl.h>\n\n#include \"libls/ls_string.h\"\n#include \"libsafe/safev.h\"\n#include \"fuzz_utils/fuzz_utils.h\"\n\n#include \"../mod_json/json_encode_str.h\"\n\nstatic void append_to_ls_string(void *ud, SAFEV v)\n{\n    LS_String *dst = ud;\n    ls_string_append_b(dst, SAFEV_ptr_UNSAFE(v), SAFEV_len(v));\n}\n\nint main(int argc, char **argv)\n{\n    if (argc != 2) {\n        fprintf(stderr, \"USAGE: harness INPUT_FILE\\n\");\n        return 2;\n    }\n\n    int fd_in = open(argv[1], O_RDONLY | O_CLOEXEC);\n    if (fd_in < 0) {\n        perror(argv[1]);\n        abort();\n    }\n\n    FuzzInput input = fuzz_input_new_prealloc(1024);\n    if (fuzz_input_read(fd_in, &input) < 0) {\n        perror(\"read\");\n        abort();\n    }\n\n    LS_String res = ls_string_new_from_s(\"escape result = \");\n    json_encode_str(append_to_ls_string, &res, SAFEV_new_UNSAFE(input.data, input.size));\n\n    fuzz_utils_used(res.data, res.size);\n\n    fuzz_input_free(input);\n    ls_string_free(res);\n    close(fd_in);\n\n    return 0;\n}\n"
  },
  {
    "path": "plugins/web/fuzz_esc_json/testcases/testcase_000",
    "content": "\\\"/\u0002/\\"
  },
  {
    "path": "plugins/web/fuzz_esc_json/testcases/testcase_001",
    "content": "\"\u0006\u0005\b\u0001\"\u0010\\\u0015"
  },
  {
    "path": "plugins/web/fuzz_esc_json/testcases/testcase_003",
    "content": "/\u0002\u0015\\\n"
  },
  {
    "path": "plugins/web/fuzz_esc_json/testcases/testcase_004",
    "content": "\"\u0018/cb\\\"\ba\u0017c\"b\\\u0005"
  },
  {
    "path": "plugins/web/fuzz_esc_json/testcases/testcase_006",
    "content": "ba\u000ba߫\u0018\""
  },
  {
    "path": "plugins/web/fuzz_esc_json/testcases/testcase_007",
    "content": "c\u0014\"bcc"
  },
  {
    "path": "plugins/web/fuzz_esc_json/testcases/testcase_008",
    "content": "cbcȂbab\\"
  },
  {
    "path": "plugins/web/fuzz_esc_json/testcases/testcase_009",
    "content": "cbƐbccb"
  },
  {
    "path": "plugins/web/fuzz_urldecode/.gitignore",
    "content": "/harness\n/findings\n"
  },
  {
    "path": "plugins/web/fuzz_urldecode/build.sh",
    "content": "#!/bin/sh\n\nif [ -z \"$CC\" ]; then\n    echo >&2 \"You must set the 'CC' environment variable.\"\n    echo >&2 \"Hint: you probably want to set 'CC' to 'some-directory/afl-gcc'.\"\n    exit 1\nfi\n\ncd -- \"$(dirname \"$(readlink \"$0\" || printf '%s\\n' \"$0\")\")\"\n\nluastatus_root=../../..\n\n$CC -Wall -Wextra -O3 -fsanitize=undefined -std=c99 -D_POSIX_C_SOURCE=200809L \\\n    -I\"$luastatus_root\" \\\n    ./harness.c \\\n    ../mod_urlencode/urldecode.c \\\n    \"$luastatus_root\"/libsafe/*.c \\\n    \"$luastatus_root\"/libls/ls_panic.c \\\n    \"$luastatus_root\"/libls/ls_cstring_utils.c \\\n    -o harness\n"
  },
  {
    "path": "plugins/web/fuzz_urldecode/fuzz.sh",
    "content": "#!/bin/sh\n\nset -e\n\nif [ -z \"$XXX_AFL_DIR\" ]; then\n    echo >&2 \"You must set the 'XXX_AFL_DIR' environment variable.\"\n    exit 1\nfi\n\ncd -- \"$(dirname \"$(readlink \"$0\" || printf '%s\\n' \"$0\")\")\"\n\nmkdir -p ./findings\n\nexport UBSAN_OPTIONS=halt_on_error=1\n\nexport AFL_EXIT_WHEN_DONE=1\n\n\"$XXX_AFL_DIR\"/afl-fuzz $extra_opts -i testcases -o findings -t 5 ./harness @@\n"
  },
  {
    "path": "plugins/web/fuzz_urldecode/gen_testcases.sh",
    "content": "#!/bin/sh\n\nset -e\n\ncd -- \"$(dirname \"$(readlink \"$0\" || printf '%s\\n' \"$0\")\")\"\n\nluastatus_root=../../..\n\n\"$luastatus_root\"/fuzz_utils/gen_testcases/gen_testcases.py \\\n    ./testcases \\\n    --a=1:x \\\n    --b=1:x \\\n    --mut-substrings=\"|%2f|%F2|%64|%bA|%2X|%XY\" \\\n    --length=10 \\\n    --num-files=10 \\\n    --random-seed=123 \\\n    --extra-testcase='extra1:%' \\\n    --extra-testcase='extra2:%%' \\\n    --extra-testcase='extra3:%%%'\n"
  },
  {
    "path": "plugins/web/fuzz_urldecode/harness.c",
    "content": "/*\n * Copyright (C) 2025  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n#include <unistd.h>\n#include <fcntl.h>\n\n#include \"libls/ls_panic.h\"\n#include \"libsafe/safev.h\"\n#include \"libsafe/mut_safev.h\"\n#include \"fuzz_utils/fuzz_utils.h\"\n\n#include \"../mod_urlencode/urldecode.h\"\n\nint main(int argc, char **argv)\n{\n    if (argc != 2) {\n        fprintf(stderr, \"USAGE: harness INPUT_FILE\\n\");\n        return 2;\n    }\n\n    int fd_in = open(argv[1], O_RDONLY | O_CLOEXEC);\n    if (fd_in < 0) {\n        perror(argv[1]);\n        abort();\n    }\n\n    FuzzInput input = fuzz_input_new_prealloc(1024);\n    if (fuzz_input_read(fd_in, &input) < 0) {\n        perror(\"read\");\n        abort();\n    }\n\n    SAFEV src = SAFEV_new_UNSAFE(input.data, input.size);\n    size_t n = urldecode_size(src);\n    char *ptr = calloc(n, 1);\n    if (!ptr && n) {\n        perror(\"calloc\");\n        abort();\n    }\n    MUT_SAFEV dst = MUT_SAFEV_new_UNSAFE(ptr, n);\n    char is_ok = urldecode(src, dst);\n\n    fuzz_utils_used(&is_ok, 1);\n    fuzz_utils_used(ptr, n);\n\n    free(ptr);\n    fuzz_input_free(input);\n    close(fd_in);\n\n    return 0;\n}\n"
  },
  {
    "path": "plugins/web/fuzz_urldecode/testcases/testcase_000",
    "content": "xxxx%2fxxxxxx"
  },
  {
    "path": "plugins/web/fuzz_urldecode/testcases/testcase_001",
    "content": "xxxxxxx%F2xxx"
  },
  {
    "path": "plugins/web/fuzz_urldecode/testcases/testcase_002",
    "content": "xxxxxxxxxx%64"
  },
  {
    "path": "plugins/web/fuzz_urldecode/testcases/testcase_003",
    "content": "xxxxx%bAxxxxx"
  },
  {
    "path": "plugins/web/fuzz_urldecode/testcases/testcase_004",
    "content": "xxxxxxxx%2Xxx"
  },
  {
    "path": "plugins/web/fuzz_urldecode/testcases/testcase_005",
    "content": "xxxxxxxxx%XYx"
  },
  {
    "path": "plugins/web/fuzz_urldecode/testcases/testcase_006",
    "content": "xxxxxxxx%2fxx"
  },
  {
    "path": "plugins/web/fuzz_urldecode/testcases/testcase_007",
    "content": "xx%F2xxxxxxxx"
  },
  {
    "path": "plugins/web/fuzz_urldecode/testcases/testcase_008",
    "content": "xxxxxxxxx%64x"
  },
  {
    "path": "plugins/web/fuzz_urldecode/testcases/testcase_009",
    "content": "xxx%bAxxxxxxx"
  },
  {
    "path": "plugins/web/fuzz_urldecode/testcases/testcase_extra1",
    "content": "%"
  },
  {
    "path": "plugins/web/fuzz_urldecode/testcases/testcase_extra2",
    "content": "%%"
  },
  {
    "path": "plugins/web/fuzz_urldecode/testcases/testcase_extra3",
    "content": "%%%"
  },
  {
    "path": "plugins/web/fuzz_urlencode/.gitignore",
    "content": "/harness\n/findings\n"
  },
  {
    "path": "plugins/web/fuzz_urlencode/build.sh",
    "content": "#!/bin/sh\n\nif [ -z \"$CC\" ]; then\n    echo >&2 \"You must set the 'CC' environment variable.\"\n    echo >&2 \"Hint: you probably want to set 'CC' to 'some-directory/afl-gcc'.\"\n    exit 1\nfi\n\ncd -- \"$(dirname \"$(readlink \"$0\" || printf '%s\\n' \"$0\")\")\"\n\nluastatus_root=../../..\n\n$CC -Wall -Wextra -O3 -fsanitize=undefined -std=c99 -D_POSIX_C_SOURCE=200809L \\\n    -I\"$luastatus_root\" \\\n    ./harness.c \\\n    ../mod_urlencode/urlencode.c \\\n    \"$luastatus_root\"/libsafe/*.c \\\n    \"$luastatus_root\"/libls/ls_panic.c \\\n    \"$luastatus_root\"/libls/ls_cstring_utils.c \\\n    -o harness\n"
  },
  {
    "path": "plugins/web/fuzz_urlencode/fuzz.sh",
    "content": "#!/bin/sh\n\nset -e\n\nif [ -z \"$XXX_AFL_DIR\" ]; then\n    echo >&2 \"You must set the 'XXX_AFL_DIR' environment variable.\"\n    exit 1\nfi\n\ncd -- \"$(dirname \"$(readlink \"$0\" || printf '%s\\n' \"$0\")\")\"\n\ncase \"$1\" in\n0|1)\n    plus_encoding=$1\n    ;;\n*)\n    printf '%s\\n' \"USAGE: $0 {0 | 1}\" >&2\n    exit 2\n    ;;\nesac\n\nmkdir -p ./findings_\"$plus_encoding\"\n\nexport UBSAN_OPTIONS=halt_on_error=1\n\nexport AFL_EXIT_WHEN_DONE=1\n\n\"$XXX_AFL_DIR\"/afl-fuzz $extra_opts -i testcases -o findings_\"$plus_encoding\" -t 5 ./harness @@ \"$plus_encoding\"\n"
  },
  {
    "path": "plugins/web/fuzz_urlencode/fuzz_all.sh",
    "content": "#!/bin/sh\n\nset -e\n\ncd -- \"$(dirname \"$(readlink \"$0\" || printf '%s\\n' \"$0\")\")\"\n\nfor plus_encoding in 0 1; do\n    ./fuzz.sh \"$plus_encoding\"\ndone\n"
  },
  {
    "path": "plugins/web/fuzz_urlencode/gen_testcases.sh",
    "content": "#!/bin/sh\n\nset -e\n\ncd -- \"$(dirname \"$(readlink \"$0\" || printf '%s\\n' \"$0\")\")\"\n\nluastatus_root=../../..\n\n\"$luastatus_root\"/fuzz_utils/gen_testcases/gen_testcases.py \\\n    ./testcases \\\n    --a=1:x% \\\n    --b=1:' ' \\\n    --length=10 \\\n    --num-files=10 \\\n    --random-seed=123\n"
  },
  {
    "path": "plugins/web/fuzz_urlencode/harness.c",
    "content": "/*\n * Copyright (C) 2025  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n#include <unistd.h>\n#include <fcntl.h>\n\n#include \"libls/ls_panic.h\"\n#include \"libsafe/safev.h\"\n#include \"libsafe/mut_safev.h\"\n#include \"fuzz_utils/fuzz_utils.h\"\n\n#include \"../mod_urlencode/urlencode.h\"\n\nint main(int argc, char **argv)\n{\n    if (argc != 3) {\n        fprintf(stderr, \"USAGE: harness INPUT_FILE PLUS_NOTATION_FLAG\\n\");\n        return 2;\n    }\n\n    int fd_in = open(argv[1], O_RDONLY | O_CLOEXEC);\n    if (fd_in < 0) {\n        perror(argv[1]);\n        abort();\n    }\n\n    bool plus_notation;\n    if (strcmp(argv[2], \"0\") == 0) {\n        plus_notation = false;\n    } else if (strcmp(argv[2], \"1\") == 0) {\n        plus_notation = true;\n    } else {\n        fprintf(stderr, \"Invalid PLUS_NOTATION_FLAG (expected either 0 or 1).\\n\");\n        return 2;\n    }\n\n    FuzzInput input = fuzz_input_new_prealloc(1024);\n    if (fuzz_input_read(fd_in, &input) < 0) {\n        perror(\"read\");\n        abort();\n    }\n\n    SAFEV src = SAFEV_new_UNSAFE(input.data, input.size);\n    size_t n = urlencode_size(src, plus_notation);\n    char *ptr = malloc(n);\n    if (!ptr && n) {\n        perror(\"malloc\");\n        abort();\n    }\n    MUT_SAFEV dst = MUT_SAFEV_new_UNSAFE(ptr, n);\n    urlencode(src, dst, plus_notation);\n\n    fuzz_utils_used(ptr, n);\n\n    free(ptr);\n    fuzz_input_free(input);\n    close(fd_in);\n\n    return 0;\n}\n"
  },
  {
    "path": "plugins/web/fuzz_urlencode/testcases/testcase_000",
    "content": "x%x%%%xxxx"
  },
  {
    "path": "plugins/web/fuzz_urlencode/testcases/testcase_001",
    "content": "%% %xxx%%x"
  },
  {
    "path": "plugins/web/fuzz_urlencode/testcases/testcase_002",
    "content": "%  xx%%xx%"
  },
  {
    "path": "plugins/web/fuzz_urlencode/testcases/testcase_003",
    "content": "x%%% % x x"
  },
  {
    "path": "plugins/web/fuzz_urlencode/testcases/testcase_004",
    "content": "%  % x x%x"
  },
  {
    "path": "plugins/web/fuzz_urlencode/testcases/testcase_005",
    "content": " x xx   x "
  },
  {
    "path": "plugins/web/fuzz_urlencode/testcases/testcase_006",
    "content": "  %x    % "
  },
  {
    "path": "plugins/web/fuzz_urlencode/testcases/testcase_007",
    "content": "   %    % "
  },
  {
    "path": "plugins/web/fuzz_urlencode/testcases/testcase_008",
    "content": "     x    "
  },
  {
    "path": "plugins/web/fuzz_urlencode/testcases/testcase_009",
    "content": "          "
  },
  {
    "path": "plugins/web/get_min_curl_ver.sh",
    "content": "#!/usr/bin/env bash\n\nset -e\nset -o pipefail\nshopt -s failglob\n\ncd -- \"$(dirname \"$(readlink \"$0\" || printf '%s\\n' \"$0\")\")\"\n\nsources=( ./*.[ch] )\n\ngen_symbol_list() {\n    grep -woE 'curl_[A-Za-z0-9_]+' \"${sources[@]}\" --binary-files=without-match --no-filename \\\n        || return $?\n    grep -woE 'CURLOPT_[A-Za-z0-9_]+' \"${sources[@]}\" --binary-files=without-match --no-filename \\\n        || return $?\n}\n\ngen_symbol_list | sort -u | while IFS= read -r sym; do\n    echo >&2 \"Querying '${sym}'...\"\n\n    { lynx -dump \"https://curl.se/libcurl/c/${sym}.html\" </dev/null || true; } \\\n        | sed -rn 's/^.*\\<Added in curl ([0-9\\.]+).*$/\\1/p'\ndone | sort -r -V | head -1\n"
  },
  {
    "path": "plugins/web/json_config.in.h",
    "content": "#pragma once\n\n#cmakedefine01 CJSON_FOUND_BY_PKG_CONFIG\n"
  },
  {
    "path": "plugins/web/make_request.c",
    "content": "/*\n * Copyright (C) 2026  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#include \"make_request.h\"\n#include <stddef.h>\n#include \"libls/ls_string.h\"\n#include \"libls/ls_strarr.h\"\n#include \"libls/ls_panic.h\"\n#include \"include/plugin_data_v1.h\"\n#include \"include/sayf_macros.h\"\n#include \"set_error.h\"\n\n#define CANNOT_FAIL(expr) \\\n    do { \\\n        if ((expr) != CURLE_OK) { \\\n            LS_PANIC(\"CANNOT_FAIL() failed\"); \\\n        } \\\n    } while (0)\n\nstatic size_t callback_resp(char *buf, size_t char_sz, size_t nbuf, void *ud)\n{\n    (void) char_sz;\n\n    LS_String *body = ud;\n    ls_string_append_b(body, buf, nbuf);\n\n    return nbuf;\n}\n\nstatic size_t callback_header(char *buf, size_t char_sz, size_t nbuf, void *ud)\n{\n    (void) char_sz;\n\n    LS_StringArray *headers = ud;\n    ls_strarr_append(headers, buf, nbuf);\n\n    return nbuf;\n}\n\nstatic const char *get_debug_prefix(curl_infotype type)\n{\n    switch (type) {\n    case CURLINFO_TEXT:\n        return \"Text\";\n    case CURLINFO_HEADER_OUT:\n        return \"Send header\";\n    case CURLINFO_DATA_OUT:\n        return \"Send data\";\n    case CURLINFO_HEADER_IN:\n        return \"Recv header\";\n    case CURLINFO_DATA_IN:\n        return \"Recv data\";\n    default:\n        return NULL;\n    }\n}\n\nstatic int callback_debug(CURL *C, curl_infotype type, char *buf, size_t nbuf, void *ud)\n{\n    (void) C;\n\n    LuastatusPluginData *pd = ud;\n\n    const char *prefix = get_debug_prefix(type);\n    if (prefix) {\n        enum { MAX_PREVIEW = 1024 * 8 };\n        int truncated_len = nbuf < MAX_PREVIEW ? nbuf : MAX_PREVIEW;\n        LS_INFOF(pd, \"<curl debug> %s: %.*s\", prefix, truncated_len, buf);\n    }\n    return 0;\n}\n\nbool make_request(\n    LuastatusPluginData *pd,\n    int req_flags,\n    CURL *C,\n    Response *out,\n    char **out_errmsg)\n{\n    *out = (Response) {\n        .status = 0,\n        .headers = ls_strarr_new(),\n        .body = ls_string_new_reserve(1024),\n    };\n\n    CANNOT_FAIL(curl_easy_setopt(C, CURLOPT_WRITEFUNCTION, callback_resp));\n\n    CANNOT_FAIL(curl_easy_setopt(C, CURLOPT_WRITEDATA, (void *) &out->body));\n\n    if (req_flags & REQ_FLAG_NEEDS_HEADERS) {\n        CANNOT_FAIL(curl_easy_setopt(C, CURLOPT_HEADERFUNCTION, callback_header));\n        CANNOT_FAIL(curl_easy_setopt(C, CURLOPT_HEADERDATA, (void *) &out->headers));\n    }\n    if (req_flags & REQ_FLAG_DEBUG) {\n        CANNOT_FAIL(curl_easy_setopt(C, CURLOPT_VERBOSE, 1L));\n        CANNOT_FAIL(curl_easy_setopt(C, CURLOPT_DEBUGFUNCTION, callback_debug));\n        CANNOT_FAIL(curl_easy_setopt(C, CURLOPT_DEBUGDATA, (void *) pd));\n    }\n\n    CURLcode rc = curl_easy_perform(C);\n    if (rc != CURLE_OK) {\n        set_curl_error(out_errmsg, rc);\n        return false;\n    }\n\n    CANNOT_FAIL(curl_easy_getinfo(C, CURLINFO_RESPONSE_CODE, &out->status));\n\n    return true;\n}\n"
  },
  {
    "path": "plugins/web/make_request.h",
    "content": "/*\n * Copyright (C) 2026  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#pragma once\n\n#include <stdbool.h>\n#include <curl/curl.h>\n#include \"libls/ls_string.h\"\n#include \"libls/ls_strarr.h\"\n#include \"include/plugin_data_v1.h\"\n\nenum {\n    REQ_FLAG_NEEDS_HEADERS = 1 << 0,\n    REQ_FLAG_DEBUG         = 1 << 1,\n};\n\ntypedef struct {\n    long status;\n    LS_StringArray headers;\n    LS_String body;\n} Response;\n\nbool make_request(\n        LuastatusPluginData *pd,\n        int req_flags,\n        CURL *C,\n        Response *out,\n        char **out_errmsg);\n"
  },
  {
    "path": "plugins/web/mod_json/json_decode.c",
    "content": "/*\n * Copyright (C) 2026  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#include \"json_decode.h\"\n#include <stdbool.h>\n#include <stdio.h>\n#include <string.h>\n#include <limits.h>\n#include <lua.h>\n#include <lauxlib.h>\n\n// It is actually located in ${CMAKE_CURRENT_BINARY_DIR}.\n// CMakeLists.txt adds it to the \"include directories\" list.\n#include \"json_config.generated.h\"\n\n#if CJSON_FOUND_BY_PKG_CONFIG\n#   include <cJSON.h>\n#else\n#   include <cjson/cJSON.h>\n#endif\n\n#include \"libls/ls_panic.h\"\n\ntypedef struct {\n    int recur_lim;\n    int lref_mt_array;\n    int lref_mt_dict;\n    bool mark_nulls;\n    const char *err_descr;\n} Params;\n\nstatic int mt_new(lua_State *L, const char *field)\n{\n    // L: ?\n    lua_createtable(L, 0, 1); // L: ? mt\n    lua_pushboolean(L, 1); // L: ? mt boolean\n    lua_setfield(L, -2, field); // L: ? mt\n    return luaL_ref(L, LUA_REGISTRYINDEX); // L: ?\n}\n\nstatic void mt_set(lua_State *L, int lref)\n{\n    // L: ? mt\n    if (lref == LUA_REFNIL) {\n        return;\n    }\n    lua_rawgeti(L, LUA_REGISTRYINDEX, lref); // L: ? table mt\n    lua_setmetatable(L, -2); // L: ? table\n}\n\nstatic void mt_unref(lua_State *L, int lref)\n{\n    if (lref == LUA_REFNIL) {\n        return;\n    }\n    luaL_unref(L, LUA_REGISTRYINDEX, lref);\n}\n\n// Forward declaration\nstatic bool convert(lua_State *L, cJSON *j, Params *params);\n\nstatic bool convert_array(lua_State *L, cJSON *j, Params *params)\n{\n    int n = cJSON_GetArraySize(j);\n    lua_createtable(L, n, 0); // L: table\n\n    mt_set(L, params->lref_mt_array);\n\n    unsigned i = 1;\n    for (cJSON *item = j->child; item; item = item->next) {\n        if (!convert(L, item, params)) {\n            return false;\n        }\n        // L: table value\n        lua_rawseti(L, -2, i); // L: table\n        ++i;\n    }\n    return true;\n}\n\nstatic bool convert_dict(lua_State *L, cJSON *j, Params *params)\n{\n    // /cJSON_GetArraySize()/ it works for dicts too\n    int n = cJSON_GetArraySize(j);\n    lua_createtable(L, 0, n); // L: table\n\n    mt_set(L, params->lref_mt_dict);\n\n    for (cJSON *item = j->child; item; item = item->next) {\n        if (!convert(L, item, params)) {\n            return false;\n        }\n        // L: table value\n        lua_setfield(L, -2, item->string); // L: table\n    }\n    return true;\n}\n\nstatic bool convert(lua_State *L, cJSON *j, Params *params)\n{\n    if (!params->recur_lim--) {\n        params->err_descr = \"depth limit exceeded\";\n        return false;\n    }\n    if (!lua_checkstack(L, 10)) {\n        params->err_descr = \"too many elements on Lua stack\";\n        return false;\n    }\n\n    if (cJSON_IsNull(j)) {\n        if (params->mark_nulls) {\n            lua_pushlightuserdata(L, NULL);\n        } else {\n            lua_pushnil(L);\n        }\n        return true;\n\n    } else if (cJSON_IsTrue(j)) {\n        lua_pushboolean(L, 1);\n        return true;\n\n    } else if (cJSON_IsFalse(j)) {\n        lua_pushboolean(L, 0);\n        return true;\n\n    } else if (cJSON_IsNumber(j)) {\n        lua_pushnumber(L, j->valuedouble);\n        return true;\n\n    } else if (cJSON_IsString(j)) {\n        lua_pushstring(L, j->valuestring);\n        return true;\n\n    } else if (cJSON_IsArray(j)) {\n        return convert_array(L, j, params);\n\n    } else if (cJSON_IsObject(j)) {\n        return convert_dict(L, j, params);\n\n    } else {\n        LS_MUST_BE_UNREACHABLE();\n    }\n}\n\nbool json_decode(lua_State *L, const char *input, int max_depth, int flags, char *errbuf, size_t nerrbuf)\n{\n    LS_ASSERT(input != NULL);\n\n    if (!lua_checkstack(L, max_depth)) {\n        snprintf(errbuf, nerrbuf, \"Lua failed to allocate stack of size %d\", max_depth);\n        return false;\n    }\n\n    if (strlen(input) > (INT_MAX - 16)) {\n        snprintf(errbuf, nerrbuf, \"JSON payload is too large\");\n        return false;\n    }\n\n    // /cJSON_GetErrorPtr/ is not thread-safe, so we use /cJSON_ParseWithOpts/.\n    const char *err_ptr;\n    cJSON *j = cJSON_ParseWithOpts(input, &err_ptr, /*require_null_terminate=*/ 1);\n    if (!j) {\n        snprintf(errbuf, nerrbuf, \"JSON parse error at byte %d\", (int) (err_ptr - input));\n        return false;\n    }\n\n    Params params = {\n        .recur_lim = max_depth,\n        .lref_mt_array = LUA_REFNIL,\n        .lref_mt_dict = LUA_REFNIL,\n        .mark_nulls = false,\n        .err_descr = NULL,\n    };\n    if (flags & JSON_DEC_MARK_ARRAYS_VS_DICT) {\n        params.lref_mt_array = mt_new(L, \"is_array\");\n        params.lref_mt_dict = mt_new(L, \"is_dict\");\n    }\n    if (flags & JSON_DEC_MARK_NULLS) {\n        params.mark_nulls = true;\n    }\n\n    bool is_ok = convert(L, j, &params);\n\n    mt_unref(L, params.lref_mt_array);\n    mt_unref(L, params.lref_mt_dict);\n\n    if (!is_ok) {\n        snprintf(errbuf, nerrbuf, \"%s\", params.err_descr);\n    }\n    cJSON_Delete(j);\n    return is_ok;\n}\n"
  },
  {
    "path": "plugins/web/mod_json/json_decode.h",
    "content": "/*\n * Copyright (C) 2026  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#pragma once\n\n#include <stdbool.h>\n#include <stddef.h>\n#include <lua.h>\n\nenum {\n    JSON_DEC_MARK_ARRAYS_VS_DICT = 1 << 0,\n    JSON_DEC_MARK_NULLS          = 1 << 1,\n};\n\nbool json_decode(lua_State *L, const char *input, int max_depth, int flags, char *errbuf, size_t nerrbuf);\n"
  },
  {
    "path": "plugins/web/mod_json/json_encode_num.c",
    "content": "/*\n * Copyright (C) 2026  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#include \"json_encode_num.h\"\n#include <stddef.h>\n#include <math.h>\n#include \"libls/ls_xallocf.h\"\n\nchar *json_encode_num(double d)\n{\n    if (!isfinite(d)) {\n        return NULL;\n    }\n    return ls_xallocf(\"%.20f\", d);\n}\n"
  },
  {
    "path": "plugins/web/mod_json/json_encode_num.h",
    "content": "/*\n * Copyright (C) 2026  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#pragma once\n\nchar *json_encode_num(double d);\n"
  },
  {
    "path": "plugins/web/mod_json/json_encode_str.c",
    "content": "/*\n * Copyright (C) 2026  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#include \"json_encode_str.h\"\n#include <stddef.h>\n#include \"libsafe/safev.h\"\n#include \"libsafe/mut_safev.h\"\n\nstatic const SAFEV HEX_CHARS = SAFEV_STATIC_INIT_FROM_LITERAL(\"0123456789ABCDEF\");\n\nvoid json_encode_str(\n        void (*append)(void *ud, SAFEV segment),\n        void *append_ud,\n        SAFEV v)\n{\n    char esc_arr[] = {'\\\\', 'u', '0', '0', '#', '#'};\n    MUT_SAFEV esc = MUT_SAFEV_new_UNSAFE(esc_arr, sizeof(esc_arr));\n\n    size_t n = SAFEV_len(v);\n    size_t prev = 0;\n    for (size_t i = 0; i < n; ++i) {\n        unsigned char c = SAFEV_at(v, i);\n        if (c < 32 || c == '\\\\' || c == '\"' || c == '/') {\n            append(append_ud, SAFEV_subspan(v, prev, i));\n            MUT_SAFEV_set_at(esc, 4, SAFEV_at(HEX_CHARS, c / 16));\n            MUT_SAFEV_set_at(esc, 5, SAFEV_at(HEX_CHARS, c % 16));\n            append(append_ud, MUT_SAFEV_TO_SAFEV(esc));\n            prev = i + 1;\n        }\n    }\n    append(append_ud, SAFEV_subspan(v, prev, n));\n}\n"
  },
  {
    "path": "plugins/web/mod_json/json_encode_str.h",
    "content": "/*\n * Copyright (C) 2026  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#pragma once\n\n#include \"libsafe/safev.h\"\n\nvoid json_encode_str(\n        void (*append)(void *ud, SAFEV segment),\n        void *append_ud,\n        SAFEV v);\n"
  },
  {
    "path": "plugins/web/mod_json/mod_json.c",
    "content": "/*\n * Copyright (C) 2026  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#include \"mod_json.h\"\n#include <stdbool.h>\n#include <stdlib.h>\n#include <lua.h>\n#include <lauxlib.h>\n#include \"libsafe/safev.h\"\n#include \"json_decode.h\"\n#include \"json_encode_str.h\"\n#include \"json_encode_num.h\"\n\nstatic inline bool getbool(lua_State *L, int arg)\n{\n    if (lua_isnoneornil(L, arg)) {\n        return false;\n    }\n    luaL_checktype(L, arg, LUA_TBOOLEAN);\n    return lua_toboolean(L, arg);\n}\n\nstatic int l_json_decode(lua_State *L)\n{\n    enum { MAX_DEPTH = 100 };\n\n    const char *input = luaL_checkstring(L, 1);\n    bool mark_arrays_vs_dicts = getbool(L, 2);\n    bool mark_nulls = getbool(L, 3);\n\n    int flags = 0;\n    if (mark_arrays_vs_dicts) {\n        flags |= JSON_DEC_MARK_ARRAYS_VS_DICT;\n    }\n    if (mark_nulls) {\n        flags |= JSON_DEC_MARK_NULLS;\n    }\n\n    char errbuf[256];\n\n    bool is_ok = json_decode(L, input, MAX_DEPTH, flags, errbuf, sizeof(errbuf));\n\n    if (is_ok) {\n        return 1;\n    } else {\n        lua_settop(L, 0);\n        lua_pushnil(L);\n        lua_pushstring(L, errbuf);\n        return 2;\n    }\n}\n\nstatic void append_to_lua_buf_callback(void *ud, SAFEV v)\n{\n    luaL_Buffer *b = ud;\n    luaL_addlstring(b, SAFEV_ptr_UNSAFE(v), SAFEV_len(v));\n}\n\nstatic int l_json_encode_str(lua_State *L)\n{\n    size_t ns;\n    const char *s = luaL_checklstring(L, -1, &ns);\n\n    luaL_Buffer b;\n    luaL_buffinit(L, &b);\n    json_encode_str(append_to_lua_buf_callback, &b, SAFEV_new_UNSAFE(s, ns));\n\n    luaL_pushresult(&b); // L: result\n    return 1;\n}\n\nstatic int l_json_encode_num(lua_State *L)\n{\n    double d = luaL_checknumber(L, 1);\n    char *res = json_encode_num(d);\n    if (res) {\n        lua_pushstring(L, res);\n    } else {\n        lua_pushnil(L);\n    }\n    free(res);\n    return 1;\n}\n\nvoid mod_json_register_funcs(lua_State *L)\n{\n#define REG(func, name) (lua_pushcfunction(L, (func)), lua_setfield((L), -2, (name)))\n\n    REG(l_json_decode, \"json_decode\");\n    REG(l_json_encode_str, \"json_encode_str\");\n    REG(l_json_encode_num, \"json_encode_num\");\n\n#undef REG\n}\n"
  },
  {
    "path": "plugins/web/mod_json/mod_json.h",
    "content": "/*\n * Copyright (C) 2026  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#pragma once\n\n#include <lua.h>\n\nvoid mod_json_register_funcs(lua_State *L);\n"
  },
  {
    "path": "plugins/web/mod_urlencode/mod_urlencode.c",
    "content": "/*\n * Copyright (C) 2026  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#include \"mod_urlencode.h\"\n#include <stdbool.h>\n#include <stdlib.h>\n#include <lua.h>\n#include <lauxlib.h>\n#include \"libsafe/safev.h\"\n#include \"libsafe/mut_safev.h\"\n#include \"urlencode.h\"\n#include \"urldecode.h\"\n#include \"libls/ls_alloc_utils.h\"\n\nstatic inline bool getbool(lua_State *L, int arg)\n{\n    if (lua_isnoneornil(L, arg)) {\n        return false;\n    }\n    luaL_checktype(L, arg, LUA_TBOOLEAN);\n    return lua_toboolean(L, arg);\n}\n\nstatic int l_urlencode(lua_State *L)\n{\n    size_t ns;\n    const char *s = luaL_checklstring(L, 1, &ns);\n    bool plus_notation = getbool(L, 2);\n\n    SAFEV src = SAFEV_new_UNSAFE(s, ns);\n\n    size_t nres = urlencode_size(src, plus_notation);\n    if (nres == (size_t) -1) {\n        ls_oom();\n    }\n\n    char *buf = LS_XNEW(char, nres);\n\n    urlencode(src, MUT_SAFEV_new_UNSAFE(buf, nres), plus_notation);\n\n    lua_pushlstring(L, buf, nres);\n\n    free(buf);\n\n    return 1;\n}\n\nstatic int l_urldecode(lua_State *L)\n{\n    size_t ns;\n    const char *s = luaL_checklstring(L, 1, &ns);\n\n    SAFEV src = SAFEV_new_UNSAFE(s, ns);\n\n    size_t nres = urldecode_size(src);\n\n    char *buf = LS_XNEW(char, nres);\n\n    if (!urldecode(src, MUT_SAFEV_new_UNSAFE(buf, nres))) {\n        lua_pushnil(L);\n        goto done;\n    }\n\n    lua_pushlstring(L, buf, nres);\n\ndone:\n    free(buf);\n    return 1;\n}\n\nvoid mod_urlencode_register_funcs(lua_State *L)\n{\n#define REG(func, name) (lua_pushcfunction(L, (func)), lua_setfield((L), -2, (name)))\n\n    REG(l_urlencode, \"urlencode\");\n    REG(l_urldecode, \"urldecode\");\n\n#undef REG\n}\n"
  },
  {
    "path": "plugins/web/mod_urlencode/mod_urlencode.h",
    "content": "/*\n * Copyright (C) 2026  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#pragma once\n\n#include <lua.h>\n\nvoid mod_urlencode_register_funcs(lua_State *L);\n"
  },
  {
    "path": "plugins/web/mod_urlencode/urldecode.c",
    "content": "/*\n * Copyright (C) 2026  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#include \"urldecode.h\"\n#include <stddef.h>\n#include <stdbool.h>\n#include \"libls/ls_panic.h\"\n\nsize_t urldecode_size(SAFEV src)\n{\n    size_t n = SAFEV_len(src);\n    size_t res = 0;\n\n    size_t i = 0;\n    while (i < n) {\n        if (SAFEV_at(src, i) == '%') {\n            i += 3;\n        } else {\n            ++i;\n        }\n        ++res;\n    }\n\n    return res;\n}\n\nstatic int parse_hex_digit(char c)\n{\n    if ('0' <= c && c <= '9') {\n        return c - '0';\n    }\n    if ('a' <= c && c <= 'f') {\n        return c - 'a' + 10;\n    }\n    if ('A' <= c && c <= 'F') {\n        return c - 'A' + 10;\n    }\n    return -1;\n}\n\nbool urldecode(SAFEV src, MUT_SAFEV dst)\n{\n    size_t n = SAFEV_len(src);\n\n    size_t i = 0; // position in /src/\n    size_t j = 0; // position in /dst/\n    while (i < n) {\n        char c = SAFEV_at(src, i);\n        char new_c;\n        if (c == '%') {\n            if (i + 2 >= n) {\n                return false;\n            }\n            int hi = parse_hex_digit(SAFEV_at(src, i + 1));\n            int lo = parse_hex_digit(SAFEV_at(src, i + 2));\n            if (hi < 0 || lo < 0) {\n                return false;\n            }\n            new_c = (hi << 4) | lo;\n            i += 3;\n        } else {\n            new_c = (c == '+') ? ' ' : c;\n            ++i;\n        }\n        MUT_SAFEV_set_at(dst, j, new_c);\n        ++j;\n    }\n\n    size_t expected_result_size = SAFEV_len(MUT_SAFEV_TO_SAFEV(dst));\n    LS_ASSERT(j == expected_result_size);\n\n    return true;\n}\n"
  },
  {
    "path": "plugins/web/mod_urlencode/urldecode.h",
    "content": "/*\n * Copyright (C) 2026  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#pragma once\n\n#include <stddef.h>\n#include <stdbool.h>\n#include \"libsafe/safev.h\"\n#include \"libsafe/mut_safev.h\"\n\nsize_t urldecode_size(SAFEV src);\n\nbool urldecode(SAFEV src, MUT_SAFEV dst);\n"
  },
  {
    "path": "plugins/web/mod_urlencode/urlencode.c",
    "content": "/*\n * Copyright (C) 2026  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#include \"urlencode.h\"\n#include <stdbool.h>\n#include <stddef.h>\n#include \"libls/ls_panic.h\"\n\nstatic inline bool should_be_escaped(char c)\n{\n    if ('A' <= c && c <= 'Z') {\n        return false;\n    }\n\n    if ('a' <= c && c <= 'z') {\n        return false;\n    }\n\n    if ('0' <= c && c <= '9') {\n        return false;\n    }\n\n    switch (c) {\n    case '-': // minus\n    case '_': // underscore\n    case '.':\n    case '~':\n        return false;\n    default:\n        return true;\n    }\n}\n\nsize_t urlencode_size(SAFEV src, bool plus_notation)\n{\n    size_t n = SAFEV_len(src);\n    size_t res = 0;\n    for (size_t i = 0; i < n; ++i) {\n        char c = SAFEV_at(src, i);\n        size_t summand;\n        if ((plus_notation && c == ' ') || !should_be_escaped(c)) {\n            summand = 1;\n        } else {\n            summand = 3;\n        }\n        res += summand;\n        if (res < summand) {\n            // overflow\n            return -1;\n        }\n    }\n    return res;\n}\n\nstatic const SAFEV HEX_CHARS = SAFEV_STATIC_INIT_FROM_LITERAL(\"0123456789ABCDEF\");\n\nvoid urlencode(SAFEV src, MUT_SAFEV dst, bool plus_notation)\n{\n    size_t n = SAFEV_len(src);\n    size_t j = 0; // position in /dst/\n\n    for (size_t i = 0; i < n; ++i) {\n        unsigned char c = SAFEV_at(src, i);\n        if (plus_notation && c == ' ') {\n            MUT_SAFEV_set_at(dst, j, '+');\n            ++j;\n        } else if (should_be_escaped(c)) {\n            MUT_SAFEV_set_at(dst, j + 0, '%');\n            MUT_SAFEV_set_at(dst, j + 1, SAFEV_at(HEX_CHARS, c / 16));\n            MUT_SAFEV_set_at(dst, j + 2, SAFEV_at(HEX_CHARS, c % 16));\n            j += 3;\n        } else {\n            MUT_SAFEV_set_at(dst, j, c);\n            ++j;\n        }\n    }\n    size_t expected_result_size = SAFEV_len(MUT_SAFEV_TO_SAFEV(dst));\n    LS_ASSERT(j == expected_result_size);\n}\n"
  },
  {
    "path": "plugins/web/mod_urlencode/urlencode.h",
    "content": "/*\n * Copyright (C) 2026  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#pragma once\n\n#include <stddef.h>\n#include <stdbool.h>\n#include \"libsafe/safev.h\"\n#include \"libsafe/mut_safev.h\"\n\nsize_t urlencode_size(SAFEV src, bool plus_notation);\n\nvoid urlencode(SAFEV src, MUT_SAFEV dst, bool plus_notation);\n"
  },
  {
    "path": "plugins/web/next_request_params.h",
    "content": "/*\n * Copyright (C) 2026  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#pragma once\n\n#include <curl/curl.h>\n\ntypedef struct {\n    CURL *C;\n    struct curl_slist *headers;\n    int local_req_flags;\n} NextRequestParams;\n"
  },
  {
    "path": "plugins/web/opts.c",
    "content": "/*\n * Copyright (C) 2026  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#include \"opts.h\"\n#include <stdbool.h>\n#include <limits.h>\n#include <math.h>\n#include <lua.h>\n#include \"libls/ls_algo.h\"\n#include \"libls/ls_lua_compat.h\"\n#include \"libls/ls_panic.h\"\n#include \"libls/ls_time_utils.h\"\n#include \"set_error.h\"\n#include \"next_request_params.h\"\n\nstatic bool apply_str(NextRequestParams *dst, lua_State *L, CURLoption which, char **out_errmsg)\n{\n    // L: ? something\n    if (!lua_isstring(L, -1)) {\n        set_type_error(out_errmsg, L, -1, LUA_TSTRING, \"\");\n        return false;\n    }\n\n    const char *s = lua_tostring(L, -1);\n    CURLcode rc = curl_easy_setopt(dst->C, which, (char *) s);\n    if (rc != CURLE_OK) {\n        set_curl_error(out_errmsg, rc);\n        return false;\n    }\n\n    return true;\n}\n\nstatic bool apply_bool(NextRequestParams *dst, lua_State *L, CURLoption which, char **out_errmsg)\n{\n    // L: ? something\n    if (!lua_isboolean(L, -1)) {\n        set_type_error(out_errmsg, L, -1, LUA_TBOOLEAN, \"\");\n        return false;\n    }\n\n    bool flag = lua_toboolean(L, -1);\n    CURLcode rc = curl_easy_setopt(dst->C, which, (long) flag);\n    if (rc != CURLE_OK) {\n        set_curl_error(out_errmsg, rc);\n        return false;\n    }\n\n    return true;\n}\n\nstatic bool fetch_int(\n    lua_State *L,\n    int *out_res,\n    char **out_errmsg)\n{\n    // L: ? something\n    if (!lua_isnumber(L, -1)) {\n        set_type_error(out_errmsg, L, -1, LUA_TNUMBER, \"\");\n        return false;\n    }\n\n    double fp = lua_tonumber(L, -1);\n\n    if (isnan(fp)) {\n        set_error(out_errmsg, \"value is NaN\");\n        return false;\n    }\n    if (fp < 0) {\n        *out_res = -1;\n        return true;\n    }\n    if (fp > INT_MAX) {\n        set_error(out_errmsg, \"value is greater than INT_MAX (%d)\", (int) INT_MAX);\n        return false;\n    }\n    *out_res = (int) fp;\n    return true;\n}\n\nstatic bool apply_long_nonneg(NextRequestParams *dst, lua_State *L, CURLoption which, char **out_errmsg)\n{\n    int val;\n    if (!fetch_int(L, &val, out_errmsg)) {\n        return false;\n    }\n\n    if (val < 0) {\n        set_error(out_errmsg, \"value is negative\");\n    }\n\n    CURLcode rc = curl_easy_setopt(dst->C, which, (long) val);\n    if (rc != CURLE_OK) {\n        set_curl_error(out_errmsg, rc);\n        return false;\n    }\n    return true;\n}\n\nstatic bool apply_long_nonneg_or_minus1(NextRequestParams *dst, lua_State *L, CURLoption which, char **out_errmsg)\n{\n    int val;\n    if (!fetch_int(L, &val, out_errmsg)) {\n        return false;\n    }\n\n    CURLcode rc = curl_easy_setopt(dst->C, which, (long) val);\n    if (rc != CURLE_OK) {\n        set_curl_error(out_errmsg, rc);\n        return false;\n    }\n    return true;\n}\n\nstatic bool apply_headers(NextRequestParams *dst, lua_State *L, CURLoption which, char **out_errmsg)\n{\n    (void) which;\n\n    // This must be impossible. Still, let's check.\n    if (dst->headers != NULL) {\n        set_error(out_errmsg, \"the headers were passed multiple times, somehow\");\n        return false;\n    }\n\n    // L: ? something\n    if (!lua_istable(L, -1)) {\n        set_type_error(out_errmsg, L, -1, LUA_TTABLE, \"\");\n        return false;\n    }\n    // L: ? table\n\n    size_t n = ls_lua_array_len(L, -1);\n\n    for (size_t i = 1; i <= n; ++i) {\n        lua_rawgeti(L, -1, i); // L: ? table header\n\n        if (!lua_isstring(L, -1)) {\n            set_type_error(out_errmsg, L, -1, LUA_TSTRING, \"array element: \");\n            return false;\n        }\n        const char *s = lua_tostring(L, -1);\n        dst->headers = curl_slist_append(dst->headers, s);\n        if (!dst->headers) {\n            LS_PANIC(\"curl_slist_append() returned NULL: out of memory\");\n        }\n\n        lua_pop(L, 1); // L: ? table\n    }\n\n    CURLcode rc = curl_easy_setopt(dst->C, CURLOPT_HTTPHEADER, dst->headers);\n    if (rc != CURLE_OK) {\n        set_curl_error(out_errmsg, rc);\n        return false;\n    }\n    return true;\n}\n\nstatic bool apply_timeout(NextRequestParams *dst, lua_State *L, CURLoption which, char **out_errmsg)\n{\n    (void) which;\n\n    // L: ? value\n    if (!lua_isnumber(L, -1)) {\n        set_type_error(out_errmsg, L, -1, LUA_TNUMBER, \"\");\n        return false;\n    }\n\n    double fp = lua_tonumber(L, -1);\n    LS_TimeDelta TD;\n    if (!ls_double_to_TD_checked(fp, &TD)) {\n        set_error(out_errmsg, \"invalid timeout\");\n        return false;\n    }\n    int ms = ls_TD_to_poll_ms_tmo(TD);\n    if (ms < 0) {\n        ms = INT_MAX;\n    }\n\n    CURLcode rc = curl_easy_setopt(dst->C, CURLOPT_TIMEOUT_MS, (long) ms);\n    if (rc != CURLE_OK) {\n        set_curl_error(out_errmsg, rc);\n        return false;\n    }\n\n    return true;\n}\n\nconst Opt OPTS[] = {\n    {\"url\", apply_str, CURLOPT_URL},\n    {\"headers\", apply_headers, 0},\n    {\"timeout\", apply_timeout, 0},\n    {\"max_file_size\", apply_long_nonneg, CURLOPT_MAXFILESIZE},\n\n    {\"auto_referer\", apply_bool, CURLOPT_AUTOREFERER},\n    {\"custom_request\", apply_str, CURLOPT_CUSTOMREQUEST},\n    {\"follow_location\", apply_bool, CURLOPT_FOLLOWLOCATION},\n    {\"interface\", apply_str, CURLOPT_INTERFACE},\n    {\"max_redirs\", apply_long_nonneg_or_minus1, CURLOPT_MAXREDIRS},\n    {\"tcp_keepalive\", apply_bool, CURLOPT_TCP_KEEPALIVE},\n\n    {\"proxy\", apply_str, CURLOPT_PROXY},\n    {\"proxy_username\", apply_str, CURLOPT_PROXYUSERNAME},\n    {\"proxy_password\", apply_str, CURLOPT_PROXYPASSWORD},\n\n    {\"post_fields\", apply_str, CURLOPT_COPYPOSTFIELDS},\n};\n\nconst size_t OPTS_NUM = LS_ARRAY_SIZE(OPTS);\n"
  },
  {
    "path": "plugins/web/opts.h",
    "content": "/*\n * Copyright (C) 2026  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#pragma once\n\n#include <stddef.h>\n#include <stdbool.h>\n#include <lua.h>\n#include <curl/curl.h>\n#include \"next_request_params.h\"\n\ntypedef struct {\n    const char *spelling;\n\n    bool (*apply)(\n        NextRequestParams *dst,\n        lua_State *L,\n        CURLoption which,\n        char **out_errmsg);\n\n    CURLoption which;\n} Opt;\n\nextern const Opt OPTS[];\nextern const size_t OPTS_NUM;\n\nenum { REQUIRED_OPTION_INDEX = 0 };\n"
  },
  {
    "path": "plugins/web/parse_opts.c",
    "content": "/*\n * Copyright (C) 2026  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#include \"parse_opts.h\"\n#include <string.h>\n#include <stdlib.h>\n#include <lua.h>\n#include \"libls/ls_panic.h\"\n#include \"opts.h\"\n#include \"next_request_params.h\"\n#include \"set_error.h\"\n#include \"make_request.h\"\n\nstatic int find_opt(const char *spelling)\n{\n    for (size_t i = 0; i < OPTS_NUM; ++i) {\n        if (strcmp(spelling, OPTS[i].spelling) == 0) {\n            return i;\n        }\n    }\n    return -1;\n}\n\nstatic bool check_if_our_flag(NextRequestParams *dst, const char *s)\n{\n    if (strcmp(s, \"with_headers\") == 0) {\n        dst->local_req_flags |= REQ_FLAG_NEEDS_HEADERS;\n        return true;\n    }\n    if (strcmp(s, \"debug\") == 0) {\n        dst->local_req_flags |= REQ_FLAG_DEBUG;\n        return true;\n    }\n    return false;\n}\n\nstatic bool handle_option(NextRequestParams *dst, lua_State *L, char **out_errmsg, int *out_opt_idx)\n{\n    // L: ? key value\n    if (!lua_isstring(L, -2)) {\n        set_type_error(out_errmsg, L, -2, LUA_TSTRING, \"table key: \");\n        return false;\n    }\n\n    const char *s = lua_tostring(L, -2);\n    if (check_if_our_flag(dst, s)) {\n        *out_opt_idx = -1;\n        return true;\n    }\n\n    int opt_idx = find_opt(s);\n    if (opt_idx < 0) {\n        set_error(out_errmsg, \"unknown option '%s'\", s);\n        return false;\n    }\n    const Opt *opt = &OPTS[opt_idx];\n\n    char *nested_errmsg;\n    if (!opt->apply(dst, L, opt->which, &nested_errmsg)) {\n        set_error(out_errmsg, \"option '%s': %s\", s, nested_errmsg);\n        free(nested_errmsg);\n        return false;\n    }\n\n    *out_opt_idx = opt_idx;\n    return true;\n}\n\nbool parse_opts(NextRequestParams *dst, lua_State *L, char **out_errmsg)\n{\n    LS_ASSERT(dst->C != NULL);\n    LS_ASSERT(dst->headers == NULL);\n\n    LS_ASSERT(lua_istable(L, -1));\n\n    bool got_required_option = false;\n\n    // L: ? table\n    lua_pushnil(L); // L: ? table nil\n    while (lua_next(L, -2)) {\n        // L: ? table key value\n\n        int opt_idx;\n        if (!handle_option(dst, L, out_errmsg, &opt_idx)) {\n            return false;\n        }\n        if (opt_idx == REQUIRED_OPTION_INDEX) {\n            got_required_option = true;\n        }\n\n        lua_pop(L, 1); // L: ? table key\n    }\n    // L: ? table\n\n    if (!got_required_option) {\n        const Opt *opt = &OPTS[REQUIRED_OPTION_INDEX];\n        set_error(out_errmsg, \"missing required option '%s'\", opt->spelling);\n        return false;\n    }\n\n    return true;\n}\n"
  },
  {
    "path": "plugins/web/parse_opts.h",
    "content": "/*\n * Copyright (C) 2026  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#pragma once\n\n#include <stdbool.h>\n#include <lua.h>\n#include \"next_request_params.h\"\n\nbool parse_opts(NextRequestParams *dst, lua_State *L, char **out_errmsg);\n"
  },
  {
    "path": "plugins/web/set_error.c",
    "content": "/*\n * Copyright (C) 2026  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#include \"set_error.h\"\n\n#include <stdlib.h>\n#include <lua.h>\n#include <lauxlib.h>\n#include <curl/curl.h>\n#include \"libls/ls_panic.h\"\n\nvoid set_type_error(char **out_errmsg, lua_State *L, int pos, int expected_type, const char *prefix)\n{\n    LS_ASSERT(prefix != NULL);\n\n    set_error(\n        out_errmsg,\n        \"%s\" \"expected %s, found %s\",\n        prefix, lua_typename(L, expected_type), luaL_typename(L, pos)\n    );\n}\n\nvoid set_curl_error(char **out_errmsg, CURLcode rc)\n{\n    set_error(out_errmsg, \"libcurl error: %s\", curl_easy_strerror(rc));\n}\n"
  },
  {
    "path": "plugins/web/set_error.h",
    "content": "/*\n * Copyright (C) 2026  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#pragma once\n\n#include <lua.h>\n#include <curl/curl.h>\n#include \"libls/ls_xallocf.h\"\n\n#define set_error(out_errmsg, ...) (*(out_errmsg) = ls_xallocf(__VA_ARGS__))\n\nvoid set_type_error(char **out_errmsg, lua_State *L, int pos, int expected_type, const char *prefix);\n\nvoid set_curl_error(char **out_errmsg, CURLcode rc);\n"
  },
  {
    "path": "plugins/web/web.c",
    "content": "/*\n * Copyright (C) 2015-2026  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#include <lua.h>\n#include <errno.h>\n#include <stdlib.h>\n#include <unistd.h>\n#include <stdbool.h>\n#include <poll.h>\n#include <sys/types.h>\n\n#include \"include/plugin_v1.h\"\n#include \"include/sayf_macros.h\"\n\n#include \"libmoonvisit/moonvisit.h\"\n\n#include \"libls/ls_alloc_utils.h\"\n#include \"libls/ls_evloop_lfuncs.h\"\n#include \"libls/ls_io_utils.h\"\n#include \"libls/ls_time_utils.h\"\n#include \"libls/ls_string.h\"\n#include \"libls/ls_strarr.h\"\n#include \"libls/ls_lua_compat.h\"\n#include \"libls/ls_tls_ebuf.h\"\n\n#include \"next_request_params.h\"\n#include \"set_error.h\"\n#include \"parse_opts.h\"\n#include \"make_request.h\"\n#include \"opts.h\"\n#include \"compat_lua_resume.h\"\n\n#include \"mod_json/mod_json.h\"\n#include \"mod_urlencode/mod_urlencode.h\"\n\ntypedef struct {\n    int global_req_flags;\n\n    lua_State *coro;\n    int lref;\n\n    int pipefds[2];\n} Priv;\n\nstatic void destroy(LuastatusPluginData *pd)\n{\n    Priv *p = pd->priv;\n\n    ls_close(p->pipefds[0]);\n    ls_close(p->pipefds[1]);\n\n    free(p);\n}\n\nstatic lua_State *make_coro(lua_State *L, int *out_lref)\n{\n    // L: ? func\n    lua_State *coro = lua_newthread(L); // L: ? func thread\n    *out_lref = luaL_ref(L, LUA_REGISTRYINDEX); // L: ? func\n    lua_xmove(L, coro, 1);\n    // L: ?\n    // coro: func\n    return coro;\n}\n\nstatic bool parse_planner(MoonVisit *mv, Priv *p)\n{\n    lua_State *L = mv->L;\n    // L: ? table\n    lua_getfield(L, -1, \"planner\"); // L: ? table planner\n    if (moon_visit_checktype_at(mv, \"planner\", -1, LUA_TFUNCTION) < 0) {\n        return false;\n    }\n    p->coro = make_coro(L, &p->lref); // L: ? table\n    return true;\n}\n\nstatic int init(LuastatusPluginData *pd, lua_State *L)\n{\n    Priv *p = pd->priv = LS_XNEW(Priv, 1);\n    *p = (Priv) {\n        .global_req_flags = 0,\n        .coro = NULL,\n        .lref = LUA_REFNIL,\n        .pipefds = {-1, -1},\n    };\n\n    char errbuf[256];\n    MoonVisit mv = {.L = L, .errbuf = errbuf, .nerrbuf = sizeof(errbuf)};\n\n    // Parse with_headers_global\n    bool with_headers_global = false;\n    if (moon_visit_bool(&mv, -1, \"with_headers_global\", &with_headers_global, true) < 0) {\n        goto mverror;\n    }\n    if (with_headers_global) {\n        p->global_req_flags |= REQ_FLAG_NEEDS_HEADERS;\n    }\n\n    // Parse debug_global\n    bool debug_global = false;\n    if (moon_visit_bool(&mv, -1, \"debug_global\", &debug_global, true) < 0) {\n        goto mverror;\n    }\n    if (debug_global) {\n        p->global_req_flags |= REQ_FLAG_DEBUG;\n    }\n\n    // Parse make_self_pipe\n    bool mkpipe = false;\n    if (moon_visit_bool(&mv, -1, \"make_self_pipe\", &mkpipe, true) < 0) {\n        goto mverror;\n    }\n    if (mkpipe) {\n        if (ls_self_pipe_open(p->pipefds) < 0) {\n            LS_FATALF(pd, \"ls_self_pipe_open: %s\", ls_tls_strerror(errno));\n            goto error;\n        }\n    }\n\n    // Parse planner\n    if (!parse_planner(&mv, p)) {\n        goto mverror;\n    }\n\n    return LUASTATUS_OK;\n\nmverror:\n    LS_FATALF(pd, \"%s\", errbuf);\nerror:\n    destroy(pd);\n    return LUASTATUS_ERR;\n}\n\nstatic int l_get_supported_opts(lua_State *L)\n{\n    lua_createtable(L, 0, OPTS_NUM); // L: ? table\n    for (size_t i = 0; i < OPTS_NUM; ++i) {\n        lua_pushboolean(L, 1); // L: ? table true\n        lua_setfield(L, -2, OPTS[i].spelling); // L: ? table\n    }\n    return 1;\n}\n\nstatic int l_time_now(lua_State *L)\n{\n    double res = ls_timespec_to_raw_double(ls_now_timespec());\n    lua_pushnumber(L, res);\n    return 1;\n}\n\nstatic void register_funcs(LuastatusPluginData *pd, lua_State *L)\n{\n    Priv *p = pd->priv;\n    // L: table\n    ls_self_pipe_push_luafunc(p->pipefds, L); // L: table func\n    lua_setfield(L, -2, \"wake_up\"); // L: table\n\n    lua_pushcfunction(L, l_get_supported_opts); // L: table func\n    lua_setfield(L, -2, \"get_supported_opts\");\n\n    lua_pushcfunction(L, l_time_now); // L: table func\n    lua_setfield(L, -2, \"time_now\");\n\n    mod_json_register_funcs(L);\n\n    mod_urlencode_register_funcs(L);\n}\n\ntypedef enum {\n    NACT_REQUEST,\n    NACT_SLEEP,\n    NACT_CALL_CB,\n    NACT__LAST,\n} NextAction;\n\ntypedef enum {\n    WUPSTAT_NO_WAKEUP,\n    WUPSTAT_YES_WAKEUP,\n    WUPSTAT_NOT_APPLICABLE,\n} WakeupStatus;\n\ntypedef struct {\n    NextAction nact;\n\n    NextRequestParams next_req_params;\n    LS_TimeDelta TD;\n    char *what;\n\n    WakeupStatus wakeup_status;\n} Ctx;\n\nstatic void clear_next_req_params(NextRequestParams *X)\n{\n    if (X->headers) {\n        curl_slist_free_all(X->headers);\n        X->headers = NULL;\n    }\n    curl_easy_reset(X->C);\n    X->local_req_flags = 0;\n}\n\nstatic void destroy_ctx(Ctx *ctx)\n{\n    clear_next_req_params(&ctx->next_req_params);\n    free(ctx->what);\n    curl_easy_cleanup(ctx->next_req_params.C);\n}\n\nstatic bool parseY_request(lua_State *L, Ctx *ctx, char **out_errmsg)\n{\n    // L: ? table\n    lua_getfield(L, -1, \"params\"); // L: ? table params\n    if (!lua_istable(L, -1)) {\n        set_type_error(out_errmsg, L, -1, LUA_TTABLE, \"'params' field: \");\n        return false;\n    }\n    return parse_opts(&ctx->next_req_params, L, out_errmsg);\n}\n\nstatic bool parseY_sleep(lua_State *L, Ctx *ctx, char **out_errmsg)\n{\n    // L: ? table\n    lua_getfield(L, -1, \"period\"); // L: ? table period\n    if (!lua_isnumber(L, -1)) {\n        set_type_error(out_errmsg, L, -1, LUA_TNUMBER, \"'period' field: \");\n        return false;\n    }\n    double d = lua_tonumber(L, -1);\n    if (!ls_double_to_TD_checked(d, &ctx->TD)) {\n        set_error(out_errmsg, \"invalid period\");\n        return false;\n    }\n    return true;\n}\n\nstatic bool parseY_call_cb(lua_State *L, Ctx *ctx, char **out_errmsg)\n{\n    // L: ? table\n    lua_getfield(L, -1, \"what\"); // L: ? table what\n    if (!lua_isstring(L, -1)) {\n        set_type_error(out_errmsg, L, -1, LUA_TSTRING, \"'what' field: \");\n        return false;\n    }\n\n    free(ctx->what);\n    ctx->what = ls_xstrdup(lua_tostring(L, -1));\n\n    return true;\n}\n\nstatic NextAction parse_action(const char *s)\n{\n    if (strcmp(s, \"request\") == 0) {\n        return NACT_REQUEST;\n    }\n    if (strcmp(s, \"sleep\") == 0) {\n        return NACT_SLEEP;\n    }\n    if (strcmp(s, \"call_cb\") == 0) {\n        return NACT_CALL_CB;\n    }\n    return NACT__LAST;\n}\n\nstatic bool parseY(lua_State *L, Ctx *ctx, char **out_errmsg)\n{\n    if (!lua_istable(L, -1)) {\n        set_type_error(out_errmsg, L, -1, LUA_TTABLE, \"yielded value: \");\n        return false;\n    }\n    // L: ? table\n\n    lua_getfield(L, -1, \"action\"); // L: ? table action\n    if (!lua_isstring(L, -1)) {\n        set_type_error(out_errmsg, L, -1, LUA_TSTRING, \"'action' field: \");\n        return false;\n    }\n    ctx->nact = parse_action(lua_tostring(L, -1));\n    lua_pop(L, 1); // L: ? table\n\n    switch (ctx->nact) {\n    case NACT_REQUEST:\n        clear_next_req_params(&ctx->next_req_params);\n        return parseY_request(L, ctx, out_errmsg);\n\n    case NACT_SLEEP:\n        return parseY_sleep(L, ctx, out_errmsg);\n\n    case NACT_CALL_CB:\n        return parseY_call_cb(L, ctx, out_errmsg);\n\n    case NACT__LAST:\n        set_error(out_errmsg, \"unknown action\");\n        return false;\n    }\n    LS_MUST_BE_UNREACHABLE();\n}\n\nstatic inline void push_wakeup_status(lua_State *L, WakeupStatus wakeup_status)\n{\n    if (wakeup_status == WUPSTAT_NOT_APPLICABLE) {\n        lua_pushnil(L);\n    } else {\n        lua_pushboolean(L, wakeup_status == WUPSTAT_YES_WAKEUP);\n    }\n}\n\nstatic inline void push_planner_by_lref(Priv *p, lua_State *L, lua_State *coro)\n{\n    // L: ?\n    // coro: ?\n    lua_rawgeti(L, LUA_REGISTRYINDEX, p->lref); // L: ? thread\n    lua_xmove(L, coro, 1);\n    // L: ?\n    // coro: ? thread\n}\n\nstatic bool make_flash_call(\n    LuastatusPluginData *pd,\n    LuastatusPluginRunFuncs funcs,\n    Ctx *ctx)\n{\n    Priv *p = pd->priv;\n\n    lua_State *L = funcs.call_begin(pd->userdata);\n    lua_State *coro = p->coro;\n\n    int L_orig_top = lua_gettop(L);\n    int coro_orig_top = lua_gettop(coro);\n\n    char *errmsg = NULL;\n    bool is_ok;\n\n    // L: ?\n    push_wakeup_status(L, ctx->wakeup_status); // L: ? wakeup_status\n    ctx->wakeup_status = WUPSTAT_NOT_APPLICABLE;\n\n    int nresults;\n    push_planner_by_lref(p, L, coro); // coro: thread\n    int rc = compat_lua_resume(coro, L, 1, &nresults);\n    if (rc == LUA_YIELD) {\n        if (nresults != 1) {\n            set_error(&errmsg, \"expected 1 yielded value, got %d\", nresults);\n            is_ok = false;\n            goto done;\n        }\n        is_ok = parseY(coro, ctx, &errmsg);\n        goto done;\n    } else if (rc == 0) {\n        // coro: uh, something; doesn't matter actually\n        set_error(&errmsg, \"coroutine finished its execution\");\n        is_ok = false;\n        goto done;\n    } else {\n        // coro: err\n        const char *lua_errmsg = lua_tostring(coro, -1);\n        if (!lua_errmsg) {\n            lua_errmsg = \"(Lua object cannot be converted to string)\";\n        }\n        set_error(&errmsg, \"Lua error in planner: %s\", lua_errmsg);\n        is_ok = false;\n        goto done;\n    }\n\ndone:\n    lua_settop(coro, coro_orig_top); // coro: -\n    lua_settop(L, L_orig_top); // L: ?\n    funcs.call_cancel(pd->userdata);\n\n    if (!is_ok) {\n        LS_ASSERT(errmsg != NULL);\n        LS_FATALF(pd, \"%s\", errmsg);\n    }\n\n    free(errmsg);\n\n    return is_ok;\n}\n\nstatic void action_call_cb(\n    LuastatusPluginData *pd,\n    LuastatusPluginRunFuncs funcs,\n    Ctx *ctx)\n{\n    lua_State *L = funcs.call_begin(pd->userdata);\n\n    // L: ?\n    lua_createtable(L, 0, 1); // L: ? table\n    lua_pushstring(L, ctx->what); // L: ? table str\n    lua_setfield(L, -2, \"what\"); // L: ? table\n\n    funcs.call_end(pd->userdata);\n\n    free(ctx->what);\n    ctx->what = NULL;\n}\n\nstatic void push_headers(lua_State *L, LS_StringArray headers)\n{\n    size_t n = ls_strarr_size(headers);\n\n    if (n > (size_t) LS_LUA_MAXI) {\n        lua_pushnil(L);\n        return;\n    }\n\n    lua_createtable(L, ls_lua_num_prealloc(n), 0); // L: ? table headers\n\n    for (size_t i = 0; i < n; ++i) {\n        size_t ns;\n        const char *s = ls_strarr_at(headers, i, &ns);\n        lua_pushlstring(L, s, ns); // L: ? table headers str\n        lua_rawseti(L, -2, i + 1); // L: ? table headers\n    }\n}\n\nstatic void report_request_result_ok(\n    LuastatusPluginData *pd, LuastatusPluginRunFuncs funcs,\n    long status,\n    const char *body, size_t nbody,\n    const LS_StringArray *p_headers)\n{\n    lua_State *L = funcs.call_begin(pd->userdata);\n\n    // L: ?\n    lua_createtable(L, 0, 3); // L: ? table\n\n    lua_pushstring(L, \"response\"); // L: ? table str\n    lua_setfield(L, -2, \"what\"); // L: ? table\n\n    lua_pushinteger(L, status); // L: ? table status\n    lua_setfield(L, -2, \"status\"); // L: ? table\n\n    if (p_headers) {\n        push_headers(L, *p_headers);\n        lua_setfield(L, -2, \"headers\"); // L: ? table\n    }\n\n    lua_pushlstring(L, body, nbody); // L: ? table body\n    lua_setfield(L, -2, \"body\"); // L: ? table\n\n    funcs.call_end(pd->userdata);\n}\n\nstatic void report_request_result_error(\n    LuastatusPluginData *pd, LuastatusPluginRunFuncs funcs,\n    const char *err_descr,\n    bool with_headers)\n{\n    lua_State *L = funcs.call_begin(pd->userdata);\n\n    // L: ?\n    lua_createtable(L, 0, 4); // L: ? table\n\n    lua_pushstring(L, \"response\"); // L: ? table str\n    lua_setfield(L, -2, \"what\"); // L: ? table\n\n    lua_pushinteger(L, 0); // L: ? table status\n    lua_setfield(L, -2, \"status\"); // L: ? table\n\n    lua_pushstring(L, \"\"); // L: ? table body\n    lua_setfield(L, -2, \"body\"); // L: ? table\n\n    if (with_headers) {\n        lua_newtable(L); // L: ? table headers\n        lua_setfield(L, -2, \"headers\"); // L: ? table\n    }\n\n    lua_pushstring(L, err_descr); // L: ? table body\n    lua_setfield(L, -2, \"error\"); // L: ? table\n\n    funcs.call_end(pd->userdata);\n}\n\nstatic void action_request(\n    LuastatusPluginData *pd,\n    LuastatusPluginRunFuncs funcs,\n    Ctx *ctx)\n{\n    Priv *p = pd->priv;\n\n    int req_flags = p->global_req_flags | ctx->next_req_params.local_req_flags;\n\n    bool with_headers = req_flags & REQ_FLAG_NEEDS_HEADERS;\n\n    Response resp;\n    char *errmsg;\n    if (make_request(pd, req_flags, ctx->next_req_params.C, &resp, &errmsg)) {\n        report_request_result_ok(\n            pd, funcs,\n            resp.status,\n            resp.body.data, resp.body.size,\n            with_headers ? &resp.headers : NULL);\n    } else {\n        report_request_result_error(pd, funcs, errmsg, with_headers);\n        free(errmsg);\n    }\n\n    ls_string_free(resp.body);\n    ls_strarr_destroy(resp.headers);\n}\n\nstatic void action_sleep(LuastatusPluginData *pd, Ctx *ctx)\n{\n    Priv *p = pd->priv;\n\n    if (p->pipefds[0] >= 0) {\n        int rc = ls_wait_input_on_fd(p->pipefds[0], ctx->TD);\n        if (rc < 0) {\n            LS_PANIC_WITH_ERRNUM(\"ls_wait_input_on_fd() failed\", errno);\n        }\n        if (rc) {\n            char ignored;\n            ssize_t num_read = read(p->pipefds[0], &ignored, 1);\n            (void) num_read;\n\n            ctx->wakeup_status = WUPSTAT_YES_WAKEUP;\n        } else {\n            ctx->wakeup_status = WUPSTAT_NO_WAKEUP;\n        }\n    } else {\n        ls_sleep(ctx->TD);\n    }\n}\n\nstatic void run(LuastatusPluginData *pd, LuastatusPluginRunFuncs funcs)\n{\n    CURL *C = curl_easy_init();\n    if (!C) {\n        LS_FATALF(pd, \"curl_easy_init() failed\");\n        return;\n    }\n\n    Ctx ctx = {\n        .nact = NACT__LAST,\n        .next_req_params = {\n            .C = C,\n            .headers = NULL,\n            .local_req_flags = 0,\n        },\n        .TD = {0},\n        .what = NULL,\n        .wakeup_status = WUPSTAT_NOT_APPLICABLE,\n    };\n\n    for (;;) {\n        if (!make_flash_call(pd, funcs, &ctx)) {\n            break;\n        }\n        switch (ctx.nact) {\n        case NACT_REQUEST:\n            action_request(pd, funcs, &ctx);\n            break;\n        case NACT_SLEEP:\n            action_sleep(pd, &ctx);\n            break;\n        case NACT_CALL_CB:\n            action_call_cb(pd, funcs, &ctx);\n            break;\n        case NACT__LAST:\n            LS_MUST_BE_UNREACHABLE();\n        }\n    }\n\n    destroy_ctx(&ctx);\n}\n\nLuastatusPluginIface luastatus_plugin_iface_v1 = {\n    .init = init,\n    .register_funcs = register_funcs,\n    .run = run,\n    .destroy = destroy,\n};\n"
  },
  {
    "path": "plugins/xkb/CMakeLists.txt",
    "content": "file (GLOB sources \"*.c\")\nluastatus_add_plugin (plugin-xkb $<TARGET_OBJECTS:ls> $<TARGET_OBJECTS:moonvisit> ${sources})\n\ntarget_compile_definitions (plugin-xkb PUBLIC -D_POSIX_C_SOURCE=200809L)\nluastatus_target_compile_with (plugin-xkb LUA)\ntarget_include_directories (plugin-xkb PUBLIC \"${PROJECT_SOURCE_DIR}\")\n\nfind_package (PkgConfig REQUIRED)\npkg_check_modules (xstuff REQUIRED x11)\nluastatus_target_build_with (plugin-xkb xstuff)\n\nluastatus_add_man_page (README.rst luastatus-plugin-xkb 7)\n"
  },
  {
    "path": "plugins/xkb/README.rst",
    "content": ".. :X-man-page-only: luastatus-plugin-xkb\n.. :X-man-page-only: ####################\n.. :X-man-page-only:\n.. :X-man-page-only: ######################################\n.. :X-man-page-only: X keyboard layout plugin for luastatus\n.. :X-man-page-only: ######################################\n.. :X-man-page-only:\n.. :X-man-page-only: :Copyright: LGPLv3\n.. :X-man-page-only: :Manual section: 7\n\nOverview\n========\nThis plugin monitors current keyboard layout, and, optionally, the state of LED indicators (such as\n\"Caps Lock\" and \"Num Lock\").\n\nOptions\n=======\nThe following options are supported:\n\n* ``display``: string\n\n  Display to connect to. Default is to use ``DISPLAY`` environment variable.\n\n* ``device_id``: number\n\n  Keyboard device ID (as shown by ``xinput(1)``). Default is ``XkbUseCoreKbd``.\n\n* ``led``: boolean\n\n  Also report (and subscribe to changes of) the state of LED indicators, such as \"Caps Lock\" and\n  \"Num Lock\".\n\n* ``how``: string\n\n  This option controls which method of obtaining the list of group names is used.\n  Currently, there are two methods.\n\n  The first one, codenamed \"*wrongly*\", is the default one; it consists of\n  querying and parsing the ``_XKB_RULES_NAMES`` property of the root window.\n  This method is known to be wrong, and on current Debian Sid it does not work as expected:\n  on a setup with two keyboard layouts, English and Russian, it reports only the English one.\n  Specify ``how=\"wrongly\"`` to use this method.\n\n  The second one, codenamed \"*somehow*\", consists of calling\n  ``XkbGetNames(..., XkbSymbolsNameMask, ...)`` in order to obtain a string called \"symbols\",\n  which looks like this::\n\n      pc+us+ru(winkeys):2+inet(evdev)+group(rctrl_toggle)+level3(ralt_switch)+capslock(ctrl_modifier)+typ\n\n  Note that this string has been truncated to exactly 99 characters (the end should have been\n  ``typo`` and then probably something else).\n  It was not me who truncated it, but rather the guts of X11. And I have no idea why.\n  The fact that this string can be truncated certainly does not contribute to the reliability of\n  this method.\n  We obtain the list of group names, then, by splitting the \"symbols\" by plus signs, and then\n  filtering out known \"bad\" symbols (that do not indicate a keyboard layout).\n  This is quite unreliable, but somehow works.\n  Specify ``how=\"somehow\"`` to use this method.\n\n* ``somehow_bad``: string\n\n  Comma-separated list of bad symbols for the \"somehow\" method (note that normally it should\n  not include spaces). Set to empty string to express an empty list.\n  The default value is ``group,inet,pc``.\n\n``cb`` argument\n===============\nA table with the following entries:\n\n* ``name``: string\n\n  Group name (if number of group names reported is sufficient).\n\n* ``id``: number\n\n  Group ID (0, 1, 2, or 3).\n\n* ``requery``: boolean\n\n  True if either this is the first call, or this call is due to a change in keyboard geometry, the\n  *list* of layouts, or similar event that requires re-query of the list of group names.\n  Otherwise, ``requery`` is nil. This is useful for widgets that fetch the list of group names via\n  some external mechanism (e.g. by parsing the output of a command): they need to re-query the\n  list if ``requery`` is true.\n\n* ``led_state``: number\n\n  Bit mask representing the current state of LED indicators.\n\n  On virtually all setups,\n  bit ``(1 << 0) = 1`` is \"Caps Lock\",\n  bit ``(1 << 1) = 2`` is \"Num Lock\",\n  bit ``(1 << 2) = 4`` is \"Scroll Lock\".\n\n  To list all indicators your X server knows of, run ``xset q``.\n  If you look at the output, you will note there are a lot of weird things that are, in some\n  reason, also considered indicators; for example, \"Group 2\" (that is, alternative keyboard\n  layout) corresponds to ``(1 << 12) = 4096`` on my setup. So you should not assume that, just\n  because your physical keyboard has no indicators for other things (or no indicators at all),\n  the mask will never contain anything other than \"Caps Lock\", \"Num Lock\" and \"Scroll Lock\".\n\n  Note that bitwise operations were only introduced in Lua 5.3.\n  The \"lowest common denominator\" (working on all Lua versions) check if a bit is set is\n  the following::\n\n      function is_set(mask, bit)\n          return mask % (2 * bit) >= bit\n      end\n\n  Use it as follows::\n\n      cb = function(t)\n          -- ...do something...\n\n          if is_set(t.led_state, 1) then\n              -- \"Caps Lock\" is ON\n              -- ...do something...\n          end\n\n          if is_set(t.led_state, 2) then\n              -- \"Num Lock\" is ON\n              -- ...do something...\n          end\n\n          if is_set(t.led_state, 4) then\n              -- \"Scroll Lock\" is ON\n              -- ...do something...\n          end\n\n          -- ...do something...\n      end,\n"
  },
  {
    "path": "plugins/xkb/somehow.c",
    "content": "/*\n * Copyright (C) 2021-2025  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#include \"somehow.h\"\n\n#include <X11/X.h>\n#include <X11/Xlib.h>\n#include <X11/extensions/XKB.h>\n#include <X11/extensions/XKBstr.h>\n#include <X11/XKBlib.h>\n#include <stdbool.h>\n#include <string.h>\n#include <stdlib.h>\n#include \"libls/ls_alloc_utils.h\"\n\nchar *somehow_fetch_symbols(Display *dpy, uint64_t deviceid)\n{\n    char *ret = NULL;\n\n    XkbDescRec *k = XkbAllocKeyboard();\n    if (!k)\n        goto done;\n\n    k->dpy = dpy;\n    if (deviceid != XkbUseCoreKbd)\n        k->device_spec = deviceid;\n\n    XkbGetNames(dpy, XkbSymbolsNameMask, k);\n    if (!k->names)\n        goto done;\n\n    Atom a = k->names->symbols;\n    if (a == None)\n        goto done;\n\n    char *s = XGetAtomName(dpy, a);\n    if (s) {\n        ret = ls_xstrdup(s);\n        XFree(s);\n    }\n\ndone:\n    if (k) {\n        XkbFreeNames(k, XkbSymbolsNameMask, True);\n        XFree(k);\n    }\n    return ret;\n}\n\n// Assumes that /s[nbad]/ and /bad[nbad]/ are defined and set to either the next symbol or '\\0'.\nstatic inline bool bad_matches(const char *bad, size_t nbad, const char *s, size_t ns)\n{\n    if (ns >= nbad && memcmp(bad, s, nbad) == 0) {\n        if (ns == nbad)\n            return true;\n\n        char c = s[nbad];\n        if (c == '(' || c == ':')\n            return true;\n    }\n    return false;\n}\n\nstatic inline bool check_bad(const char *bad, const char *s, size_t ns)\n{\n    if (!ns)\n        return true;\n    if (bad[0] == '\\0')\n        return false;\n    for (const char *bad_next; (bad_next = strchr(bad, ',')); bad = bad_next + 1)\n        if (bad_matches(bad, bad_next - bad, s, ns))\n            return true;\n    return bad_matches(bad, strlen(bad), s, ns);\n}\n\nvoid somehow_parse_symbols(const char *symbols, LS_StringArray *out, const char *bad)\n{\n    const char *prev = symbols;\n    size_t balance = 0;\n    for (;; ++symbols) {\n        switch (*symbols) {\n        case '(':\n            ++balance;\n            break;\n        case ')':\n            --balance;\n            break;\n        case '+':\n            if (balance == 0) {\n                if (!check_bad(bad, prev, symbols - prev)) {\n                    ls_strarr_append(out, prev, symbols - prev);\n                }\n                prev = symbols + 1;\n            }\n            break;\n        case '\\0':\n            if (!check_bad(bad, prev, symbols - prev)) {\n                ls_strarr_append(out, prev, symbols - prev);\n            }\n            return;\n        }\n    }\n}\n"
  },
  {
    "path": "plugins/xkb/somehow.h",
    "content": "/*\n * Copyright (C) 2021-2025  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifndef somehow_h_\n#define somehow_h_\n\n#include <X11/Xlib.h>\n#include <stdint.h>\n\n#include \"libls/ls_strarr.h\"\n\n// Returns either NULL or a NUL-terminated string allocated as if with /malloc()/.\nchar *somehow_fetch_symbols(Display *dpy, uint64_t deviceid);\n\nvoid somehow_parse_symbols(const char *symbols, LS_StringArray *out, const char *bad);\n\n#endif\n"
  },
  {
    "path": "plugins/xkb/wrongly.c",
    "content": "/*\n * Copyright (C) 2015-2025  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#include \"wrongly.h\"\n\n#include <stdbool.h>\n#include <string.h>\n#include <stdlib.h>\n#include <limits.h>\n#include <X11/X.h>\n#include <X11/Xatom.h>\n\n#include \"libls/ls_alloc_utils.h\"\n\nstatic const char *NAMES_PROP_ATOM = \"_XKB_RULES_NAMES\";\n\nstatic inline char *dup_and_advance(const char **pcur, const char *end)\n{\n    const char *cur = *pcur;\n    if (cur == end)\n        return NULL;\n\n    size_t n = strlen(cur);\n    *pcur += n + 1;\n    return ls_xmemdup(cur, n + 1);\n}\n\nbool wrongly_fetch(Display *dpy, WronglyResult *out)\n{\n    bool ret = false;\n    unsigned char *data = NULL;\n\n    Atom rules_atom = XInternAtom(dpy, NAMES_PROP_ATOM, True);\n    if (rules_atom == None)\n        goto done;\n\n    unsigned long ndata;\n    long maxlen = 1024;\n    for (;;) {\n        Atom actual_type;\n        int fmt;\n        unsigned long bytes_after;\n        if (XGetWindowProperty(\n                    dpy,\n                    DefaultRootWindow(dpy),\n                    rules_atom,\n                    0L,\n                    maxlen,\n                    False,\n                    XA_STRING,\n                    &actual_type,\n                    &fmt,\n                    &ndata,\n                    &bytes_after,\n                    &data)\n                != Success)\n        {\n            data = NULL;\n            goto done;\n        }\n        if (actual_type != XA_STRING)\n            goto done;\n        if (fmt != 8)\n            goto done;\n\n        if (!bytes_after)\n            break;\n\n        if (maxlen > LONG_MAX / 2)\n            goto done;\n        maxlen *= 2;\n    }\n\n    const char *cur = (const char *) data;\n    const char *end = cur + ndata;\n\n    out->rules = dup_and_advance(&cur, end);\n    out->model = dup_and_advance(&cur, end);\n    out->layout = dup_and_advance(&cur, end);\n    out->options = dup_and_advance(&cur, end);\n\n    ret = true;\ndone:\n    if (data)\n        XFree(data);\n    return ret;\n}\n\nvoid wrongly_parse_layout(const char *layout, LS_StringArray *out)\n{\n    const char *prev = layout;\n    size_t balance = 0;\n    for (;; ++layout) {\n        switch (*layout) {\n        case '(':\n            ++balance;\n            break;\n        case ')':\n            --balance;\n            break;\n        case ',':\n            if (balance == 0) {\n                ls_strarr_append(out, prev, layout - prev);\n                prev = layout + 1;\n            }\n            break;\n        case '\\0':\n            ls_strarr_append(out, prev, layout - prev);\n            return;\n        }\n    }\n}\n"
  },
  {
    "path": "plugins/xkb/wrongly.h",
    "content": "/*\n * Copyright (C) 2015-2025  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifndef wrongly_h_\n#define wrongly_h_\n\n#include <X11/Xlib.h>\n#include <stdbool.h>\n\n#include \"libls/ls_strarr.h\"\n\ntypedef struct {\n    // These all are either zero-terminated strings allocated as if with /malloc()/, or /NULL/.\n    char *rules;\n    char *model;\n    char *layout;\n    char *options;\n} WronglyResult;\n\nbool wrongly_fetch(Display *dpy, WronglyResult *out);\n\nvoid wrongly_parse_layout(const char *layout, LS_StringArray *out);\n\n#endif\n"
  },
  {
    "path": "plugins/xkb/xkb.c",
    "content": "/*\n * Copyright (C) 2015-2025  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#include <X11/X.h>\n#include <X11/Xlib.h>\n#include <X11/extensions/XKB.h>\n#include <X11/XKBlib.h>\n#include <X11/extensions/XKBstr.h>\n\n#include <stdint.h>\n#include <stdbool.h>\n#include <stdlib.h>\n#include <string.h>\n#include <lua.h>\n\n#include \"include/plugin_v1.h\"\n#include \"include/sayf_macros.h\"\n\n#include \"libmoonvisit/moonvisit.h\"\n\n#include \"libls/ls_alloc_utils.h\"\n#include \"libls/ls_strarr.h\"\n\n#include \"wrongly.h\"\n#include \"somehow.h\"\n\n// If this plugin is used, the whole process gets killed if a connection to the display is lost,\n// because Xlib is terrible.\n//\n// See:\n// * https://tronche.com/gui/x/xlib/event-handling/protocol-errors/XSetIOErrorHandler.html\n// * https://tronche.com/gui/x/xlib/event-handling/protocol-errors/XSetErrorHandler.html\n\ntypedef enum {\n    HOW_WRONGLY,\n    HOW_SOMEHOW,\n} How;\n\ntypedef struct {\n    char *dpyname;\n    uint64_t deviceid;\n    How how;\n    char *somehow_bad;\n    bool led;\n} Priv;\n\nstatic int parse_how_str(MoonVisit *mv, void *ud, const char *s, size_t ns)\n{\n    (void) ns;\n    How *out = ud;\n    if (strcmp(s, \"wrongly\") == 0) {\n        *out = HOW_WRONGLY;\n        return 1;\n    }\n    if (strcmp(s, \"somehow\") == 0) {\n        *out = HOW_SOMEHOW;\n        return 1;\n    }\n    moon_visit_err(mv, \"unknown how string: '%s'\", s);\n    return -1;\n}\n\nstatic void destroy(LuastatusPluginData *pd)\n{\n    Priv *p = pd->priv;\n    free(p->dpyname);\n    free(p->somehow_bad);\n    free(p);\n}\n\nstatic int init(LuastatusPluginData *pd, lua_State *L)\n{\n    Priv *p = pd->priv = LS_XNEW(Priv, 1);\n    *p = (Priv) {\n        .dpyname = NULL,\n        .deviceid = XkbUseCoreKbd,\n        .how = HOW_WRONGLY,\n        .somehow_bad = NULL,\n        .led = false,\n    };\n\n    char errbuf[256];\n    MoonVisit mv = {.L = L, .errbuf = errbuf, .nerrbuf = sizeof(errbuf)};\n\n    // Parse display\n    if (moon_visit_str(&mv, -1, \"display\", &p->dpyname, NULL, true) < 0)\n        goto mverror;\n\n    // Parse device_id\n    if (moon_visit_uint(&mv, -1, \"device_id\", &p->deviceid, true) < 0)\n        goto mverror;\n\n    // Parse how\n    if (moon_visit_str_f(&mv, -1, \"how\", parse_how_str, &p->how, true) < 0)\n        goto mverror;\n\n    // Parse somehow_bad\n    if (moon_visit_str(&mv, -1, \"somehow_bad\", &p->somehow_bad, NULL, true) < 0)\n        goto mverror;\n\n    if (!p->somehow_bad)\n        p->somehow_bad = ls_xstrdup(\"group,inet,pc\");\n\n    // Parse led\n    if (moon_visit_bool(&mv, -1, \"led\", &p->led, true) < 0)\n        goto mverror;\n\n    // Call 'XInitThreads()' if we are the first entity to use libx11.\n    static char dummy[1];\n    void **ptr = pd->map_get(pd->userdata, \"flag:library_used:x11\");\n    if (!*ptr) {\n        if (!XInitThreads()) {\n            LS_FATALF(pd, \"XInitThreads() failed\");\n            goto error;\n        }\n        *ptr = dummy;\n    }\n\n    return LUASTATUS_OK;\n\nmverror:\n    LS_FATALF(pd, \"%s\", errbuf);\nerror:\n    destroy(pd);\n    return LUASTATUS_ERR;\n}\n\nstatic Display *open_dpy(\n        LuastatusPluginData *pd,\n        char *dpyname,\n        int *ext_base_ev_code)\n{\n    XkbIgnoreExtension(False);\n\n    int event_out;\n    int error_out;\n    int reason_out;\n    int major_in_out = XkbMajorVersion;\n    int minor_in_out = XkbMinorVersion;\n\n    Display *dpy = XkbOpenDisplay(\n        dpyname, &event_out, &error_out, &major_in_out, &minor_in_out, &reason_out);\n\n    if (dpy && reason_out == XkbOD_Success) {\n        *ext_base_ev_code = event_out;\n        return dpy;\n    }\n\n    const char *msg;\n    switch (reason_out) {\n    case XkbOD_BadLibraryVersion:\n        msg = \"bad XKB library version\";\n        break;\n    case XkbOD_ConnectionRefused:\n        msg = \"can't open display, connection refused\";\n        break;\n    case XkbOD_BadServerVersion:\n        msg = \"server has an incompatible extension version\";\n        break;\n    case XkbOD_NonXkbServer:\n        msg = \"extension is not present in the server\";\n        break;\n    default:\n        msg = \"unknown error\";\n        break;\n    }\n    LS_FATALF(pd, \"XkbOpenDisplay() failed: %s\", msg);\n    return NULL;\n}\n\nstatic bool query_wrongly(LuastatusPluginData *pd, Display *dpy, LS_StringArray *groups)\n{\n    ls_strarr_clear(groups);\n\n    WronglyResult res;\n    if (!wrongly_fetch(dpy, &res)) {\n        LS_WARNF(pd, \"[wrongly] wrongly_fetch() failed\");\n        return false;\n    }\n\n    if (res.layout) {\n        LS_DEBUGF(pd, \"[wrongly] layout string: %s\", res.layout);\n        wrongly_parse_layout(res.layout, groups);\n    } else {\n        LS_DEBUGF(pd, \"[wrongly] layout string is NULL\");\n    }\n\n    free(res.rules);\n    free(res.model);\n    free(res.layout);\n    free(res.options);\n\n    return true;\n}\n\nstatic bool query_somehow(LuastatusPluginData *pd, Display *dpy, LS_StringArray *groups)\n{\n    ls_strarr_clear(groups);\n\n    Priv *p = pd->priv;\n    char *res = somehow_fetch_symbols(dpy, p->deviceid);\n    if (!res) {\n        LS_WARNF(pd, \"[somehow] somehow_fetch_symbols() failed\");\n        return false;\n    }\n\n    LS_DEBUGF(pd, \"[somehow] symbols string: %s\", res);\n    somehow_parse_symbols(res, groups, p->somehow_bad);\n\n    free(res);\n\n    return true;\n}\n\nstatic inline bool query(LuastatusPluginData *pd, Display *dpy, LS_StringArray *groups)\n{\n    Priv *p = pd->priv;\n    switch (p->how) {\n    case HOW_WRONGLY:\n        return query_wrongly(pd, dpy, groups);\n    case HOW_SOMEHOW:\n        return query_somehow(pd, dpy, groups);\n    }\n    LS_MUST_BE_UNREACHABLE();\n}\n\nstatic void run(LuastatusPluginData *pd, LuastatusPluginRunFuncs funcs)\n{\n    Priv *p = pd->priv;\n    LS_StringArray groups = ls_strarr_new();\n    Display *dpy = NULL;\n    int ext_base_ev_code;\n\n    if (!(dpy = open_dpy(pd, p->dpyname, &ext_base_ev_code)))\n        goto error;\n\n    // So, the X server maintains the bit mask of events we are interesed in; we can alter this mask\n    // for XKB-related events with 'XkbSelectEvents()' and 'XkbSelectEventDetails()'.\n    // Both of those functions take\n    //    'unsigned long bits_to_change,\n    //     unsigned long values_for_bits'\n    // as the last two arguments. What it means is that, by invoking those functions, we say that we\n    // only want to *change* the bits in our current event mask that are *set* in 'bits_to_change';\n    // and that we want to assign them the *values* of corresponding bits in 'values_for_bits'.\n    //\n    // In other words, the algorithm for the server is something like this:\n    //\n    //     // clear bits that the client wants to be changed\n    //     mask &= ~bits_to_change;\n    //\n    //     // set bits that the client wants to set\n    //     mask |= (values_for_bits & bits_to_change);\n\n    // These are events that any XKB client *must* listen to, unrelated to layout/LED.\n    const unsigned long BOILERPLATE_EVENTS_MASK =\n        XkbNewKeyboardNotifyMask |\n        XkbMapNotifyMask;\n\n    // These are events related to LED.\n    const unsigned long LED_EVENTS_MASK =\n        XkbIndicatorStateNotifyMask |\n        XkbIndicatorMapNotifyMask;\n\n    if (XkbSelectEvents(\n                dpy,\n                p->deviceid,\n                /*bits_to_change=*/BOILERPLATE_EVENTS_MASK,\n                /*values_for_bits=*/BOILERPLATE_EVENTS_MASK)\n            == False)\n    {\n        LS_FATALF(pd, \"XkbSelectEvents() failed (boilerplate events)\");\n        goto error;\n    }\n\n    if (XkbSelectEventDetails(\n                dpy,\n                p->deviceid,\n                /*event_type=*/XkbStateNotify,\n                /*bits_to_change=*/XkbAllStateComponentsMask,\n                /*values_for_bits=*/XkbGroupStateMask)\n            == False)\n    {\n        LS_FATALF(pd, \"XkbSelectEventDetails() failed (layout events)\");\n        goto error;\n    }\n\n    if (p->led) {\n        if (XkbSelectEvents(\n                dpy,\n                p->deviceid,\n                /*bits_to_change=*/LED_EVENTS_MASK,\n                /*values_for_bits=*/LED_EVENTS_MASK)\n            == False)\n        {\n            LS_FATALF(pd, \"XkbSelectEvents() failed (LED events)\");\n            goto error;\n        }\n    }\n\n    bool requery_everything = true;\n    size_t group = -1;\n    unsigned led_state = -1;\n\n    while (1) {\n        // re-query everything, if needed\n        if (requery_everything) {\n            // re-query group names\n            if (!query(pd, dpy, &groups)) {\n                LS_WARNF(pd, \"query() failed\");\n            }\n\n            // explicitly query current group\n            XkbStateRec state;\n            if (XkbGetState(dpy, p->deviceid, &state) != Success) {\n                LS_FATALF(pd, \"XkbGetState() failed\");\n                goto error;\n            }\n            group = state.group;\n\n            // check if group is valid and possibly re-query group names\n            if (group >= ls_strarr_size(groups)) {\n                LS_WARNF(pd, \"group ID (%zu) is too large, requerying\", group);\n\n                if (!query(pd, dpy, &groups)) {\n                    LS_WARNF(pd, \"query() failed\");\n                }\n\n                if (group >= ls_strarr_size(groups)) {\n                    LS_WARNF(pd, \"group ID is still too large\");\n                }\n            }\n\n            // explicitly query current state of LED indicators\n            if (p->led) {\n                if (XkbGetIndicatorState(dpy, p->deviceid, &led_state) != Success) {\n                    LS_FATALF(pd, \"XkbGetIndicatorState() failed\");\n                    goto error;\n                }\n            }\n        }\n\n        // make a call\n        lua_State *L = funcs.call_begin(pd->userdata); // L: -\n        lua_createtable(L, 0, 2); // L: table\n        lua_pushinteger(L, group); // L: table n\n        lua_setfield(L, -2, \"id\"); // L: table\n        if (group < ls_strarr_size(groups)) {\n            size_t nbuf;\n            const char *buf = ls_strarr_at(groups, group, &nbuf);\n            lua_pushlstring(L, buf, nbuf); // L: table name\n            lua_setfield(L, -2, \"name\"); // L: table\n        }\n        if (p->led) {\n            lua_pushinteger(L, led_state); // L: table n\n            lua_setfield(L, -2, \"led_state\"); // L: table\n        }\n        if (requery_everything) {\n            lua_pushboolean(L, true); // L: table true\n            lua_setfield(L, -2, \"requery\"); // L: table\n        }\n        funcs.call_end(pd->userdata);\n\n        // wait for next event\n        XEvent event = {0};\n        XNextEvent(dpy, &event);\n\n        // interpret the event\n        requery_everything = false;\n        if (event.type == ext_base_ev_code) {\n\n            XkbAnyEvent ev1;\n            memcpy(&ev1, &event, sizeof(ev1));\n\n            switch (ev1.xkb_type) {\n            case XkbNewKeyboardNotify:\n            case XkbMapNotify:\n                requery_everything = true;\n                break;\n            case XkbStateNotify:\n                {\n                    XkbStateNotifyEvent ev2;\n                    memcpy(&ev2, &event, sizeof(ev2));\n                    group = ev2.group;\n                }\n                break;\n            case XkbIndicatorStateNotify:\n            case XkbIndicatorMapNotify:\n                {\n                    XkbIndicatorNotifyEvent ev2;\n                    memcpy(&ev2, &event, sizeof(ev2));\n                    led_state = ev2.state;\n                }\n                break;\n            default:\n                LS_WARNF(pd, \"got XKB event of unknown xkb_type %d\", ev1.xkb_type);\n            }\n        } else {\n            LS_WARNF(pd, \"got X event of unknown type %d\", event.type);\n        }\n    }\n\nerror:\n    if (dpy)\n        XCloseDisplay(dpy);\n    ls_strarr_destroy(groups);\n}\n\nLuastatusPluginIface luastatus_plugin_iface_v1 = {\n    .init = init,\n    .run = run,\n    .destroy = destroy,\n};\n"
  },
  {
    "path": "plugins/xtitle/CMakeLists.txt",
    "content": "file (GLOB sources \"*.c\")\nluastatus_add_plugin (plugin-xtitle $<TARGET_OBJECTS:ls> $<TARGET_OBJECTS:moonvisit> ${sources})\n\ntarget_compile_definitions (plugin-xtitle PUBLIC -D_POSIX_C_SOURCE=200809L)\nluastatus_target_compile_with (plugin-xtitle LUA)\ntarget_include_directories (plugin-xtitle PUBLIC \"${PROJECT_SOURCE_DIR}\")\n\nfind_package (PkgConfig REQUIRED)\npkg_check_modules (XCBLIBS REQUIRED xcb xcb-ewmh xcb-icccm xcb-event)\nluastatus_target_build_with (plugin-xtitle XCBLIBS)\n\nluastatus_add_man_page (README.rst luastatus-plugin-xtitle 7)\n"
  },
  {
    "path": "plugins/xtitle/README.rst",
    "content": ".. :X-man-page-only: luastatus-plugin-xtitle\n.. :X-man-page-only: #######################\n.. :X-man-page-only:\n.. :X-man-page-only: ########################################\n.. :X-man-page-only: active window title plugin for luastatus\n.. :X-man-page-only: ########################################\n.. :X-man-page-only:\n.. :X-man-page-only: :Copyright: LGPLv3\n.. :X-man-page-only: :Manual section: 7\n\nOverview\n========\nThis plugin monitors the title of the active window.\n\nOptions\n=======\n\n* ``display``: string\n\n  Display to connect to. Default is to use ``DISPLAY`` environment variable.\n\n* ``visible``: boolean\n\n  If true, try to retrieve the title from the ``_NET_WM_VISIBLE_NAME`` atom. Default is false.\n\n* ``extended_fmt``: boolean\n\n  If true, *class* and *instance* of the active window will also be reported;\n  the argument to ``cb`` will be a table instead of string or nil.\n  Defaults to false.\n\n``cb`` argument\n===============\nIf ``extended_fmt`` option was not enabled (this is the default), the argument is a string\nwith the title of the active window, or ``nil`` if there is no active window.\n\nIf ``extended_fmt`` is enabled, the argument will be a table with keys\n``class``, ``instance`` and ``title``. All values are strings or ``nil`` if there is no active window.\n"
  },
  {
    "path": "plugins/xtitle/xtitle.c",
    "content": "/*\n * Copyright (C) 2015-2025  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#include <lua.h>\n#include <xcb/xcb.h>\n#include <xcb/xproto.h>\n#include <xcb/xcb_event.h>\n#include <xcb/xcb_icccm.h>\n#include <xcb/xcb_ewmh.h>\n#include <errno.h>\n#include <stdint.h>\n#include <stdbool.h>\n#include <stdlib.h>\n\n#include \"include/plugin_v1.h\"\n#include \"include/sayf_macros.h\"\n\n#include \"libmoonvisit/moonvisit.h\"\n\n#include \"libls/ls_alloc_utils.h\"\n#include \"libls/ls_tls_ebuf.h\"\n#include \"libls/ls_io_utils.h\"\n#include \"libls/ls_time_utils.h\"\n\n// some parts of this file (including the name) are proudly stolen from\n// xtitle (https://github.com/baskerville/xtitle).\n\ntypedef struct {\n    char *dpyname;\n    bool visible;\n    bool extended_fmt;\n} Priv;\n\nstatic void destroy(LuastatusPluginData *pd)\n{\n    Priv *p = pd->priv;\n    free(p->dpyname);\n    free(p);\n}\n\nstatic int init(LuastatusPluginData *pd, lua_State *L)\n{\n    Priv *p = pd->priv = LS_XNEW(Priv, 1);\n    *p = (Priv) {\n        .dpyname = NULL,\n        .visible = false,\n        .extended_fmt = false,\n    };\n    char errbuf[256];\n    MoonVisit mv = {.L = L, .errbuf = errbuf, .nerrbuf = sizeof(errbuf)};\n\n    // Parse display\n    if (moon_visit_str(&mv, -1, \"display\", &p->dpyname, NULL, true) < 0)\n        goto mverror;\n\n    // Parse visible\n    if (moon_visit_bool(&mv, -1, \"visible\", &p->visible, true) < 0)\n        goto mverror;\n\n    // Parse extended_fmt\n    if (moon_visit_bool(&mv, -1, \"extended_fmt\", &p->extended_fmt, true) < 0)\n        goto mverror;\n\n    return LUASTATUS_OK;\n\nmverror:\n    LS_FATALF(pd, \"%s\", errbuf);\n//error:\n    destroy(pd);\n    return LUASTATUS_ERR;\n}\n\ntypedef struct {\n    xcb_connection_t *conn;\n    xcb_ewmh_connection_t *ewmh;\n    bool ewmh_inited;\n    xcb_window_t root;\n    int screenp;\n    bool visible;\n    bool extended_fmt;\n} Data;\n\nstatic inline void do_watch(Data *d, xcb_window_t win, bool state)\n{\n    if (win == XCB_NONE)\n        return;\n    uint32_t value = state ? XCB_EVENT_MASK_PROPERTY_CHANGE : XCB_EVENT_MASK_NO_EVENT;\n    xcb_change_window_attributes(d->conn, win, XCB_CW_EVENT_MASK, &value);\n}\n\nstatic inline bool get_active_window(Data *d, xcb_window_t *win)\n{\n    return\n        xcb_ewmh_get_active_window_reply(\n            d->ewmh,\n            xcb_ewmh_get_active_window(d->ewmh, d->screenp),\n            win,\n            NULL\n        ) == 1;\n}\n\nstatic bool push_window_title(Data *d, lua_State *L, xcb_window_t win)\n{\n    if (win == XCB_NONE)\n        return false;\n\n    xcb_ewmh_get_utf8_strings_reply_t ewmh_txt_prop;\n    xcb_icccm_get_text_property_reply_t icccm_txt_prop;\n    ewmh_txt_prop.strings = NULL;\n    icccm_txt_prop.name = NULL;\n\n    if (d->visible &&\n        xcb_ewmh_get_wm_visible_name_reply(\n            d->ewmh,\n            xcb_ewmh_get_wm_visible_name(d->ewmh, win),\n            &ewmh_txt_prop,\n            NULL\n        ) == 1 &&\n        ewmh_txt_prop.strings)\n    {\n        lua_pushlstring(L, ewmh_txt_prop.strings, ewmh_txt_prop.strings_len);\n        xcb_ewmh_get_utf8_strings_reply_wipe(&ewmh_txt_prop);\n        return true;\n    }\n\n    if (xcb_ewmh_get_wm_name_reply(\n            d->ewmh,\n            xcb_ewmh_get_wm_name(d->ewmh, win),\n            &ewmh_txt_prop,\n            NULL\n        ) == 1 &&\n        ewmh_txt_prop.strings)\n    {\n        lua_pushlstring(L, ewmh_txt_prop.strings, ewmh_txt_prop.strings_len);\n        xcb_ewmh_get_utf8_strings_reply_wipe(&ewmh_txt_prop);\n        return true;\n    }\n\n    if (xcb_icccm_get_wm_name_reply(\n            d->conn,\n            xcb_icccm_get_wm_name(d->conn, win),\n            &icccm_txt_prop,\n            NULL\n        ) == 1 &&\n        icccm_txt_prop.name)\n    {\n        lua_pushlstring(L, icccm_txt_prop.name, icccm_txt_prop.name_len);\n        xcb_icccm_get_text_property_reply_wipe(&icccm_txt_prop);\n        return true;\n    }\n\n    return false;\n}\n\nstatic inline void push_window_title_or_nil(Data *d, lua_State *L, xcb_window_t win)\n{\n    if (!push_window_title(d, L, win)) {\n        lua_pushnil(L);\n    }\n}\n\nstatic bool push_window_class_and_instance(Data *d, lua_State *L, xcb_window_t win)\n{\n    if (win == XCB_NONE) {\n        return false;\n    }\n\n    xcb_get_property_cookie_t cookie =\n        xcb_icccm_get_wm_class(d->conn, win);\n\n    xcb_icccm_get_wm_class_reply_t prop;\n    if (!xcb_icccm_get_wm_class_reply(d->conn, cookie, &prop, NULL)) {\n        return false;\n    }\n\n    const char *class_name = prop.class_name;\n    if (!class_name) {\n        class_name = \"\";\n    }\n    lua_pushstring(L, class_name); // L: class\n\n    const char *instance_name = prop.instance_name;\n    if (!instance_name) {\n        instance_name = \"\";\n    }\n    lua_pushstring(L, instance_name); // L: instance\n\n    xcb_icccm_get_wm_class_reply_wipe(&prop);\n\n    return true;\n}\n\nstatic void push_arg(Data *d, lua_State *L, xcb_window_t win)\n{\n    if (!d->extended_fmt) {\n        push_window_title_or_nil(d, L, win); // L: title\n        return;\n    }\n\n    lua_createtable(L, 0, 3); // L: table\n\n    push_window_title_or_nil(d, L, win); // L: table title\n    lua_setfield(L, -2, \"title\"); // L: table\n\n    if (!push_window_class_and_instance(d, L, win)) {\n        lua_pushnil(L); // L: table nil\n        lua_pushnil(L); // L: table nil nil\n    }\n    // L: table class instance\n    lua_setfield(L, -3, \"instance\"); // L: table class\n    lua_setfield(L, -2, \"class\"); // L: table\n}\n\n// updates /*win/ and /*last_win/ if the active window was changed\nstatic bool title_changed(\n        Data *d,\n        xcb_generic_event_t *evt,\n        xcb_window_t *win,\n        xcb_window_t *last_win)\n{\n    if (XCB_EVENT_RESPONSE_TYPE(evt) != XCB_PROPERTY_NOTIFY)\n        return false;\n\n    xcb_property_notify_event_t *pne = (xcb_property_notify_event_t *) evt;\n\n    if (pne->atom == d->ewmh->_NET_ACTIVE_WINDOW) {\n        do_watch(d, *last_win, false);\n        if (get_active_window(d, win)) {\n            do_watch(d, *win, true);\n            *last_win = *win;\n        } else {\n            *win = *last_win = XCB_NONE;\n        }\n        return true;\n    }\n\n    if (*win != XCB_NONE && pne->window == *win &&\n        ((d->visible && pne->atom == d->ewmh->_NET_WM_VISIBLE_NAME) ||\n         pne->atom == d->ewmh->_NET_WM_NAME ||\n         pne->atom == XCB_ATOM_WM_NAME))\n    {\n        return true;\n    }\n\n    return false;\n}\n\nstatic xcb_screen_iterator_t find_nth_screen(xcb_connection_t *conn, int n)\n{\n    const xcb_setup_t *setup = xcb_get_setup(conn);\n    xcb_screen_iterator_t iter = xcb_setup_roots_iterator(setup);\n    for (int i = 0; i < n; ++i)\n        xcb_screen_next(&iter);\n    return iter;\n}\n\nstatic void run(LuastatusPluginData *pd, LuastatusPluginRunFuncs funcs)\n{\n    Priv *p = pd->priv;\n\n    Data d = {\n        .conn = NULL,\n        .ewmh = LS_XNEW(xcb_ewmh_connection_t, 1),\n        .ewmh_inited = false,\n        .visible = p->visible,\n        .extended_fmt = p->extended_fmt,\n    };\n    int err;\n\n    // connect\n    d.conn = xcb_connect(p->dpyname, &d.screenp);\n    if ((err = xcb_connection_has_error(d.conn))) {\n        LS_FATALF(pd, \"xcb_connect: XCB error %d\", err);\n        // /xcb_disconnect/ should be called even if /xcb_connection_has_error/ returned non-zero,\n        // so we should not set /d.conn/ to /NULL/ here.\n        goto error;\n    }\n\n    // iterate over screens to find our root window\n    d.root = find_nth_screen(d.conn, d.screenp).data->root;\n\n    // initialize ewmh\n    if (\n        xcb_ewmh_init_atoms_replies(\n            d.ewmh,\n            xcb_ewmh_init_atoms(d.conn, d.ewmh),\n            NULL)\n        == 0)\n    {\n        LS_FATALF(pd, \"xcb_ewmh_init_atoms_replies() failed\");\n        goto error;\n    }\n    d.ewmh_inited = true;\n\n    xcb_window_t win = XCB_NONE;\n    xcb_window_t last_win = XCB_NONE;\n\n    // get initial active window; make a call on success.\n    if (get_active_window(&d, &win)) {\n        push_arg(&d, funcs.call_begin(pd->userdata), win);\n        funcs.call_end(pd->userdata);\n    }\n\n    // set up initial watchers\n    do_watch(&d, d.root, true);\n    do_watch(&d, win, true);\n\n    // poll for changes\n\n    int fd = xcb_get_file_descriptor(d.conn);\n    xcb_flush(d.conn);\n    while (1) {\n        int nfds = ls_wait_input_on_fd(fd, LS_TD_FOREVER);\n        if (nfds < 0) {\n            LS_FATALF(pd, \"ls_wait_input_on_fd: %s\", ls_tls_strerror(errno));\n            goto error;\n        } else if (nfds > 0) {\n            xcb_generic_event_t *evt;\n            while ((evt = xcb_poll_for_event(d.conn))) {\n                if (title_changed(&d, evt, &win, &last_win)) {\n                    push_arg(&d, funcs.call_begin(pd->userdata), win);\n                    funcs.call_end(pd->userdata);\n                }\n                free(evt);\n            }\n            if ((err = xcb_connection_has_error(d.conn))) {\n                LS_FATALF(pd, \"xcb_poll_for_event: XCB error %d\", err);\n                goto error;\n            }\n        }\n    }\n\nerror:\n    if (d.ewmh_inited) {\n       xcb_ewmh_connection_wipe(d.ewmh);\n    }\n    if (d.conn) {\n        xcb_disconnect(d.conn);\n    }\n    free(d.ewmh);\n}\n\nLuastatusPluginIface luastatus_plugin_iface_v1 = {\n    .init = init,\n    .run = run,\n    .destroy = destroy,\n};\n"
  },
  {
    "path": "release.sh",
    "content": "#!/bin/sh\nset -e\n\nif [ \"$#\" -ne 1 ]; then\n    echo >&2 \"USAGE: $0 new_version\"\n    exit 2\nfi\n\nRELEASE_BRANCH=release\nDEV_BRANCH=master\nNEW_VERSION=$1\n\nT() {\n    printf >&2 -- '-> %s\\n' \"$*\"\n    \"$@\"\n}\n\nT_redir_to_file() {\n    local f=$1\n    shift\n    printf >&2 -- '-> %s > %s\\n' \"$*\" \"$f\"\n    \"$@\" > \"$f\"\n}\n\nT_append_to_file() {\n    local f=$1\n    shift\n    printf >&2 -- '-> %s >> %s\\n' \"$*\" \"$f\"\n    \"$@\" >> \"$f\"\n}\n\nask() {\n    local ans\n    echo \"\"\n    echo -n \"  $* (“yes” to proceed) >>> \"\n    read -r ans || exit 3\n    if [ \"$ans\" != yes ]; then\n        exit 3\n    fi\n}\n\nif ! T git checkout $RELEASE_BRANCH; then\n    T git branch $RELEASE_BRANCH\n    T git checkout $RELEASE_BRANCH\nfi\n\nlast_msg=$(git log -1 --pretty=%B)\nrelease_msg=\"Release $NEW_VERSION\"\nif [ \"$last_msg\" = \"$release_msg\" ]; then\n    echo \"\"\n    echo \"Last commit message is “$last_msg”.\"\n    echo \"Apparently, you have run this script before, so exiting now.\"\n    exit 1\nfi\n\nT git merge $DEV_BRANCH\nT_redir_to_file VERSION echo $NEW_VERSION\nT_append_to_file RELEASE_NOTES echo\nT sed \\\n    -e \"1i\\luastatus $NEW_VERSION ($(LC_ALL=C date +'%B %d, %Y'))\\n===\\n(Please describe the changes here.)\\n\" \\\n    -e :a -e '/^\\n*$/{$d;N;};/\\n$/ba' \\\n    -i RELEASE_NOTES\nT ${EDITOR:-edit} RELEASE_NOTES\nask 'Commit?'\nT git add VERSION RELEASE_NOTES\nT git commit -m \"$release_msg\"\n\ncat <<EOF\n\n===\nDone! Now:\n\n  * Make manual changes, if needed.\n\n  * Run the following commands:\n    * git tag --force v$NEW_VERSION\n    * git push --set-upstream origin $RELEASE_BRANCH\n    * git push --tags\n    * git checkout $DEV_BRANCH\n\n  * Release it on GitHub:\n    * The “<number> release(s)” link on the GitHub page of the project\n    * The “Tags” tab\n    * “Add release notes”\n    * Fill in everything needed, click “Publish release”\n\n  * Tell everybody about it!!\n===\nEOF\n"
  },
  {
    "path": "run_literally_all_tests.sh",
    "content": "#!/usr/bin/env bash\n\nset -e\n\nif (( $# != 1 )); then\n    printf '%s\\n' \"USAGE: $0 BUILD_DIR\" >&2\n    exit 2\nfi\n\n# Resolve $1 into $BUILD_DIR according to the old working directory\nif [[ $1 == /* ]]; then\n    BUILD_DIR=$1\nelse\n    BUILD_DIR=$PWD/$1\nfi\n\ncd -- \"$(dirname \"$(readlink \"$0\" || printf '%s\\n' \"$0\")\")\"\n\nCOMMON_D_ARGS=(\n    -DBUILD_PLUGIN_PULSE=YES\n    -DBUILD_PLUGIN_UNIXSOCK=YES\n    -DBUILD_PLUGIN_WEB=YES\n    -DBUILD_TESTS=YES\n)\nOPTIONS=(\n    WITH_UBSAN\n    WITH_ASAN\n    WITH_TSAN\n    WITH_LSAN\n)\nMAX_LAG_valgrind=500\nMAX_LAG_sanitizers=250\n\nLAST_OPTION='(none, just starting up)'\n\nget_cmake_D_args() {\n    local enable_opt=$1\n    local opt\n\n    for opt in \"${OPTIONS[@]}\"; do\n        local yesno=NO\n        if [[ $opt == $enable_opt ]]; then\n            yesno=YES\n        fi\n        printf '%s\\n' \"-D${opt}=${yesno}\"\n    done\n}\n\nbuild_with_option() {\n    LAST_OPTION=${2:-$1}\n\n    cmake \"${COMMON_D_ARGS[@]}\" $(get_cmake_D_args \"$1\") \"$BUILD_DIR\" \\\n        || return $?\n\n    ( set -e; cd \"$BUILD_DIR\"; make; ) \\\n        || return $?\n\n    echo >&2 \"Built with option: $LAST_OPTION\"\n}\n\ntrap '\n    if [[ $LAST_OPTION != \"(ok)\" ]]; then\n        echo >&2 \"Last enabled option: $LAST_OPTION\"\n    fi\n' EXIT\n\nbuild_with_option '-' '(none)'\n./tests/pt.sh \"$BUILD_DIR\"\n\nbuild_with_option '-' '(none, under valgrind/memcheck)'\nPT_TOOL=valgrind PT_MAX_LAG=$MAX_LAG_valgrind ./tests/pt.sh \"$BUILD_DIR\"\n\nbuild_with_option '-' '(none, under valgrind/helgrind)'\nPT_TOOL=helgrind PT_MAX_LAG=$MAX_LAG_valgrind ./tests/pt.sh \"$BUILD_DIR\"\n\nbuild_with_option '-' '(none, torture test)'\n./tests/torture.sh \"$BUILD_DIR\"\n\nfor opt in \"${OPTIONS[@]}\"; do\n    build_with_option \"$opt\"\n    PT_MAX_LAG=$MAX_LAG_sanitizers ./tests/pt.sh \"$BUILD_DIR\"\ndone\n\nLAST_OPTION='(ok)'\n\necho >&2 \"OK\"\n"
  },
  {
    "path": "run_luacheck.sh",
    "content": "#!/usr/bin/env bash\n\nset -e\n\ncd -- \"$(dirname \"$(readlink \"$0\" || printf '%s\\n' \"$0\")\")\"\n\nif ! command -v luacheck >/dev/null; then\n    echo >&2 'FATAL: \"luacheck\" command was not found. Please install it.'\n    exit 1\nfi\n\nrc=0\nfind -type f -name '*.lua' -print0 | xargs -0 luacheck --config ./.luacheckrc \\\n    || rc=$?\n\necho >&2 '=============================='\nif (( rc == 0 )); then\n    echo >&2 'Everything is OK'\nelse\n    echo >&2 'luacheck failed; see above.'\nfi\n\nexit $rc\n"
  },
  {
    "path": "tests/.gitignore",
    "content": "/parrot\n/stopwatch\n/listnets\n"
  },
  {
    "path": "tests/CMakeLists.txt",
    "content": "luastatus_add_plugin_noinstall (plugin-mock $<TARGET_OBJECTS:ls> $<TARGET_OBJECTS:moonvisit> \"mock_plugin.c\")\ntarget_compile_definitions (plugin-mock PUBLIC -D_POSIX_C_SOURCE=200809L)\nluastatus_target_compile_with (plugin-mock LUA)\ntarget_include_directories (plugin-mock PUBLIC \"${PROJECT_SOURCE_DIR}\")\n\nluastatus_add_barlib_noinstall (barlib-mock $<TARGET_OBJECTS:ls> \"mock_barlib.c\")\ntarget_compile_definitions (barlib-mock PUBLIC -D_POSIX_C_SOURCE=200809L)\nluastatus_target_compile_with (barlib-mock LUA)\ntarget_include_directories (barlib-mock PUBLIC \"${PROJECT_SOURCE_DIR}\")\n\nadd_executable (parrot \"parrot.c\")\ntarget_compile_definitions (parrot PUBLIC -D_POSIX_C_SOURCE=200809L)\n# find pthreads\nset (CMAKE_THREAD_PREFER_PTHREAD TRUE)\nset (THREADS_PREFER_PTHREAD_FLAG TRUE)\nfind_package (Threads REQUIRED)\n# link against pthreads\ntarget_link_libraries (parrot PUBLIC Threads::Threads)\n\nadd_executable (stopwatch \"stopwatch.c\")\ntarget_compile_definitions (stopwatch PUBLIC -D_POSIX_C_SOURCE=200809L)\n\nadd_executable (kcov_wrapper \"kcov_wrapper.c\")\ntarget_compile_definitions (kcov_wrapper PUBLIC -D_POSIX_C_SOURCE=200809L)\n\nif (BUILD_PLUGIN_INOTIFY)\n    add_executable (listnets \"listnets.c\")\n    target_compile_definitions (listnets PUBLIC -D_POSIX_C_SOURCE=200809L)\nendif ()\n\nif (BUILD_PLUGIN_WEB)\n    add_subdirectory (\"httpserv\")\nendif ()\n"
  },
  {
    "path": "tests/README.txt",
    "content": "This directory contains two test systems for luastatus:\n\n  * pt (plain tests): the main test system; it tests plugins, barlibs and\n    luastatus itself. It is written in bash.\n\n  * torture: stress tests for luastatus, under valgrind.\n\nkcov\n----\n\nkcov does not redirect the signals it receives to its child process.\nThis is why we have the kcov_wrapper thing: it is intended for wrapping\n  kcov DIR COMMAND [ARGS...]\nprocess. This wrapper spawns a process (normally kcov), then redirects the\nfollowing signals:\n  * SIGINT;\n  * SIGTERM;\n  * SIGHUP;\n  * SIGQUIT\nto the child's child process. If the child spawns more than one process, then\nwhich grandchild is selected for sending signals is unspecified.\n\nUntil the grandchild process is spawned, all signals received are queued.\n\nIt works under Linux only (requires procfs to be mounted at /proc).\n\nAlso, luastatus sometimes (rarely) segfaults when run under kcov.\nWe don't know why.\n"
  },
  {
    "path": "tests/barlib-runners/runner-redirect-34",
    "content": "#!/bin/sh\nexec \"$@\" 3<&0 0</dev/null 4>&1 1>&2\n"
  },
  {
    "path": "tests/dlopen.supp",
    "content": "{\n   Pattern 1\n   Memcheck:Leak\n   match-leak-kinds: reachable\n   fun:calloc\n   ...\n   fun:_dlerror_run\n   fun:dlopen@@GLIBC_2.2.5\n   ...\n}\n{\n   Pattern 2\n   Memcheck:Leak\n   match-leak-kinds: reachable\n   fun:malloc\n   ...\n   fun:dlclose\n   ...\n}\n{\n   Pattern 3\n   Memcheck:Leak\n   match-leak-kinds: reachable\n   fun:malloc\n   ...\n   fun:openaux\n   ...\n   fun:_dl_open\n   fun:dlopen_doit\n   fun:_dl_catch_error\n}\n{\n   Pattern 4\n   Memcheck:Leak\n   match-leak-kinds: reachable\n   fun:calloc\n   ...\n   fun:openaux\n   ...\n   fun:_dl_open\n   fun:dlopen_doit\n   fun:_dl_catch_error\n}\n"
  },
  {
    "path": "tests/glib.supp",
    "content": "{\n   glib-thread-fuckery-misc-1\n   Helgrind:Race\n   ...\n   fun:g_source_attach\n}\n\n{\n   glib-thread-fuckery-misc-2\n   Helgrind:Race\n   ...\n   fun:g_bus_get_sync\n}\n\n{\n   glib-thread-fuckery-misc-3\n   Helgrind:Race\n   ...\n   fun:g_dbus_connection_send_message_with_reply_sync\n}\n\n{\n   glib-thread-fuckery-misc-4\n   Helgrind:Race\n   ...\n   fun:g_task_propagate_pointer\n}\n\n{\n   glib-thread-fuckery-ctx-1\n   Helgrind:Race\n   ...\n   fun:g_main_context_acquire\n}\n\n{\n   glib-thread-fuckery-ctx-2\n   Helgrind:Race\n   ...\n   fun:g_main_context_iteration\n}\n\n{\n   glib-thread-fuckery-ctx-3\n   Helgrind:Race\n   ...\n   fun:g_main_context_release\n}\n\n{\n   glib-thread-fuckery-ctx-4\n   Helgrind:Race\n   ...\n   fun:g_main_loop_run\n}\n\n{\n   glib-thread-fuckery-wtf\n   Helgrind:Race\n   ...\n   fun:g_variant_get_type\n}\n"
  },
  {
    "path": "tests/httpserv/.gitignore",
    "content": "/httpserv\n"
  },
  {
    "path": "tests/httpserv/CMakeLists.txt",
    "content": "file (GLOB sources \"*.c\")\nadd_executable (httpserv ${sources})\ntarget_compile_definitions (httpserv PUBLIC -D_POSIX_C_SOURCE=200809L)\n\nfind_package (PkgConfig REQUIRED)\npkg_check_modules (LWS REQUIRED libwebsockets)\n\ntarget_include_directories (httpserv SYSTEM PUBLIC ${LWS_INCLUDE_DIRS})\ntarget_compile_options (httpserv PUBLIC ${LWS_CFLAGS_OTHER})\ntarget_link_libraries (httpserv PUBLIC ${LWS_LIBRARIES})\n"
  },
  {
    "path": "tests/httpserv/argparser.c",
    "content": "/*\n * Copyright (C) 2026  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#include \"argparser.h\"\n#include <stdbool.h>\n#include <stdlib.h>\n#include <string.h>\n#include <stdio.h>\n#include <errno.h>\n\n// If zero-terminated string /str/ starts with zero-terminated string /prefix/, returns\n// /str + strlen(prefix)/; otherwise, returns /NULL/.\nstatic const char *strfollow(const char *str, const char *prefix)\n{\n    size_t nprefix = strlen(prefix);\n    return strncmp(str, prefix, nprefix) == 0 ? str + nprefix : NULL;\n}\n\nstatic bool parse_int(const char *s, int *dst)\n{\n    errno = 0;\n    char *endptr;\n    long res = strtol(s, &endptr, 10);\n    if (errno != 0 || *s == '\\0' || *endptr != '\\0') {\n        return false;\n    }\n#if LONG_MAX > INT_MAX\n    if (res < INT_MIN || res > INT_MAX) {\n        return false;\n    }\n#endif\n    *dst = res;\n    return true;\n}\n\nstatic ArgParser_Option *find_opt(ArgParser_Option *opts, const char *arg, const char **out_value)\n{\n    for (ArgParser_Option *cur = opts; cur->spelling; ++cur) {\n        const char *value = strfollow(arg, cur->spelling);\n        if (value) {\n            *out_value = value;\n            return cur;\n        }\n    }\n    return NULL;\n}\n\nbool arg_parser_parse(\n        ArgParser_Option *opts,\n        char **pos, int npos,\n        int argc, char **argv,\n        char *errbuf, size_t nerrbuf)\n{\n    bool dash_dash_mode = false;\n    int pos_idx = 0;\n\n    for (int i = 1; i < argc; ++i) {\n        char *arg = argv[i];\n\n        if (arg[0] == '-' && !dash_dash_mode) {\n            // This is either an option or a dash-dash.\n\n            if (strcmp(arg, \"--\") == 0) {\n                // This is a dash-dash.\n                dash_dash_mode = true;\n                continue;\n            }\n\n            // This is an option.\n            const char *value;\n            ArgParser_Option *found = find_opt(opts, arg, &value);\n            if (!found) {\n                snprintf(errbuf, nerrbuf, \"unknown option '%s'\", arg);\n                return false;\n            }\n            if (!parse_int(value, found->dst)) {\n                snprintf(errbuf, nerrbuf, \"cannot parse value of '%s' option as int\", arg);\n                return false;\n            }\n\n        } else {\n            // This is a position argument.\n            if (pos_idx == npos) {\n                snprintf(errbuf, nerrbuf, \"too many positional arguments\");\n                return false;\n            }\n            pos[pos_idx++] = arg;\n        }\n    }\n\n    if (pos_idx != npos) {\n        snprintf(errbuf, nerrbuf, \"not enough positional arguments\");\n        return false;\n    }\n\n    return true;\n}\n"
  },
  {
    "path": "tests/httpserv/argparser.h",
    "content": "/*\n * Copyright (C) 2026  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#pragma once\n\n#include <stdbool.h>\n#include <stddef.h>\n\ntypedef struct {\n    const char *spelling;\n    int *dst;\n} ArgParser_Option;\n\nbool arg_parser_parse(\n        ArgParser_Option *opts,\n        char **pos, int npos,\n        int argc, char **argv,\n        char *errbuf, size_t nerrbuf);\n"
  },
  {
    "path": "tests/httpserv/buffer.c",
    "content": "/*\n * Copyright (C) 2026  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#include \"buffer.h\"\n#include <string.h>\n#include <stdlib.h>\n#include \"common.h\"\n\nvoid buffer_append(Buffer *b, const char *chunk, size_t len)\n{\n    if (!len) {\n        return;\n    }\n    while (b->capacity - b->size < len) {\n        b->data = x2realloc(b->data, &b->capacity, sizeof(char));\n    }\n    memcpy(b->data + b->size, chunk, len);\n    b->size += len;\n}\n\nvoid buffer_destroy(Buffer *b)\n{\n    free(b->data);\n}\n"
  },
  {
    "path": "tests/httpserv/buffer.h",
    "content": "/*\n * Copyright (C) 2026  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#pragma once\n\n#include <stddef.h>\n\ntypedef struct {\n    char *data;\n    size_t size;\n    size_t capacity;\n} Buffer;\n\n#define BUFFER_STATIC_INIT {0}\n\nvoid buffer_append(Buffer *b, const char *chunk, size_t len);\n\nvoid buffer_destroy(Buffer *b);\n"
  },
  {
    "path": "tests/httpserv/common.c",
    "content": "/*\n * Copyright (C) 2026  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#include \"common.h\"\n#include <stdlib.h>\n#include <stdio.h>\n#include <stdint.h>\n#include <string.h>\n\nstatic inline size_t mul_zu_or_oom(size_t a, size_t b)\n{\n    if (b && a > SIZE_MAX / b) {\n        panic_oom();\n    }\n    return a * b;\n}\n\nvoid *xrealloc(void *p, size_t n, size_t m)\n{\n    size_t total = mul_zu_or_oom(n, m);\n    if (!total) {\n        free(p);\n        return NULL;\n    }\n    void *r = realloc(p, total);\n    if (!r) {\n        panic_oom();\n    }\n    return r;\n}\n\nvoid *x2realloc(void *p, size_t *pcapacity, size_t elem_sz)\n{\n    if (*pcapacity == 0) {\n        *pcapacity = 1;\n    } else {\n        *pcapacity = mul_zu_or_oom(*pcapacity, 2);\n    }\n    return xrealloc(p, *pcapacity, elem_sz);\n}\n\nvoid *xmemdup(const void *p, size_t n)\n{\n    if (!n) {\n        return NULL;\n    }\n    void *r = xmalloc(n, 1);\n    memcpy(r, p, n);\n    return r;\n}\n\nchar *xstrdup(const char *s)\n{\n    return xmemdup(s, strlen(s) + 1);\n}\n"
  },
  {
    "path": "tests/httpserv/common.h",
    "content": "/*\n * Copyright (C) 2026  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#pragma once\n\n#include <stdlib.h>\n#include <stdio.h>\n\n#define xmalloc(n, m) xrealloc(NULL, (n), (m))\n\nvoid *xrealloc(void *p, size_t n, size_t m);\n\nvoid *x2realloc(void *p, size_t *pcapacity, size_t elem_sz);\n\nvoid *xmemdup(const void *p, size_t n);\n\nchar *xstrdup(const char *s);\n\n#define panic(s) (fprintf(stderr, \"FATAL: %s\\n\", (s)), abort())\n\n#define panic_oom() panic(\"out of memory\")\n\n#if __GNUC__ >= 2\n#   define ATTR_NORETURN __attribute__((noreturn))\n#else\n#   define ATTR_NORETURN /*nothing*/\n#endif\n"
  },
  {
    "path": "tests/httpserv/main.c",
    "content": "/*\n * Copyright (C) 2026  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#include <stdlib.h>\n#include <stdint.h>\n#include <stdio.h>\n#include <stdbool.h>\n#include <string.h>\n#include <limits.h>\n#include <unistd.h>\n#include <errno.h>\n#include <sys/types.h>\n#include \"server.h\"\n#include \"common.h\"\n#include \"sleep_millis.h\"\n#include \"argparser.h\"\n\ntypedef struct {\n    const char *expected_path;\n    bool expected_method_is_post;\n    int max_requests;\n    int freeze_for;\n} GlobalOptions;\n\nstatic GlobalOptions global_options = {\n    .max_requests = -1,\n    .freeze_for = 0,\n};\n\ntypedef struct {\n    uint64_t cookie;\n    int status;\n    const char *status_text;\n} Request;\n\ntypedef struct {\n    Request *data;\n    size_t size;\n    size_t capacity;\n} RequestList;\n\nstatic RequestList request_list = {0};\n\nRequest *request_new(uint64_t cookie)\n{\n    Request req = {\n        .cookie = cookie,\n        .status = 0,\n    };\n    if (request_list.size == request_list.capacity) {\n        request_list.data = x2realloc(request_list.data, &request_list.capacity, sizeof(Request));\n    }\n    Request *p_req = &request_list.data[request_list.size++];\n    *p_req = req;\n    return p_req;\n}\n\nstatic Request *request_find_or_die(uint64_t cookie)\n{\n    for (size_t i = 0; i < request_list.size; ++i) {\n        if (request_list.data[i].cookie == cookie) {\n            return &request_list.data[i];\n        }\n    }\n    panic(\"cannot find request with given cookie\");\n}\n\nstatic void request_free(Request *req)\n{\n    *req = request_list.data[request_list.size - 1];\n    --request_list.size;\n}\n\nstatic int request_prepare_error(Request *req, int status, const char *status_text)\n{\n    req->status = status;\n    req->status_text = status_text;\n    return status;\n}\n\nstatic const char *strip_leading_slashes(const char *s)\n{\n    while (*s == '/') {\n        ++s;\n    }\n    return s;\n}\n\nint my_before_req_cb(\n    uint64_t cookie,\n    const char *path,\n    bool is_method_post,\n    char **out_mime_type,\n    void *ud)\n{\n    (void) ud;\n\n    *out_mime_type = xstrdup(\"text/plain\");\n\n    Request *req = request_new(cookie);\n\n    const char *normalized_path_cur = strip_leading_slashes(\n        path);\n\n    const char *normalized_path_exp = strip_leading_slashes(\n        global_options.expected_path);\n\n    if (strcmp(normalized_path_cur, normalized_path_exp) != 0) {\n        return request_prepare_error(req, 404, \"Not Found\");\n    }\n    if (is_method_post != global_options.expected_method_is_post) {\n        return request_prepare_error(req, 405, \"Method Not Allowed\");\n    }\n    req->status = 200;\n    return req->status;\n}\n\nstatic char *send_prepared_error(Request *req, size_t *out_len)\n{\n    char buf[128];\n    snprintf(buf, sizeof(buf), \"%d %s\", req->status, req->status_text);\n    *out_len = strlen(buf);\n    return xstrdup(buf);\n}\n\nstatic char *my_write_body_cb(\n    uint64_t cookie,\n    const char *body,\n    size_t nbody,\n    size_t *out_len,\n    void *ud)\n{\n    (void) ud;\n\n    Request *req = request_find_or_die(cookie);\n    char *ret;\n\n    int status = req->status;\n    if (status != 200) {\n        ret = send_prepared_error(req, out_len);\n        goto done;\n    }\n\n    if (putchar('>') == EOF) {\n        goto write_error;\n    }\n    for (size_t i = 0; i < nbody; ++i) {\n        char c = body[i];\n        if (c == '\\n') {\n            c = ' ';\n        }\n        if (putchar((unsigned char) c) == EOF) {\n            goto write_error;\n        }\n    }\n    if (putchar('\\n') == EOF) {\n        goto write_error;\n    }\n    if (fflush(stdout) == EOF) {\n        goto write_error;\n    }\n\n    char *buf = NULL;\n    size_t nbuf = 0;\n    ssize_t r = getline(&buf, &nbuf, stdin);\n    if (r < 0) {\n        perror(\"getline\");\n        panic(\"getline() failed\");\n    } else if (r == 0) {\n        panic(\"got EOF\");\n    }\n\n    if (buf[r - 1] == '\\n') {\n        --r;\n    }\n    *out_len = r;\n    ret = buf;\n\ndone:\n    request_free(req);\n    return ret;\n\nwrite_error:\n    panic(\"cannot write to stdout\");\n}\n\nstatic void freeze_or_die(void)\n{\n    if (global_options.freeze_for < 0) {\n        fprintf(stderr, \"Existing as requested by --max-requests=...\\n\");\n        exit(0);\n    } else if (global_options.freeze_for > 0) {\n        fprintf(stderr, \"Freezing as requested by --freeze-for=...\\n\");\n        sleep_millis(global_options.freeze_for);\n    }\n}\n\nstatic void my_after_req_cb(\n    void *ud)\n{\n    (void) ud;\n\n    static unsigned req_count = 0;\n    ++req_count;\n    if (\n        global_options.max_requests >= 0 &&\n        req_count == (unsigned) global_options.max_requests)\n    {\n        freeze_or_die();\n        req_count = 0;\n    }\n}\n\nstatic void my_ready_cb(\n    void *ud)\n{\n    (void) ud;\n\n    if (fputs(\"ready\\n\", stdout) == EOF) {\n        goto fail;\n    }\n    if (fflush(stdout) == EOF) {\n        goto fail;\n    }\n    return;\nfail:\n    panic(\"cannot write to stdout\");\n}\n\nATTR_NORETURN\nstatic void print_usage_and_die(const char *s)\n{\n    fprintf(stderr, \"Wrong USAGE: %s\\n\", s);\n    fprintf(stderr, \"USAGE: httpserv [--port=PORT] [--max-requests=N] [--freeze-for=MILLIS] METHOD PATH\\n\");\n    exit(2);\n}\n\nstatic bool parse_method_or_die(const char *s)\n{\n    if (strcmp(s, \"GET\") == 0) {\n        return false;\n    }\n    if (strcmp(s, \"POST\") == 0) {\n        return true;\n    }\n    print_usage_and_die(\"invalid METHOD\");\n}\n\nint main(int argc, char **argv)\n{\n    int port = 8080;\n    char *pos[2];\n\n    ArgParser_Option options[] = {\n        {\"--port=\", &port},\n        {\"--max-requests=\", &global_options.max_requests},\n        {\"--freeze-for=\", &global_options.freeze_for},\n        {0},\n    };\n\n    char errbuf[128];\n    if (!arg_parser_parse(options, pos, 2, argc, argv, errbuf, sizeof(errbuf))) {\n        print_usage_and_die(errbuf);\n    }\n\n    if (port < 0 || port > 0xFFFF) {\n        print_usage_and_die(\"invalid PORT\");\n    }\n\n    global_options.expected_method_is_post = parse_method_or_die(pos[0]);\n\n    global_options.expected_path = xstrdup(pos[1]);\n\n    run_server(\n        port,\n        my_before_req_cb,\n        my_write_body_cb,\n        my_after_req_cb,\n        my_ready_cb,\n        NULL);\n}\n"
  },
  {
    "path": "tests/httpserv/server.c",
    "content": "/*\n * Copyright (C) 2026  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#include \"server.h\"\n#include <libwebsockets.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <signal.h>\n#include \"buffer.h\"\n#include \"common.h\"\n\ntypedef struct {\n    BeforeRequestCallback before_req_cb;\n    WriteBodyCallback write_body_cb;\n    AfterRequestCallback after_req_cb;\n    ReadyCallback ready_cb;\n    void *ud;\n} Callbacks;\n\nstatic Callbacks callbacks;\n\ntypedef struct {\n    uint64_t cookie;\n    Buffer buf;\n    bool done_writing;\n} MyContext;\n\nstatic uint64_t next_cookie;\n\nstatic void my_context_new(MyContext *my_ctx)\n{\n    *my_ctx = (MyContext) {\n        .cookie = next_cookie++,\n        .buf = BUFFER_STATIC_INIT,\n        .done_writing = false,\n    };\n}\n\nstatic int my_context_before_request(\n    MyContext *my_ctx,\n    const char *path,\n    bool is_method_post,\n    char **out_mime_type)\n{\n    return callbacks.before_req_cb(\n        my_ctx->cookie,\n        path,\n        is_method_post,\n        out_mime_type,\n        callbacks.ud\n    );\n}\n\nstatic void my_context_add_chunk(MyContext *ctx, const char *chunk, size_t len)\n{\n    buffer_append(&ctx->buf, chunk, len);\n}\n\nstatic char *my_context_write_body(\n    MyContext *my_ctx,\n    size_t *out_len)\n{\n    return callbacks.write_body_cb(\n        my_ctx->cookie,\n        my_ctx->buf.data,\n        my_ctx->buf.size,\n        out_len,\n        callbacks.ud\n    );\n}\n\nstatic void my_context_destroy(MyContext *my_ctx)\n{\n    buffer_destroy(&my_ctx->buf);\n    my_ctx->buf = (Buffer) BUFFER_STATIC_INIT;\n}\n\nstatic int my_callback(\n    struct lws *wsi,\n    enum lws_callback_reasons reason,\n    void *userdata,\n    void *in,\n    size_t len)\n{\n    MyContext *my_ctx = userdata;\n\n    uint8_t buf[LWS_PRE + 512];\n    uint8_t *start = buf + LWS_PRE;\n    uint8_t *end = buf + sizeof(buf) - 1;\n    uint8_t *p = start;\n\n    if (reason == LWS_CALLBACK_HTTP) {\n        my_context_new(my_ctx);\n        char *mime_type;\n        int status = my_context_before_request(\n            my_ctx,\n            /*path=*/ in,\n            /*is_method_post=*/ lws_hdr_total_length(wsi, WSI_TOKEN_POST_URI) > 0,\n            /*out_mime_type=*/ &mime_type\n        );\n\n        bool is_ok = true;\n        if (lws_add_http_common_headers(\n                wsi,\n                status,\n                mime_type,\n                LWS_ILLEGAL_HTTP_CONTENT_LEN,\n                &p,\n                end) < 0)\n        {\n            is_ok = false;\n        }\n        if (lws_finalize_write_http_header(wsi, start, &p, end)) {\n            is_ok = false;\n        }\n\n        free(mime_type);\n\n        if (is_ok) {\n            lws_callback_on_writable(wsi);\n        }\n        return is_ok ? 0 : -1;\n\n    } else if (reason == LWS_CALLBACK_HTTP_BODY) {\n        my_context_add_chunk(my_ctx, in, len);\n        return 0;\n\n    } else if (reason == LWS_CALLBACK_HTTP_BODY_COMPLETION) {\n        lws_callback_on_writable(wsi);\n        return 0;\n\n    } else if (reason == LWS_CALLBACK_HTTP_WRITEABLE) {\n        if (!my_ctx) {\n            return -1;\n        }\n        if (my_ctx->done_writing) {\n            return 0;\n        }\n        my_ctx->done_writing = true;\n\n        size_t nbody;\n        char *body = my_context_write_body(my_ctx, &nbody);\n        if (nbody > INT_MAX) {\n            panic(\"response body is too large\");\n        }\n\n        int write_rc = lws_write(wsi, (uint8_t *) body, nbody, LWS_WRITE_HTTP_FINAL);\n\n        my_context_destroy(my_ctx);\n        free(body);\n\n        if (write_rc != (int) nbody) {\n            return -1;\n        }\n        if (lws_http_transaction_completed(wsi)) {\n            return -1;\n        }\n        return 0;\n\n    } else if (reason == LWS_CALLBACK_CLOSED_HTTP) {\n        my_context_destroy(my_ctx);\n        callbacks.after_req_cb(callbacks.ud);\n        return 0;\n\n    } else {\n        return 0;\n    }\n}\n\nstatic struct lws_protocols protocols[] = {\n    {\"http\", my_callback, sizeof(MyContext), 0, 0, NULL, 0},\n    {NULL, NULL, 0, 0, 0, NULL, 0},\n};\n\nstatic const struct lws_http_mount mount = {\n    .mount_next = NULL,\n    .mountpoint = \"/\",\n    .mountpoint_len = 1,\n    .origin = NULL,\n    .def = NULL,\n    .protocol = \"http\",\n    .cgienv = NULL,\n    .extra_mimetypes = NULL,\n    .interpret = NULL,\n    .cgi_timeout = 0,\n    .cache_max_age = 0,\n    .auth_mask = 0,\n    .cache_reusable = 0,\n    .cache_revalidate = 0,\n    .cache_intermediaries = 0,\n    .origin_protocol = LWSMPRO_CALLBACK,\n    .basic_auth_login_file = NULL,\n};\n\nbool run_server(\n    int port,\n    BeforeRequestCallback before_req_cb,\n    WriteBodyCallback write_body_cb,\n    AfterRequestCallback after_req_cb,\n    ReadyCallback ready_cb,\n    void *ud)\n{\n    callbacks = (Callbacks) {\n        .before_req_cb = before_req_cb,\n        .write_body_cb = write_body_cb,\n        .after_req_cb = after_req_cb,\n        .ready_cb = ready_cb,\n        .ud = ud,\n    };\n\n    signal(SIGPIPE, SIG_IGN);\n\n    struct lws_context_creation_info info = {\n        .port = port,\n        .protocols = protocols,\n        .mounts = &mount,\n        .options = LWS_SERVER_OPTION_DISABLE_IPV6,\n    };\n\n    lws_set_log_level(LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE, NULL);\n\n    struct lws_context *context = lws_create_context(&info);\n    if (!context) {\n        fprintf(stderr, \"lws_create_context() failed\\n\");\n        return false;\n    }\n\n    bool first = true;\n\n    while (lws_service(context, 1000) >= 0) {\n        if (first) {\n            callbacks.ready_cb(callbacks.ud);\n            first = false;\n        }\n    }\n\n    // unreachable\n\n    lws_context_destroy(context);\n    return true;\n}\n"
  },
  {
    "path": "tests/httpserv/server.h",
    "content": "/*\n * Copyright (C) 2026  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#pragma once\n\n#include <stdbool.h>\n#include <stdint.h>\n#include <stddef.h>\n\ntypedef int (*BeforeRequestCallback)(\n    uint64_t cookie,\n    const char *path,\n    bool is_method_post,\n    char **out_mime_type,\n    void *ud);\n\ntypedef char *(*WriteBodyCallback)(\n    uint64_t cookie,\n    const char *body,\n    size_t nbody,\n    size_t *out_len,\n    void *ud);\n\ntypedef void (*AfterRequestCallback)(\n    void *ud);\n\ntypedef void (*ReadyCallback)(\n    void *ud);\n\nbool run_server(\n    int port,\n    BeforeRequestCallback before_req_cb,\n    WriteBodyCallback write_body_cb,\n    AfterRequestCallback after_req_cb,\n    ReadyCallback ready_cb,\n    void *ud\n);\n"
  },
  {
    "path": "tests/httpserv/sleep_millis.c",
    "content": "/*\n * Copyright (C) 2026  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#include \"sleep_millis.h\"\n#include <time.h>\n#include <assert.h>\n#include <errno.h>\n\nvoid sleep_millis(int millis)\n{\n    assert(millis >= 0);\n\n    struct timespec ts = {\n        .tv_sec = millis / 1000,\n        .tv_nsec = (millis % 1000) * 1000 * 1000,\n    };\n    struct timespec rem;\n    while (nanosleep(&ts, &rem) < 0 && errno == EINTR) {\n        ts = rem;\n    }\n}\n"
  },
  {
    "path": "tests/httpserv/sleep_millis.h",
    "content": "/*\n * Copyright (C) 2026  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#pragma once\n\nvoid sleep_millis(int millis);\n"
  },
  {
    "path": "tests/kcov_wrapper.c",
    "content": "/*\n * Copyright (C) 2026  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n// This thing is intended for wrapping \"kcov DIR COMMAND [ARGS...]\" process: kcov\n// does not redirect the signals it receives to its child process.\n//\n// This wrapper spawns a process (normally kcov), then redirects the following signals:\n//   * SIGINT;\n//   * SIGTERM;\n//   * SIGHUP;\n//   * SIGQUIT\n// to the child's child process. If the child spawns more than one process, then which\n// grandchild is selected for sending signals is unspecified.\n//\n// Until the grandchild process is spawned, all signals received are queued.\n//\n// It works under Linux only (requires procfs to be mounted at /proc).\n\n#include <stdbool.h>\n#include <fcntl.h>\n#include <unistd.h>\n#include <errno.h>\n#include <string.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <signal.h>\n#include <stdint.h>\n#include <sys/wait.h>\n#include <sys/types.h>\n#include <inttypes.h>\n#include <spawn.h>\n#include <poll.h>\n\n#define MY_NAME \"kcov_wrapper\"\n\nstatic inline void say_perror(const char *msg)\n{\n    fprintf(stderr, \"%s: %s: %s\\n\", MY_NAME, msg, strerror(errno));\n}\n\nstatic inline void say_error(const char *msg)\n{\n    fprintf(stderr, \"%s: %s\\n\", MY_NAME, msg);\n}\n\nenum {\n    RC_SPAWN_ERROR = 50,\n    RC_UNEXPECTED_ERROR = 51,\n    RC_CHILD_TERMINATED_IN_WEIRD_WAY = 99,\n};\n\nstatic int self_pipe_fds[2] = {-1, -1};\n\nstatic int make_cloexec(int fd)\n{\n    int flags = fcntl(fd, F_GETFD);\n    if (flags < 0) {\n        return -1;\n    }\n    flags |= FD_CLOEXEC;\n    if (fcntl(fd, F_SETFD, flags) < 0) {\n        return -1;\n    }\n    return fd;\n}\n\nstatic bool make_self_pipe(void)\n{\n    if (pipe(self_pipe_fds) < 0) {\n        say_perror(\"pipe\");\n        return false;\n    }\n    (void) make_cloexec(self_pipe_fds[0]);\n    (void) make_cloexec(self_pipe_fds[1]);\n    return true;\n}\n\nstatic int full_read(int fd, char *buf, size_t nbuf)\n{\n    size_t nread = 0;\n    while (nread != nbuf) {\n        ssize_t r = read(fd, buf + nread, nbuf - nread);\n        if (r < 0) {\n            if (errno == EINTR) {\n                continue;\n            }\n            return -1;\n        } else if (r == 0) {\n            return 0;\n        } else {\n            nread += r;\n        }\n    }\n    return 1;\n}\n\nstatic int wait_for_input_on_fd(int fd, int timeout_ms)\n{\n    struct pollfd pfd = {.fd = fd, .events = POLLIN};\n    int rc = poll(&pfd, 1, timeout_ms);\n    if (rc < 0 && errno != EINTR) {\n        say_perror(\"poll\");\n        abort();\n    }\n    return rc;\n}\n\nstatic int full_write(int fd, const char *data, size_t ndata)\n{\n    size_t nwritten = 0;\n    while (nwritten != ndata) {\n        ssize_t w = write(fd, data + nwritten, ndata - nwritten);\n        if (w < 0) {\n            if (errno == EINTR) {\n                continue;\n            }\n            return -1;\n        }\n        nwritten += w;\n    }\n    return 0;\n}\n\nstatic void sig_handler(int signo)\n{\n    int saved_errno = errno;\n\n    if (full_write(self_pipe_fds[1], (char *) &signo, sizeof(signo)) < 0) {\n        abort();\n    }\n\n    errno = saved_errno;\n}\n\nstatic bool handle_signal(int signo)\n{\n    sigset_t empty;\n    if (sigemptyset(&empty) < 0) {\n        say_perror(\"sigemptyset\");\n        return false;\n    }\n\n    int flags = SA_RESTART;\n    if (signo == SIGCHLD) {\n        flags |= SA_NOCLDSTOP;\n    }\n\n    struct sigaction sa = {\n        .sa_handler = sig_handler,\n        .sa_mask = empty,\n        .sa_flags = flags,\n    };\n    if (sigaction(signo, &sa, NULL) < 0) {\n        say_perror(\"sigaction\");\n        return false;\n    }\n\n    return true;\n}\n\nstatic const int TERM_SIGNALS[] = {\n    SIGINT,\n    SIGTERM,\n    SIGHUP,\n    SIGQUIT,\n};\nenum { TERM_SIGNALS_NUM = sizeof(TERM_SIGNALS) / sizeof(TERM_SIGNALS[0]) };\n\nstatic bool install_signal_handlers(void)\n{\n    if (!handle_signal(SIGCHLD)) {\n        return false;\n    }\n    for (int i = 0; i < TERM_SIGNALS_NUM; ++i) {\n        if (!handle_signal(TERM_SIGNALS[i])) {\n            return false;\n        }\n    }\n    return true;\n}\n\nstatic int wait_and_make_exit_code(pid_t pid)\n{\n    int status;\n    pid_t rc;\n    while ((rc = waitpid(pid, &status, 0)) < 0 && errno == EINTR) {\n        // do nothing\n    }\n    if (rc < 0) {\n        say_perror(\"waitpid\");\n        return RC_UNEXPECTED_ERROR;\n    } else if (rc == 0) {\n        say_error(\"waitpid returned 0\");\n        return RC_UNEXPECTED_ERROR;\n    }\n\n    if (WIFEXITED(status)) {\n        return WEXITSTATUS(status);\n    }\n    if (WIFSIGNALED(status)) {\n        return 128 + WTERMSIG(status);\n    }\n    return RC_CHILD_TERMINATED_IN_WEIRD_WAY;\n}\n\nstatic char *read_line(FILE *f)\n{\n    char *buf = NULL;\n    size_t nbuf = 0;\n    if (getline(&buf, &nbuf, f) < 0) {\n        free(buf);\n        return NULL;\n    }\n    return buf;\n}\n\nstatic bool parse_imax(const char *s, intmax_t *out)\n{\n    errno = 0;\n    char *endptr;\n    *out = strtoimax(s, &endptr, 10);\n    if (errno || endptr == s || endptr[0] != '\\0') {\n        return false;\n    }\n    return true;\n}\n\nstatic void trim_newline(char *s)\n{\n    size_t ns = strlen(s);\n    if (ns && s[ns - 1] == '\\n') {\n        s[ns - 1] = '\\0';\n    }\n}\n\nstatic pid_t get_grandchild_pid(pid_t child_pid)\n{\n    static const char *SHELL_PROGRAM_SUFFIX =\n        \"cd /proc || exit $?\\n\"\n        \"exec 2>/dev/null || exit $?\\n\"\n        \"for pid in [0-9]*; do\\n\"\n        \"  read _ _ _ ppid _ < $pid/stat || continue\\n\"\n        \"  if [ $ppid = $target_pid ]; then\\n\"\n        \"    x=$(readlink $pid/exe) || continue\\n\"\n        \"    case \\\"$x\\\" in\\n\"\n        \"    */luastatus) echo $pid; exit $? ;;\\n\"\n        \"    esac\"\n        \"  fi\\n\"\n        \"done\\n\"\n        \"echo 0\\n\"\n    ;\n\n    enum { CAPACITY = 16 * 1024 };\n    char *prog = malloc(CAPACITY);\n    if (!prog) {\n        say_perror(\"malloc\");\n        abort();\n    }\n    int nprog = snprintf(prog, CAPACITY, \"target_pid=%jd; %s\", (intmax_t) child_pid, SHELL_PROGRAM_SUFFIX);\n    if (nprog < 0 || nprog > CAPACITY - 32) {\n        say_error(\"snprintf failed (shell program)\");\n        abort();\n    }\n\n    pid_t res = -1;\n\n    char *line = NULL;\n    FILE *f = popen(prog, \"r\");\n    if (!f) {\n        say_perror(\"popen\");\n        goto cleanup;\n    }\n\n    line = read_line(f);\n    if (!line) {\n        say_perror(\"cannot read line from shell program\");\n        goto cleanup;\n    }\n    trim_newline(line);\n    intmax_t raw_res;\n    if (!parse_imax(line, &raw_res)) {\n        say_error(\"cannot parse line from shell program into intmax_t\");\n        goto cleanup;\n    }\n    res = raw_res;\n\ncleanup:\n    free(line);\n    if (f) {\n        int status = pclose(f);\n        if (status < 0 || !WIFEXITED(status) || WEXITSTATUS(status) != 0) {\n            say_error(\"our shell program failed\");\n            res = -1;\n        }\n    }\n    free(prog);\n    return res;\n}\n\ntypedef struct {\n    pid_t child_pid;\n    pid_t grandchild_pid;\n} State;\n\nstatic bool state_has_grandchild_pid(State *s)\n{\n    if (s->grandchild_pid) {\n        return true;\n    }\n    pid_t new_pid = get_grandchild_pid(s->child_pid);\n    if (new_pid < 0) {\n        say_error(\"(warning) cannot get real luastatus' PID\");\n        return false;\n    } else if (new_pid == 0) {\n        say_error(\"(warning) real luastatus was not spawned yet\");\n        return false;\n    } else {\n        fprintf(stderr, \"%s: info: real luastatus' PID = %jd\\n\", MY_NAME, (intmax_t) new_pid);\n        s->grandchild_pid = new_pid;\n        return true;\n    }\n}\n\nstatic void state_kill(State *s, int signo)\n{\n    fprintf(stderr, \"%s: info: killing luastatus with signal %d\\n\", MY_NAME, signo);\n    if (kill(s->grandchild_pid, signo) < 0) {\n        say_perror(\"(warning) kill\");\n    }\n}\n\ntypedef struct {\n    int signums[TERM_SIGNALS_NUM];\n} QueuedSignals;\n\n#define queued_signals_nonempty(Q_) (((Q_)->signums[0]) != 0)\n\n#define queued_signals_at(Q_, Idx_) ((Q_)->signums[(Idx_)])\n\nstatic inline void queued_signals_add(QueuedSignals *q, int signo)\n{\n    bool found = false;\n    for (int i = 0; i < TERM_SIGNALS_NUM; ++i) {\n        found |= (TERM_SIGNALS[i] == signo);\n    }\n    if (!found) {\n        return;\n    }\n    for (int i = 0; i < TERM_SIGNALS_NUM; ++i) {\n        if (q->signums[i] == signo) {\n            return;\n        } else if (!q->signums[i]) {\n            q->signums[i] = signo;\n            return;\n        }\n    }\n}\n\nint main(int argc, char **argv)\n{\n    if (argc < 2) {\n        fprintf(stderr, \"USAGE: %s COMMAND [ARGS...]\\n\", MY_NAME);\n        return 2;\n    }\n\n    char **child_argv = argv + 1;\n\n    if (!make_self_pipe()) {\n        return RC_UNEXPECTED_ERROR;\n    }\n    if (!install_signal_handlers()) {\n        return RC_UNEXPECTED_ERROR;\n    }\n\n    extern char **environ;\n\n    pid_t child_pid;\n    int rc = posix_spawnp(\n        &child_pid,\n        child_argv[0],\n        NULL,\n        NULL,\n        child_argv,\n        environ);\n\n    if (rc != 0) {\n        errno = rc;\n        say_perror(\"posix_spawnp\");\n        return RC_SPAWN_ERROR;\n    }\n\n    fprintf(stderr, \"%s: info: child PID: %jd\\n\", MY_NAME, (intmax_t) child_pid);\n\n    State state = {.child_pid = child_pid, .grandchild_pid = 0};\n\n    QueuedSignals q = {0};\n\n    for (;;) {\n\n        int poll_rc = wait_for_input_on_fd(self_pipe_fds[0], /*timeout_ms=*/ 200);\n        if (poll_rc == 0) {\n            // Timeout\n            if (queued_signals_nonempty(&q)) {\n                say_error(\"timeout and we have queued signals\");\n                if (state_has_grandchild_pid(&state)) {\n                    for (int i = 0; i < TERM_SIGNALS_NUM; ++i) {\n                        int signo = queued_signals_at(&q, i);\n                        if (signo) {\n                            state_kill(&state, signo);\n                        }\n                    }\n                }\n                q = (QueuedSignals) {0};\n            }\n            continue;\n        }\n\n        int signo;\n        int read_rc = full_read(self_pipe_fds[0], (char *) &signo, sizeof(signo));\n        if (read_rc < 0) {\n            say_perror(\"read (from self-pipe)\");\n            return RC_UNEXPECTED_ERROR;\n        } else if (read_rc == 0) {\n            say_error(\"read (from self-pipe) returned 0\");\n            return RC_UNEXPECTED_ERROR;\n        }\n\n        if (signo == SIGCHLD) {\n            fprintf(stderr, \"%s: info: got SIGCHLD\\n\", MY_NAME);\n            return wait_and_make_exit_code(child_pid);\n        } else {\n            fprintf(stderr, \"%s: info: got signal %d\\n\", MY_NAME, signo);\n\n            if (state_has_grandchild_pid(&state)) {\n                state_kill(&state, signo);\n            } else {\n                say_error(\"queueing this signal\");\n                queued_signals_add(&q, signo);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "tests/listnets.c",
    "content": "/*\n * Copyright (C) 2021-2025  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#include <sys/types.h>\n#include <ifaddrs.h>\n#include <stdio.h>\n#include <sys/socket.h>\n#include <netdb.h>\n#include <netinet/in.h>\n\nint main()\n{\n    struct ifaddrs *nets;\n    if (getifaddrs(&nets) < 0) {\n        perror(\"listnets: getifaddrs\");\n        return 1;\n    }\n    for (struct ifaddrs *p = nets; p; p = p->ifa_next) {\n        if (!p->ifa_addr) {\n            continue;\n        }\n        int family = p->ifa_addr->sa_family;\n        if (family != AF_INET && family != AF_INET6) {\n            continue;\n        }\n        char host[1025];\n        int r = getnameinfo(\n            p->ifa_addr,\n            family == AF_INET ? sizeof(struct sockaddr_in) : sizeof(struct sockaddr_in6),\n            host, sizeof(host),\n            NULL, 0, NI_NUMERICHOST);\n        if (r) {\n            if (r == EAI_SYSTEM) {\n                perror(\"listnets: getnameinfo\");\n            } else {\n                fprintf(stderr, \"listnets: getnameinfo: %s\\n\", gai_strerror(r));\n            }\n            continue;\n        }\n        printf(\"%s %s %s\\n\", p->ifa_name, family == AF_INET ? \"ipv4\" : \"ipv6\", host);\n    }\n    freeifaddrs(nets);\n    return 0;\n}\n"
  },
  {
    "path": "tests/minstd.h",
    "content": "/*\n * Copyright (C) 2015-2025  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifndef minstd_h_\n#define minstd_h_\n\n#include <stdint.h>\n#include <time.h>\n#include <unistd.h>\n#include \"libls/ls_compdep.h\"\n#include \"libls/ls_panic.h\"\n\n// MINSTD pseudo-random number generator, year 1990 version.\n\nLS_INHEADER uint32_t minstd_make_up_some_seed(void)\n{\n    struct timespec ts;\n    if (clock_gettime(CLOCK_REALTIME, &ts) < 0) {\n        return getpid();\n    }\n\n    uint32_t nsec = ts.tv_nsec;\n    uint32_t sec = ts.tv_sec;\n\n    return nsec + sec * (1000 * 1000 * 1000);\n}\n\ntypedef struct {\n    uint32_t x;\n} MINSTD_Prng;\n\nenum {\n    MINSTD_M = 0x7fffffff,\n    MINSTD_A = 48271,\n};\n\nLS_INHEADER MINSTD_Prng minstd_prng_new(uint32_t seed)\n{\n    uint32_t res = seed % MINSTD_M;\n    if (!res) {\n        res = 1 + (seed % (MINSTD_M - 1));\n    }\n    return (MINSTD_Prng) {res};\n}\n\nLS_INHEADER uint32_t minstd_prng_next_raw(MINSTD_Prng *P)\n{\n    uint64_t prod = ((uint64_t) P->x) * MINSTD_A;\n    P->x = prod % MINSTD_M;\n    return P->x;\n}\n\nLS_INHEADER uint64_t minstd_prng_next_u64(MINSTD_Prng *P)\n{\n    uint64_t res = 0;\n    for (int i = 0; i < 5; ++i) {\n        res *= MINSTD_M;\n        res += minstd_prng_next_raw(P);\n    }\n    return res;\n}\n\n// Returns random integer x such that 0 <= x < limit.\nLS_INHEADER uint32_t minstd_prng_next_limit_u32(MINSTD_Prng *P, uint32_t limit)\n{\n    LS_ASSERT(limit != 0);\n    return minstd_prng_next_u64(P) % limit;\n}\n\n#endif\n"
  },
  {
    "path": "tests/mock_barlib.c",
    "content": "/*\n * Copyright (C) 2015-2025  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#include <stdlib.h>\n#include <stdint.h>\n#include <inttypes.h>\n\n#include <lua.h>\n\n#include \"include/barlib_v1.h\"\n#include \"include/sayf_macros.h\"\n\n#include \"libls/ls_alloc_utils.h\"\n#include \"libls/ls_cstring_utils.h\"\n#include \"libls/ls_parse_int.h\"\n\n#include \"minstd.h\"\n\ntypedef struct {\n    size_t nwidgets;\n    unsigned char *widgets;\n    int nevents;\n    int64_t prng_seed;\n} Priv;\n\nstatic void destroy(LuastatusBarlibData *bd)\n{\n    Priv *p = bd->priv;\n    free(p->widgets);\n    free(p);\n}\n\nstatic int init(LuastatusBarlibData *bd, const char *const *opts, size_t nwidgets)\n{\n    Priv *p = bd->priv = LS_XNEW(Priv, 1);\n    *p = (Priv) {\n        .nwidgets = nwidgets,\n        .widgets = LS_XNEW0(unsigned char, nwidgets),\n        .nevents = 0,\n        .prng_seed = -1,\n    };\n\n    if (nwidgets > (size_t) INT32_MAX) {\n        LS_FATALF(bd, \"too many widgets\");\n        goto error;\n    }\n\n    for (const char *const *s = opts; *s; ++s) {\n        const char *v;\n        if ((v = ls_strfollow(*s, \"gen_events=\"))) {\n            if ((p->nevents = ls_full_strtou(v)) < 0) {\n                LS_FATALF(bd, \"gen_events value is not a proper integer\");\n                goto error;\n            }\n\n        } else if ((v = ls_strfollow(*s, \"prng_seed=\"))) {\n            if ((p->prng_seed = ls_full_strtou(v)) < 0) {\n                LS_FATALF(bd, \"prng_seed value is not a proper integer\");\n                goto error;\n            }\n\n        } else {\n            LS_FATALF(bd, \"unknown option: '%s'\", *s);\n            goto error;\n        }\n    }\n\n    return LUASTATUS_OK;\nerror:\n    destroy(bd);\n    return LUASTATUS_ERR;\n}\n\nstatic int set(LuastatusBarlibData *bd, lua_State *L, size_t widget_idx)\n{\n    Priv *p = bd->priv;\n    if (!lua_isnil(L, -1)) {\n        LS_ERRF(bd, \"got non-nil data\");\n        return LUASTATUS_NONFATAL_ERR;\n    }\n    ++p->widgets[widget_idx];\n    return LUASTATUS_OK;\n}\n\nstatic int set_error(LuastatusBarlibData *bd, size_t widget_idx)\n{\n    Priv *p = bd->priv;\n    --p->widgets[widget_idx];\n    return LUASTATUS_OK;\n}\n\nstatic int event_watcher(LuastatusBarlibData *bd, LuastatusBarlibEWFuncs funcs)\n{\n    Priv *p = bd->priv;\n    if (p->nevents && !p->nwidgets) {\n        LS_FATALF(bd, \"no widgets to generate events on\");\n        return LUASTATUS_ERR;\n    }\n\n    uint32_t seed;\n    if (p->prng_seed >= 0) {\n        seed = (uint32_t) p->prng_seed;\n    } else {\n        static const uint32_t SALT = 41744457;\n        seed = minstd_make_up_some_seed() ^ SALT;\n        LS_INFOF(bd, \"Seed: %\" PRIu32, seed);\n    }\n    MINSTD_Prng my_prng = minstd_prng_new(seed);\n\n    for (int i = 0; i < p->nevents; ++i) {\n        size_t widget_idx = minstd_prng_next_limit_u32(&my_prng, p->nwidgets);\n        lua_State *L = funcs.call_begin(bd->userdata, widget_idx);\n        lua_pushnil(L);\n        funcs.call_end(bd->userdata, widget_idx);\n    }\n    return LUASTATUS_NONFATAL_ERR;\n}\n\nLuastatusBarlibIface luastatus_barlib_iface_v1 = {\n    .init = init,\n    .set = set,\n    .set_error = set_error,\n    .event_watcher = event_watcher,\n    .destroy = destroy,\n};\n"
  },
  {
    "path": "tests/mock_plugin.c",
    "content": "/*\n * Copyright (C) 2015-2025  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#include <lua.h>\n#include <stdlib.h>\n#include <stdint.h>\n#include <stdbool.h>\n\n#include \"include/plugin_v1.h\"\n#include \"include/sayf_macros.h\"\n\n#include \"libmoonvisit/moonvisit.h\"\n\n#include \"libls/ls_alloc_utils.h\"\n\ntypedef struct {\n    uint64_t ncalls;\n} Priv;\n\nstatic void destroy(LuastatusPluginData *pd)\n{\n    Priv *p = pd->priv;\n    free(p);\n}\n\nstatic int init(LuastatusPluginData *pd, lua_State *L)\n{\n    Priv *p = pd->priv = LS_XNEW(Priv, 1);\n    *p = (Priv) {\n        .ncalls = 0,\n    };\n\n    char errbuf[256];\n    MoonVisit mv = {.L = L, .errbuf = errbuf, .nerrbuf = sizeof(errbuf)};\n\n    // Parse make_calls\n    if (moon_visit_uint(&mv, -1, \"make_calls\", &p->ncalls, true) < 0) {\n        goto mverror;\n    }\n\n    return LUASTATUS_OK;\n\nmverror:\n    LS_FATALF(pd, \"%s\", errbuf);\n//error:\n    destroy(pd);\n    return LUASTATUS_ERR;\n}\n\nstatic void run(LuastatusPluginData *pd, LuastatusPluginRunFuncs funcs)\n{\n    Priv *p = pd->priv;\n    for (uint64_t i = 0; i < p->ncalls; ++i) {\n        lua_State *L = funcs.call_begin(pd->userdata);\n        lua_pushnil(L);\n        funcs.call_end(pd->userdata);\n    }\n}\n\nLuastatusPluginIface luastatus_plugin_iface_v1 = {\n    .init = init,\n    .run = run,\n    .destroy = destroy,\n};\n"
  },
  {
    "path": "tests/optional/dbus_srv.py",
    "content": "#!/usr/bin/env python3\n\n# Copyright (C) 2026  luastatus developers\n#\n# This file is part of luastatus.\n#\n# luastatus is free software: you can redistribute it and/or modify\n# it under the terms of the GNU Lesser General Public License as published by\n# the Free Software Foundation, either version 3 of the License, or\n# (at your option) any later version.\n#\n# luastatus is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n# GNU Lesser General Public License for more details.\n#\n# You should have received a copy of the GNU Lesser General Public License\n# along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n\n# ================================================================================\n# This is a simple D-Bus server. It is needed for testing plugin functions of the\n# \"dbus\" plugin.\n# ================================================================================\n\nimport sys\n\nimport gi.repository.GLib\nimport dbus\nimport dbus.service\nimport dbus.mainloop.glib\n\n\nMY_INTERFACE = 'io.github.shdown.luastatus.test'\n\n\nMY_BUS_NAME = 'io.github.shdown.luastatus.test'\n\n\nMY_OBJECT_PATH = '/io/github/shdown/luastatus/test/MyObject'\n\n\nPROP_INTERFACE = 'org.freedesktop.DBus.Properties'\n\n\ndef log(s):\n    print(f'dbus_srv.py: {s}', file=sys.stderr)\n\n\nclass MyServerObject(dbus.service.Object):\n    def __init__(self, bus, object_path):\n        dbus.service.Object.__init__(self, bus, object_path, MY_BUS_NAME)\n        self.prop = ''\n\n    @dbus.service.method(MY_INTERFACE, in_signature='s', out_signature='s')\n    def Upcase(self, msg):\n        msg = str(msg)\n        log(f'Called Upcase on \"{msg}\"')\n        return str(msg).upper()\n\n    @dbus.service.method(MY_INTERFACE, in_signature='', out_signature='i')\n    def ReturnFortyTwo(self):\n        log('Called ReturnFortyTwo')\n        return 42\n\n    @dbus.service.method(MY_INTERFACE, in_signature='as', out_signature='a{ss}')\n    def ConvertArrayToDictHexify(self, arr):\n        log('Called ConvertArrayToDictHexify')\n        def _hexify(s):\n            return s.encode('utf8').hex().upper()\n        return {\n            _hexify(str(i)): _hexify(s)\n            for i, s in enumerate(arr)\n        }\n\n    @dbus.service.method(MY_INTERFACE, in_signature='a{ss}', out_signature='s')\n    def ConvertDictToString(self, d):\n        log('Called ConvertDictToString')\n        return ','.join(f'{key}:{value}' for key, value in sorted(d.items()))\n\n    @dbus.service.method(MY_INTERFACE, in_signature='', out_signature='s')\n    def RecvTuple0(self):\n        log('Called RecvTuple0')\n        return 'Empty'\n\n    @dbus.service.method(MY_INTERFACE, in_signature='s', out_signature='s')\n    def RecvTuple1(self, arg):\n        log('Called RecvTuple1')\n        return arg\n\n    @dbus.service.method(MY_INTERFACE, in_signature='ss', out_signature='s')\n    def RecvTuple2(self, arg1, arg2):\n        log('Called RecvTuple2')\n        return arg1 + arg2\n\n    @dbus.service.method(MY_INTERFACE, in_signature='sss', out_signature='s')\n    def RecvTuple3(self, arg1, arg2, arg3):\n        log('Called RecvTuple3')\n        return arg1 + arg2 + arg3\n\n    @dbus.service.method(MY_INTERFACE, in_signature='v', out_signature='s')\n    def RecvVariant(self, x):\n        log(f'Called RecvVariant, type(x)={type(x)}')\n\n        TABLE = [\n            (dbus.Boolean, 'bool'),\n            (dbus.Double, 'double'),\n            (dbus.Byte, 'byte'),\n            (dbus.String, 'string'),\n            (dbus.ObjectPath, 'object_path'),\n            (dbus.Signature, 'signature'),\n\n            (dbus.Int16, 'i16'),\n            (dbus.Int32, 'i32'),\n            (dbus.Int64, 'i64'),\n\n            (dbus.UInt16, 'u16'),\n            (dbus.UInt32, 'u32'),\n            (dbus.UInt64, 'u64'),\n        ]\n\n        if hasattr(x, 'variant_level'):\n            if x.variant_level > 0:\n                return 'variant'\n\n        for dbus_type, retval in TABLE:\n            if isinstance(x, dbus_type):\n                return retval\n\n        raise ValueError('Got Variant wrapping an unsupported type')\n\n    @dbus.service.method(PROP_INTERFACE, in_signature='ss', out_signature='s')\n    def Get(self, _, prop_name):\n        prop_name = str(prop_name)\n        log(f'Called Get (property): prop_name=\"{prop_name}\"')\n        if prop_name != 'MyProperty':\n            raise ValueError('wrong property name')\n        return self.prop\n\n    @dbus.service.method(PROP_INTERFACE, in_signature='s', out_signature='a{sv}')\n    def GetAll(self, _):\n        log('Called GetAll (properties)')\n        return {\n            'MyProperty': self.prop,\n        }\n\n    @dbus.service.method(PROP_INTERFACE, in_signature='sss', out_signature='')\n    def Set(self, _, prop_name, value):\n        prop_name = str(prop_name)\n        value = str(value)\n        log(f'Called Set (property): prop_name=\"{prop_name}\", value=\"{value}\"')\n        if prop_name != 'MyProperty':\n            raise ValueError('wrong property name')\n        self.prop = value\n\n\ndef main():\n    args = sys.argv[1:]\n    if len(args) != 1:\n        print(f'USAGE: {sys.argv[0]} FIFO_FILE', file=sys.stderr)\n        sys.exit(2)\n\n    log('Opening FIFO...')\n    with open(args[0], 'w') as fifo_file:\n        log('OK, opened')\n\n        dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)\n\n        bus = dbus.SessionBus()\n        bus_name = dbus.service.BusName(MY_BUS_NAME, bus, do_not_queue=True)\n\n        log(f'Acquired bus name \"{bus_name.get_name()}\"')\n\n        my_object = MyServerObject(bus, MY_OBJECT_PATH)\n\n        mainloop = gi.repository.GLib.MainLoop()\n\n        log('Starting up')\n        print('running', file=fifo_file, flush=True)\n        mainloop.run()\n\n\nif __name__ == '__main__':\n    main()\n"
  },
  {
    "path": "tests/parrot.c",
    "content": "/*\n * Copyright (C) 2021-2025  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#include <pthread.h>\n#include <errno.h>\n#include <stdlib.h>\n#include <stdio.h>\n#include <stdbool.h>\n#include <unistd.h>\n#include <fcntl.h>\n#include <string.h>\n#include <sys/types.h>\n#include <sys/socket.h>\n#include <sys/un.h>\n#include <arpa/inet.h>\n#include <netinet/in.h>\n\nstatic struct {\n    bool half_duplex_ok;\n    bool reuseaddr;\n    bool print_line_when_ready;\n    bool print_line_on_accept;\n    bool just_check;\n} global_flags;\n\nstatic void fail(void)\n{\n    _exit(1);\n}\n\nstatic int full_write(int fd, const char *buf, size_t nbuf)\n{\n    for (size_t nwritten = 0; nwritten < nbuf;) {\n        ssize_t w = write(fd, buf + nwritten, nbuf - nwritten);\n        if (w < 0) {\n            perror(\"parrot: write\");\n            return -1;\n        }\n        nwritten += w;\n    }\n    return 0;\n}\n\ntypedef struct {\n    int read_fd;\n    int write_fd;\n} ThreadArg;\n\nstatic void *thread_func(void *varg)\n{\n    ThreadArg *arg = varg;\n    int read_fd = arg->read_fd;\n    int write_fd = arg->write_fd;\n\n    char buf[1024];\n    for (;;) {\n        ssize_t r = read(read_fd, buf, sizeof(buf));\n        if (r < 0) {\n            perror(\"parrot: read\");\n            fail();\n        }\n        if (r == 0) {\n            break;\n        }\n        if (full_write(write_fd, buf, r) < 0) {\n            fail();\n        }\n    }\n\n    if (!global_flags.half_duplex_ok) {\n        _exit(0);\n    }\n    return NULL;\n}\n\nstatic void parrot(int other_fd)\n{\n    ThreadArg args[2] = {\n        {.read_fd = other_fd, .write_fd = 1},\n        {.read_fd = 0, .write_fd = other_fd},\n    };\n    pthread_t threads[2];\n    for (int i = 0; i < 2; ++i) {\n        if ((errno = pthread_create(&threads[i], NULL, thread_func, &args[i]))) {\n            perror(\"parrot: pthead_create\");\n            fail();\n        }\n    }\n    for (int i = 0; i < 2; ++i) {\n        if ((errno = pthread_join(threads[i], NULL))) {\n            perror(\"parrot: pthread_join\");\n            fail();\n        }\n    }\n}\n\ntypedef struct {\n    bool *out;\n    const char *name;\n} FlagOption;\n\nstatic FlagOption flag_options[] = {\n    {&global_flags.half_duplex_ok,        \"--half-duplex-ok\"},\n    {&global_flags.reuseaddr,             \"--reuseaddr\"},\n    {&global_flags.print_line_when_ready, \"--print-line-when-ready\"},\n    {&global_flags.print_line_on_accept,  \"--print-line-on-accept\"},\n    {&global_flags.just_check,            \"--just-check\"},\n    {0},\n};\n\nstatic void print_usage(void)\n{\n    fprintf(stderr, \"USAGE: parrot [options...] {TCP-CLIENT port | TCP-SERVER port | UNIX-CLIENT path | UNIX-SERVER path}\\n\");\n    fprintf(stderr, \"Supported options:\\n\");\n    fprintf(stderr, \"  --half-duplex-ok\\n\");\n    fprintf(stderr, \"    Continue even after one side has hung up.\\n\");\n    fprintf(stderr, \"  --reuseaddr\\n\");\n    fprintf(stderr, \"    Set SO_REUSEADDR option on the TCP server socket.\\n\");\n    fprintf(stderr, \"  --print-line-when-ready\\n\");\n    fprintf(stderr, \"    Write 'parrot: ready' line when the server is ready to accept a connection.\\n\");\n    fprintf(stderr, \"  --print-line-on-accept\\n\");\n    fprintf(stderr, \"    Write 'parrot: accepted' line when the server has accepted a connection.\\n\");\n    fprintf(stderr, \"  --just-check\\n\");\n    fprintf(stderr, \"    Exit with code 0 after creating the server.\\n\");\n}\n\nstatic int client(int (*func)(const char *arg), const char *arg)\n{\n    int fd = func(arg);\n    if (fd < 0) {\n        fail();\n    }\n    parrot(fd);\n    close(fd);\n    return 0;\n}\n\nstatic int server(int (*func)(const char *arg), const char *arg)\n{\n    int server_fd = func(arg);\n    if (server_fd < 0) {\n        fail();\n    }\n\n    if (global_flags.just_check) {\n        close(server_fd);\n        return 0;\n    }\n\n    if (global_flags.print_line_when_ready) {\n        const char *line = \"parrot: ready\\n\";\n        if (full_write(1, line, strlen(line)) < 0) {\n            fail();\n        }\n    }\n\n    int client_fd = accept(server_fd, NULL, NULL);\n    if (client_fd < 0) {\n        perror(\"parrot: accept\");\n        fail();\n    }\n    close(server_fd);\n\n    if (global_flags.print_line_on_accept) {\n        const char *line = \"parrot: accepted\\n\";\n        if (full_write(1, line, strlen(line)) < 0) {\n            fail();\n        }\n    }\n\n    parrot(client_fd);\n    close(client_fd);\n    return 0;\n}\n\nstatic in_addr_t localhost(void)\n{\n    in_addr_t res = inet_addr(\"127.0.0.1\");\n    if (res == (in_addr_t) -1) {\n        perror(\"parrot: inet_addr\");\n        fail();\n    }\n    return res;\n}\n\nstatic int make_unix_client(const char *path)\n{\n    int fd = -1;\n\n    struct sockaddr_un saun = {.sun_family = AF_UNIX};\n    size_t npath = strlen(path);\n    if (npath + 1 > sizeof(saun.sun_path)) {\n        fprintf(stderr, \"parrot: socket path is too long.\\n\");\n        goto error;\n    }\n    memcpy(saun.sun_path, path, npath + 1);\n    fd = socket(PF_UNIX, SOCK_STREAM, 0);\n    if (fd < 0) {\n        perror(\"parrot: socket\");\n        goto error;\n    }\n    if (connect(fd, (struct sockaddr *) &saun, sizeof(saun)) < 0) {\n        perror(\"parrot: connect\");\n        goto error;\n    }\n    return fd;\nerror:\n    close(fd);\n    return -1;\n}\n\nstatic bool parse_uint(const char *s, unsigned *out)\n{\n    char *endptr;\n    errno = 0;\n    *out = strtoul(s, &endptr, 10);\n    if (errno || endptr == s || endptr[0] != '\\0') {\n        return false;\n    }\n    return true;\n}\n\nstatic int make_tcp_client(const char *portstr)\n{\n    int fd = -1;\n\n    unsigned port;\n    if (!parse_uint(portstr, &port)) {\n        fprintf(stderr, \"parrot: invalid port number: '%s'.\\n\", portstr);\n        goto error;\n    }\n\n    fd = socket(AF_INET, SOCK_STREAM, 0);\n    if (fd < 0) {\n        perror(\"parrot: socket\");\n        goto error;\n    }\n\n    struct sockaddr_in sain = {\n        .sin_family = AF_INET,\n        .sin_addr = {.s_addr = localhost()},\n        .sin_port = htons(port),\n    };\n    if (connect(fd, (struct sockaddr *) &sain, sizeof(sain)) < 0) {\n        perror(\"parrot: connect\");\n        goto error;\n    }\n    return fd;\n\nerror:\n    close(fd);\n    return -1;\n}\n\nstatic int make_tcp_server(const char *portstr)\n{\n    int fd = -1;\n\n    unsigned port;\n    if (!parse_uint(portstr, &port)) {\n        fprintf(stderr, \"parrot: invalid port number: '%s'.\\n\", portstr);\n        goto error;\n    }\n\n    fd = socket(AF_INET, SOCK_STREAM, 0);\n    if (fd < 0) {\n        perror(\"parrot: socket\");\n        goto error;\n    }\n\n    if (global_flags.reuseaddr) {\n        int value = 1;\n        if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &value, sizeof(value)) < 0) {\n            perror(\"parrot: setsockopt (SO_REUSEADDR)\");\n            goto error;\n        }\n    }\n\n    struct sockaddr_in sain = {\n        .sin_family = AF_INET,\n        .sin_addr = {.s_addr = localhost()},\n        .sin_port = htons(port),\n    };\n    if (bind(fd, (struct sockaddr *) &sain, sizeof(sain)) < 0) {\n        perror(\"parrot: bind\");\n        goto error;\n    }\n    if (listen(fd, SOMAXCONN) < 0) {\n        perror(\"parrot: listen\");\n        goto error;\n    }\n    return fd;\n\nerror:\n    close(fd);\n    return -1;\n}\n\nstatic int make_unix_server(const char *path)\n{\n    int fd = socket(AF_UNIX, SOCK_STREAM, 0);\n    if (fd < 0) {\n        perror(\"parrot: socket\");\n        goto error;\n    }\n\n    struct sockaddr_un saun = {.sun_family = AF_UNIX};\n    size_t npath = strlen(path);\n    if (npath + 1 > sizeof(saun.sun_path)) {\n        fprintf(stderr, \"parrot: socket path is too long.\\n\");\n        goto error;\n    }\n    memcpy(saun.sun_path, path, npath + 1);\n\n    if (bind(fd, (struct sockaddr *) &saun, sizeof(saun)) < 0) {\n        perror(\"parrot: bind\");\n        goto error;\n    }\n    if (listen(fd, SOMAXCONN) < 0) {\n        perror(\"parrot: listen\");\n        goto error;\n    }\n    return fd;\n\nerror:\n    close(fd);\n    return fd;\n}\n\nint main(int argc, char **argv)\n{\n    int argi = 1;\n    for (; argi < argc; ++argi) {\n        const char *arg = argv[argi];\n        if (arg[0] != '-') {\n            break;\n        }\n        bool *out = NULL;\n        for (FlagOption *o = flag_options; o->out; ++o) {\n            if (strcmp(arg, o->name) == 0) {\n                out = o->out;\n                break;\n            }\n        }\n        if (!out) {\n            fprintf(stderr, \"Unknown option: '%s'.\\n\", arg);\n            print_usage();\n            return 2;\n        }\n        *out = true;\n    }\n    if (argc - argi != 2) {\n        fprintf(stderr, \"Expected exactly 2 positional arguments, found %d.\\n\", argc - argi);\n        print_usage();\n        return 2;\n    }\n    const char *what     = argv[argi];\n    const char *what_arg = argv[argi + 1];\n\n    if (strcmp(what, \"TCP-CLIENT\") == 0) {\n        return client(make_tcp_client, what_arg);\n\n    } else if (strcmp(what, \"TCP-SERVER\") == 0) {\n        return server(make_tcp_server, what_arg);\n\n    } else if (strcmp(what, \"UNIX-CLIENT\") == 0) {\n        return client(make_unix_client, what_arg);\n\n    } else if (strcmp(what, \"UNIX-SERVER\") == 0) {\n        return server(make_unix_server, what_arg);\n\n    } else {\n        fprintf(stderr, \"Unknown command: '%s'.\\n\", what);\n        print_usage();\n        return 2;\n    }\n}\n"
  },
  {
    "path": "tests/pt.sh",
    "content": "#!/usr/bin/env bash\n\nset -e\n\nPT_OPWD=$PWD\ncd -- \"$(dirname \"$(readlink \"$0\" || printf '%s\\n' \"$0\")\")\"\n\nsource ./utils.lib.bash\n\nfail_wrong_usage() {\n    printf >&2 '%s\\n' \"$*\"\n    printf >&2 '%s\\n' \"USAGE: $0 <build root> [{run:<suite> | skip:<suite>}...]\"\n    exit 2\n}\n\nif (( $# < 1 )); then\n    fail_wrong_usage \"No build root passed.\"\nfi\n\nPT_BUILD_DIR=$(resolve_relative \"$1\" \"$PT_OPWD\")\n# Shift the build root\nshift\n\nPT_SOURCE_DIR=..\n\ncase \"$PT_TOOL\" in\n'')\n    PT_PREFIX=()\n    ;;\nvalgrind)\n    PT_PREFIX=( valgrind -q --exit-on-first-error=yes --error-exitcode=42 )\n    ;;\nhelgrind)\n    PT_PREFIX=( valgrind --tool=helgrind -q --exit-on-first-error=yes --error-exitcode=42 )\n    PT_PREFIX+=( --suppressions=\"$PT_BUILD_DIR/tests/glib.supp\" )\n    ;;\nkcov)\n    PT_PREFIX=( ./kcov_wrapper kcov \"${PT_KCOV_DIR?}\" )\n    ;;\n*)\n    echo >&2 \"$0: Unknown PT_TOOL: $PT_TOOL.\"\n    exit 2\n    ;;\nesac\n\nPT_LUASTATUS=( \"$PT_BUILD_DIR\"/luastatus/luastatus ${DEBUG:+-l trace} )\n\nPT_PARROT=$PT_BUILD_DIR/tests/parrot\nPT_HTTPSERV=$PT_BUILD_DIR/tests/httpserv/httpserv\n\nPT_WIDGET_FILES=()\nPT_FILES_TO_REMOVE=()\nPT_DIRS_TO_REMOVE=()\ndeclare -A PT_SPAWNED_THINGS=()\ndeclare -A PT_SPAWNED_THINGS_FDS_0=()\ndeclare -A PT_SPAWNED_THINGS_FDS_1=()\nPT_LINE=\n\nsource ./pt_stopwatch.lib.bash\nsource ./pt_dbus_daemon.lib.bash\nsource ./pt_pulseaudio_daemon.lib.bash\n\n# Sanity checks\nif ! [[ -x \"$PT_BUILD_DIR\"/luastatus/luastatus ]]; then\n    echo >&2 \"FATAL ERROR: '$PT_BUILD_DIR/luastatus/luastatus' is not an executable file.\"\n    echo >&2 \"Is '$PT_BUILD_DIR' the correct build root? Did you forget to build the project?\"\n    exit 1\nfi\nif ! [[ -x \"$PT_PARROT\" ]]; then\n    echo >&2 \"FATAL ERROR: '$PT_PARROT' is not an executable file.\"\n    echo >&2 \"Did you forget to pass '-DBUILD_TESTS=ON' to cmake?\"\n    exit 1\nfi\n\npt_stack_trace() {\n    echo >&2 \"Stack trace:\"\n    local n=${#FUNCNAME[@]}\n    local i=${1:-1}\n    for (( ; i < n; ++i )); do\n        local func=${FUNCNAME[$i]:-MAIN}\n        local lineno=${BASH_LINENO[(( i - 1 ))]}\n        local src=${BASH_SOURCE[$i]:-'???'}\n        echo >&2 \"  in $src:$lineno (function $func)\"\n    done\n}\n\npt_fail_internal_error() {\n    printf >&2 '%s\\n' '=== INTERNAL ERROR ===' \"$@\"\n    pt_stack_trace 2\n    exit 3\n}\n\npt_fail() {\n    printf >&2 '%s\\n' '=== FAILED ===' \"$@\"\n    pt_stack_trace 2\n    exit 1\n}\n\npt_check() {\n    \"$@\" || pt_fail \"pt_check($*) failed with code $?\"\n}\n\npt_write_widget_file() {\n    local f\n    f=$(mktemp --suffix=.lua) || pt_fail \"mktemp failed\"\n    PT_WIDGET_FILES+=(\"$f\")\n    PT_FILES_TO_REMOVE+=(\"$f\")\n    cat > \"$f\" || pt_fail \"cannot write widget file\"\n}\n\npt_add_file_to_remove() {\n    PT_FILES_TO_REMOVE+=(\"$1\")\n}\n\npt_add_dir_to_remove() {\n    PT_DIRS_TO_REMOVE+=(\"$1\")\n}\n\npt_add_dirs_to_remove_inorder() {\n    PT_DIRS_TO_REMOVE+=(\"$@\")\n}\n\npt_add_fifo() {\n    rm -f \"$1\" || true\n    mkfifo \"$1\" || pt_fail \"mkfifo failed\"\n    pt_add_file_to_remove \"$1\"\n}\n\npt_find_free_tcp_port() {\n    local port=12121\n    while true; do\n        echo >&2 \"[pt_find_free_tcp_port] Checking port $port...\"\n        if \"$PT_PARROT\" --reuseaddr --just-check TCP-SERVER \"$port\"; then\n            break\n        fi\n        echo >&2 \"[pt_find_free_tcp_port] Port $port does not seem to be free, incrementing.\"\n        port=$(( port + 1 ))\n        if (( port >= 65536 )); then\n            pt_fail \"[pt_find_free_tcp_port] Cannot find a free port.\"\n        fi\n    done\n    echo >&2 \"[pt_find_free_tcp_port] Chosen port $port.\"\n    PT_FOUND_FREE_PORT=$port\n}\n\npt_require_tools() {\n    local tool\n    for tool in \"$@\"; do\n        if ! command -v \"$tool\" >/dev/null; then\n            pt_fail \"pt_require_tools: tool '$tool' was not found.\"\n        fi\n    done\n}\n\npt_read_line() {\n    echo >&2 \"Reading line...\"\n    IFS= read -r PT_LINE || pt_fail \"pt_read_line: cannot read next line (process died?)\"\n}\n\npt_expect_line() {\n    echo >&2 \"Expecting line “$1”...\"\n    IFS= read -r PT_LINE || pt_fail \"expect_line: cannot read next line (process died?)\"\n    if [[ \"$PT_LINE\" != \"$1\" ]]; then\n        pt_fail \"pt_expect_line: line does not match\" \"Expected: '$1'\" \"Found: '$PT_LINE'\"\n    fi\n}\n\npt_spawn_thing() {\n    local k=$1\n    shift\n\n    local pid=${PT_SPAWNED_THINGS[$k]}\n    if [[ -n $pid ]]; then\n        pt_fail_internal_error \"pt_spawn_thing: thing '$k' has already been spawned (PID $pid).\"\n    fi\n\n    \"$@\" &\n    pid=$!\n    PT_SPAWNED_THINGS[$k]=$pid\n}\n\npt_spawn_thing_pipe() {\n    local k=$1\n    shift\n\n    local pid=${PT_SPAWNED_THINGS[$k]}\n    if [[ -n $pid ]]; then\n        pt_fail_internal_error \"pt_spawn_thing_pipe: thing '$k' has already been spawned (PID $pid).\"\n    fi\n\n    local fifo_in=./_internal-tmpfifo-$k-in\n    local fifo_out=./_internal-tmpfifo-$k-out\n    pt_add_fifo \"$fifo_in\"\n    pt_add_fifo \"$fifo_out\"\n\n    \"$@\" >\"$fifo_out\" <\"$fifo_in\" &\n    pid=$!\n    PT_SPAWNED_THINGS[$k]=$pid\n\n    exec {PT_SPAWNED_THINGS_FDS_0[$k]}<\"$fifo_out\"\n    exec {PT_SPAWNED_THINGS_FDS_1[$k]}>\"$fifo_in\"\n}\n\npt_has_spawned_thing() {\n    local k=$1\n    [[ -n \"${PT_SPAWNED_THINGS[$k]}\" ]]\n}\n\npt_close_fd() {\n    local fd=$(( $1 ))\n    exec {fd}>&-\n}\n\npt_close_thing_fds() {\n    local k=$1\n\n    local fd\n    for fd in \"${PT_SPAWNED_THINGS_FDS_0[$k]}\" \"${PT_SPAWNED_THINGS_FDS_1[$k]}\"; do\n        if [[ -n $fd ]]; then\n            pt_close_fd \"$fd\"\n        fi\n    done\n    unset PT_SPAWNED_THINGS_FDS_0[$k]\n    unset PT_SPAWNED_THINGS_FDS_1[$k]\n}\n\npt_wait_thing() {\n    local k=$1\n    local pid=${PT_SPAWNED_THINGS[$k]}\n    if [[ -z $pid ]]; then\n        pt_fail_internal_error \"pt_wait_thing: unknown thing '$k' (PT_SPAWNED_THINGS has no such key)\"\n    fi\n    echo >&2 \"Waiting for '$k' (PID $pid)...\"\n    local c=0\n    wait \"${PT_SPAWNED_THINGS[$k]}\" || c=$?\n    unset PT_SPAWNED_THINGS[$k]\n    pt_close_thing_fds \"$k\"\n    return -- \"$c\"\n}\n\npt_kill_thing() {\n    local k=$1\n    local pid=${PT_SPAWNED_THINGS[$k]}\n    if [[ -n $pid ]]; then\n        kill \"$pid\" || pt_fail \"Cannot kill '$k' (PID $pid).\"\n        wait \"$pid\" || true\n    fi\n    unset PT_SPAWNED_THINGS[$k]\n    pt_close_thing_fds \"$k\"\n}\n\npt_spawn_luastatus() {\n    pt_spawn_thing luastatus \\\n        \"${PT_PREFIX[@]}\" \"${PT_LUASTATUS[@]}\" -b ./barlib-mock.so \"$@\" \"${PT_WIDGET_FILES[@]}\"\n}\n\npt_spawn_luastatus_for_barlib_test_via_runner() {\n    local runner=$1\n    shift\n    pt_spawn_thing_pipe luastatus \\\n        \"$runner\" \"${PT_PREFIX[@]}\" \"${PT_LUASTATUS[@]}\" \"$@\" \"${PT_WIDGET_FILES[@]}\"\n}\n\npt_spawn_luastatus_directly() {\n    pt_spawn_thing luastatus \\\n        \"${PT_PREFIX[@]}\" \"${PT_LUASTATUS[@]}\" \"$@\" \"${PT_WIDGET_FILES[@]}\"\n}\n\npt_wait_luastatus() {\n    pt_wait_thing luastatus\n}\n\npt_kill_luastatus() {\n    pt_kill_thing luastatus\n}\n\npt_kill_everything() {\n    local k\n    for k in \"${!PT_SPAWNED_THINGS[@]}\"; do\n        pt_kill_thing \"$k\"\n    done\n}\n\npt_testcase_begin() {\n    true\n}\n\npt_testcase_end() {\n    pt_kill_luastatus\n    pt_kill_everything\n\n    local x\n    for x in \"${PT_FILES_TO_REMOVE[@]}\"; do\n        rm -f \"$x\" || true\n    done\n    for x in \"${PT_DIRS_TO_REMOVE[@]}\"; do\n        rmdir \"$x\" || true\n    done\n    PT_FILES_TO_REMOVE=()\n    PT_DIRS_TO_REMOVE=()\n    PT_WIDGET_FILES=()\n}\n\ntrap '\n    for k in \"${!PT_SPAWNED_THINGS[@]}\"; do\n        pid=${PT_SPAWNED_THINGS[$k]}\n        echo >&2 \"Killing “$k” (PID $pid).\"\n        kill \"$pid\" || true\n    done\n' EXIT\n\npt_run_test_case() {\n    echo >&2 \"==> Invoking file '$1'...\"\n    source \"$1\" || pt_fail \"'source' failed\"\n}\n\npt_run_test_suite() {\n    if ! [[ -d $1 ]]; then\n        pt_fail_internal_error \"'$1' is not a directory.\"\n    fi\n\n    if [[ -f \"$1/skip-me-if.lib.bash\" ]]; then\n        if source \"$1/skip-me-if.lib.bash\"; then\n            echo >&2 \"==> Skipping test suite '$1' (skip-me-if.lib.bash said so).\"\n            return\n        fi\n    fi\n\n    echo >&2 \"==> Listing files in '$1'...\"\n    local f\n    for f in \"$1\"/*; do\n        if [[ $f != *.lib.bash ]]; then\n            pt_fail_internal_error \"File '$f' does not have suffix '.lib.bash'.\"\n        fi\n        if [[ $f == */skip-me-if.lib.bash ]]; then\n            continue\n        fi\n        if [[ ${f##*/} != [0-9][0-9]-* ]]; then\n            pt_fail_internal_error \"File '$f' does not have prefix of two digits and a dash (e.g. '99-testcase.lib.bash').\"\n        fi\n        pt_run_test_case \"$f\"\n    done\n}\n\npt_cmake_opt_enabled() {\n    local sed_cmd='s/^'\n    sed_cmd+=$1\n    sed_cmd+=':BOOL=(.*)$/\\1/p'\n\n    local val\n    val=$(cmake -LA \"$PT_SOURCE_DIR\" | sed -rn \"$sed_cmd\") \\\n        || pt_fail \"Cannot extract value of CMake option '$1'\"\n\n    case \"$val\" in\n    1|ON|YES|TRUE|Y)\n        return 0\n        ;;\n    0|OFF|NO|FALSE|N|IGNORE|NOTFOUND|*-NOTFOUND)\n        return 1\n        ;;\n    esac\n    pt_fail \"Cannot make heads or tails of CMake boolean value '$val' (key $1)\"\n}\nPT_SKIP_ME_YES=0\nPT_SKIP_ME_NO=1\n\npt_setup_sanitizers() {\n    if pt_cmake_opt_enabled WITH_TSAN; then\n        export TSAN_OPTIONS=halt_on_error=1,atexit_sleep_ms=1\n    fi\n    if pt_cmake_opt_enabled WITH_ASAN; then\n        export ASAN_OPTIONS=halt_on_error=1,detect_leaks=1,detect_stack_use_after_return=1,strict_string_checks=1,detect_invalid_pointer_pairs=3\n    fi\n    if pt_cmake_opt_enabled WITH_LSAN; then\n        export LSAN_OPTIONS=halt_on_error=true\n    fi\n    if pt_cmake_opt_enabled WITH_UBSAN; then\n        export UBSAN_OPTIONS=halt_on_error=1\n    fi\n}\n\npt_main() {\n    local args_run=()\n    local -A args_skip=()\n\n    local arg\n    for arg in \"$@\"; do\n        if [[ $arg == */* ]]; then\n            fail_wrong_usage \"Invalid argument: '$arg': suite name can not contain a slash.\"\n        fi\n        if [[ $arg != *:* ]]; then\n            fail_wrong_usage \"Invalid argument: '$arg': is not of key:value form.\"\n        fi\n        local k=${arg%%:*}\n        local v=${arg#*:}\n        if [[ -z \"$v\" ]]; then\n            fail_wrong_usage \"Invalid argument: '$arg': value is empty.\"\n        fi\n        case \"$k\" in\n        run)\n            args_run+=(\"$v\")\n            ;;\n        skip)\n            args_skip[$v]=1\n            ;;\n        *)\n            fail_wrong_usage \"Invalid argument: '$arg': unknown key '$k'.\"\n            ;;\n        esac\n    done\n\n    if (( ${#args_run[@]} != 0 )); then\n        local x\n        for x in \"${args_run[@]}\"; do\n            local d=./pt_tests/$x\n            if [[ -n \"${args_skip[$x]}\" ]]; then\n                echo >&2 \"==> Skipping test suite '$d'.\"\n                continue\n            fi\n            pt_run_test_suite \"$d\"\n        done\n    else\n        local d\n        for d in ./pt_tests/*; do\n            if ! [[ -d $d ]]; then\n                continue\n            fi\n            local x=${d##*/}\n            if [[ -n \"${args_skip[$x]}\" ]]; then\n                echo >&2 \"==> Skipping test suite '$d'.\"\n                continue\n            fi\n            pt_run_test_suite \"$d\"\n        done\n    fi\n}\n\npt_setup_sanitizers\n\npt_main \"$@\"\n\necho >&2 \"=== PASSED ===\"\n"
  },
  {
    "path": "tests/pt_dbus_daemon.lib.bash",
    "content": "PT_DBUS_DAEMON_FIFO=./_tmpfifo_dbus_daemon_fd3\nPT_DBUS_DAEMON_ALREADY_RUNNING=-1\n\npt_dbus_daemon__wrapper() {\n    echo >&2 \"Spawning dbus-daemon as PID $$.\"\n    exec dbus-daemon \"$@\" --print-address=3 3>\"$PT_DBUS_DAEMON_FIFO\"\n}\n\npt_dbus_daemon__wait_until_works() {\n    local bus_arg=$1\n\n    local other_args=(\n        /org/luastatus/just/testing/whether/dbus/works\n        org.luastatus.ExampleInterface.ExampleMethod\n        int32:42\n        objpath:/org/luastatus/sample/object/name\n    )\n\n    pt_require_tools dbus-send\n\n    local i\n    for (( i = 0; i < 10; ++i )); do\n        if dbus-send \"$bus_arg\" \"${other_args[@]}\"; then\n            break\n        fi\n        sleep 1\n    done\n\n    pt_fail \"dbus ($bus_arg) does not work (waited for 10 seconds)\"\n}\n\npt_dbus_daemon_spawn() {\n    local bus_arg=$1\n    if [[ \"$bus_arg\" != --system && \"$bus_arg\" != --session ]]; then\n        pt_fail_internal_error \"pt_dbus_daemon_launch: expected either '--system' or '--session' as argument, found '$bus_arg'.\"\n    fi\n\n    if (( PT_DBUS_DAEMON_ALREADY_RUNNING < 0 )); then\n        if [[ -n \"$DBUS_SESSION_BUS_ADDRESS\" ]]; then\n            echo >&2 \"dbus-daemon seems to be already running: DBUS_SESSION_BUS_ADDRESS=$DBUS_SESSION_BUS_ADDRESS\"\n            PT_DBUS_DAEMON_ALREADY_RUNNING=1\n        else\n            PT_DBUS_DAEMON_ALREADY_RUNNING=0\n        fi\n    fi\n\n    if (( PT_DBUS_DAEMON_ALREADY_RUNNING )); then\n        return 0\n    fi\n\n    pt_require_tools dbus-daemon\n\n    pt_add_fifo \"$PT_DBUS_DAEMON_FIFO\"\n\n    echo >&2 \"Spawning dbus-daemon ${bus_arg}.\"\n    pt_spawn_thing __dbus_daemon__ pt_dbus_daemon__wrapper --nofork \"$bus_arg\"\n\n    local response\n    local rc=0\n    IFS= read -t 10 -r response < \"$PT_DBUS_DAEMON_FIFO\" || rc=$?\n    if (( rc != 0 )); then\n        pt_fail \"Cannot read dbus-daemon response within 10 seconds: read exit code $rc.\"\n    fi\n\n    export DBUS_SESSION_BUS_ADDRESS=$response\n\n    echo >&2 \"Read DBUS_SESSION_BUS_ADDRESS: $DBUS_SESSION_BUS_ADDRESS\"\n\n    pt_dbus_daemon__wait_until_works \"$bus_arg\"\n}\n\npt_dbus_daemon_kill() {\n    if (( PT_DBUS_DAEMON_ALREADY_RUNNING )); then\n        return 0\n    fi\n\n    echo >&2 \"Killing dbus-daemon.\"\n\n    pt_kill_thing __dbus_daemon__\n\n    export DBUS_SESSION_BUS_ADDRESS=\n}\n"
  },
  {
    "path": "tests/pt_pulseaudio_daemon.lib.bash",
    "content": "PT_PULSEAUDIO_DAEMON_ALREADY_RUNNING=-1\n\npt_pulseaudio_daemon__check() {\n    local n=$1\n\n    pt_require_tools pactl\n\n    echo >&2 \"Checking if PulseAudio daemon is available ($n time(s))...\"\n\n    local i\n    for (( i = 1; i <= n; ++i )); do\n        if pactl info; then\n            echo >&2 \"OK, PulseAudio daemon is available.\"\n            return 0\n        fi\n        if (( i != n )); then\n            sleep 1 || return $?\n        fi\n    done\n\n    echo >&2 \"PulseAudio daemon seems to be unavailable.\"\n    return 1\n}\n\npt_pulseaudio_daemon_spawn() {\n    if (( PT_PULSEAUDIO_DAEMON_ALREADY_RUNNING < 0 )); then\n        if pt_pulseaudio_daemon__check 1; then\n            echo >&2 \"PulseAudio daemon seems to be already running.\"\n            PT_PULSEAUDIO_DAEMON_ALREADY_RUNNING=1\n        else\n            PT_PULSEAUDIO_DAEMON_ALREADY_RUNNING=0\n        fi\n    fi\n\n    if (( PT_PULSEAUDIO_DAEMON_ALREADY_RUNNING )); then\n        return 0\n    fi\n\n    pt_require_tools pulseaudio\n\n    echo >&2 \"Spawning PulseAudio daemon.\"\n    pt_spawn_thing __pulseaudio_daemon__ pulseaudio --daemonize=no --disallow-exit=yes --exit-idle-time=-1\n\n    if ! pt_pulseaudio_daemon__check 10; then\n        pt_fail \"PulseAudio daemon is not available after 10 seconds.\"\n    fi\n}\n\npt_pulseaudio_daemon_kill() {\n    if (( PT_PULSEAUDIO_DAEMON_ALREADY_RUNNING )); then\n        return 0\n    fi\n\n    echo >&2 \"Killing PulseAudio daemon.\"\n\n    pt_kill_thing __pulseaudio_daemon__\n}\n"
  },
  {
    "path": "tests/pt_stopwatch.lib.bash",
    "content": "using_measure() {\n    pt_spawn_thing_pipe stopwatch \"$PT_BUILD_DIR\"/tests/stopwatch \"${PT_MAX_LAG:-75}\"\n}\n\nmeasure_start() {\n    if ! pt_has_spawned_thing stopwatch; then\n        pt_fail_internal_error \"measure_start: stopwatch was not spawned (did you forget 'using_measure'?).\"\n    fi\n    echo s >&${PT_SPAWNED_THINGS_FDS_1[stopwatch]}\n}\n\nmeasure_get_ms() {\n    echo q >&${PT_SPAWNED_THINGS_FDS_1[stopwatch]}\n    local ans\n    IFS= read -r ans <&${PT_SPAWNED_THINGS_FDS_0[stopwatch]} \\\n        || pt_fail \"Cannot read next line from stopwatch (the process died?).\"\n    printf '%s\\n' \"$ans\"\n}\n\nmeasure_check_ms() {\n    echo \"c $1\" >&${PT_SPAWNED_THINGS_FDS_1[stopwatch]}\n    local ans\n    IFS= read -r ans <&${PT_SPAWNED_THINGS_FDS_0[stopwatch]} \\\n        || pt_fail \"Cannot read next line from stopwatch (the process died?).\"\n    if [[ $ans != 1* ]]; then\n        pt_fail \"measure_check_ms $1: stopwatch said: '$ans'.\"\n    fi\n}\n"
  },
  {
    "path": "tests/pt_tests/_misc/01-simul.lib.bash",
    "content": "# Regression test for https://github.com/shdown/luastatus/issues/63.\n\npt_testcase_begin\nfor (( i = 0; i < 8; ++i )); do\n    pt_write_widget_file <<__EOF__\nwidget = {\n    plugin = '$PT_BUILD_DIR/plugins/mpd/plugin-mpd.so',\n    opts = {\n        port = 0,\n        retry_in = -1,\n    },\n    cb = function(t) end,\n}\n__EOF__\ndone\npt_spawn_luastatus -e\npt_wait_luastatus || pt_fail \"luastatus exited with non-zero code $?\"\npt_testcase_end\n"
  },
  {
    "path": "tests/pt_tests/add_test.sh",
    "content": "#!/usr/bin/env bash\n\nset -e\nset -o pipefail\nshopt -s nullglob\n\ndest_dir=${1?}\nnew_number=${2?}\nnew_name=${3?}\n\ncd -- \"$dest_dir\"\n\nprev_testcases_raw=$(printf '%s\\n' [0-9][0-9]-*.lib.bash | LC_ALL=C sort -r)\nprev_testcases=( $prev_testcases_raw )\n\necho >&2 \"Current number of testcases: ${#prev_testcases[@]}\"\n\nif (( ${#prev_testcases[@]} >= 100 )); then\n    echo >&2 \"ERROR: too many testcases.\"\n    exit 1\nfi\n\nNN_to_normal() {\n    echo \"${1#0}\"\n}\n\nnormal_to_NN() {\n    printf '%02d\\n' \"$1\"\n}\n\nif [[ $new_number == ?? ]]; then\n    j_new=$(NN_to_normal \"$new_number\")\nelse\n    j_new=$new_number\nfi\n\nfor f in \"${prev_testcases[@]}\"; do\n    NN=${f%%-*}\n    j=$(NN_to_normal \"$NN\")\n    if (( j >= j_new )); then\n        f_suffix=${f#??-}\n        NN_next=$(normal_to_NN \"$(( j + 1 ))\")\n        mv -v -- \"$f\" \"${NN_next}-${f_suffix}\"\n    fi\ndone\n\nnew_NN=$(normal_to_NN \"$j_new\")\n\nbad=$(printf '%s\\n' \"$new_NN\"-*.lib.bash)\nif [[ -n \"$bad\" ]]; then\n    echo >&2 \"ERROR: there are still files with given number after the 'moving' step:\"\n    echo >&2 \"~~~\"\n    printf >&2 '%s\\n' \"$bad\"\n    echo >&2 \"~~~\"\n    exit 1\nfi\n\ntouch -- \"${new_NN}-${new_name}.lib.bash\"\n\necho OK\n"
  },
  {
    "path": "tests/pt_tests/barlib-i3/00-common.lib.bash",
    "content": "main_fifo_file=./tmp-fifo-main\n\nx_spawn_luastatus() {\n    pt_spawn_luastatus_for_barlib_test_via_runner \\\n        \"$PT_SOURCE_DIR\"/tests/barlib-runners/runner-redirect-34 \\\n        -b \"$PT_BUILD_DIR\"/barlibs/i3/barlib-i3.so -B in_fd=3 -B out_fd=4 \\\n        \"$@\"\n}\n"
  },
  {
    "path": "tests/pt_tests/barlib-i3/01-output.lib.bash",
    "content": "pt_require_tools jq\n\nx_jq_read_file_option=\nx_figure_out_jq_version() {\n    x_jq_read_file_option=argfile\n    local out\n    if ! out=$(jq --slurpfile foo /dev/null -n '123'); then\n        return 0\n    fi\n    if [[ \"$out\" != '123' ]]; then\n        return 0\n    fi\n    x_jq_read_file_option=slurpfile\n}\nx_figure_out_jq_version\necho >&2 \"Figured out jq option for reading a file into a variable: --$x_jq_read_file_option\"\n\nx_assert_json_eq() {\n    echo >&2 \"Comparing JSONs:\nExpected: $1\nActual:   $2\"\n\n    local out\n    if ! out=$(jq --$x_jq_read_file_option a <(printf '%s\\n' \"$1\") --$x_jq_read_file_option b <(printf '%s\\n' \"$2\") -n '$a == $b'); then\n        pt_fail \"jq failed\"\n    fi\n    case \"$out\" in\n    true)\n        ;;\n    false)\n        pt_fail 'x_assert_json_eq: JSON does not match.' \"Expected: $1\" \"Found: $2\"\n        ;;\n    *)\n        pt_fail \"Unexpected output of jq: '$out'.\"\n        ;;\n    esac\n}\n\nx_testcase_output() {\n    local lua_expr=$1\n    local expect_json=$2\n\n    pt_testcase_begin\n    pt_write_widget_file <<__EOF__\nlocal _my_flag = false\nwidget = {\n    plugin = '$PT_BUILD_DIR/tests/plugin-mock.so',\n    opts = {make_calls = 2},\n    cb = function()\n        if _my_flag then\n            error('Boom')\n        end\n        _my_flag = true\n        return ($lua_expr)\n    end,\n}\n__EOF__\n\n    local opts=()\n    if (( O_NO_SEPARATORS )); then\n        opts+=(-B no_separators)\n    fi\n    if (( O_NO_CLICK_EVENTS )); then\n        opts+=(-B no_click_events)\n    fi\n    if (( O_ALLOW_STOPPING )); then\n        opts+=(-B allow_stopping)\n    fi\n\n    local first_line='{\"version\":1'\n    if (( O_NO_CLICK_EVENTS )); then\n        first_line+=',\"click_events\":false'\n    else\n        first_line+=',\"click_events\":true'\n    fi\n    if (( ! O_ALLOW_STOPPING )); then\n        first_line+=',\"stop_signal\":0,\"cont_signal\":0'\n    fi\n    first_line+='}'\n\n    local last_line='[{\"full_text\":\"(Error)\",\"color\":\"#ff0000\",\"background\":\"#000000\"'\n    if (( O_NO_SEPARATORS )); then\n        last_line+=',\"separator\":false'\n    fi\n    last_line+='}],'\n\n    x_spawn_luastatus \"${opts[@]}\"\n    pt_expect_line \"$first_line\" <&${PT_SPAWNED_THINGS_FDS_0[luastatus]}\n    pt_expect_line '[' <&${PT_SPAWNED_THINGS_FDS_0[luastatus]}\n    if [[ -n \"$expect_json\" ]]; then\n        pt_read_line <&${PT_SPAWNED_THINGS_FDS_0[luastatus]}\n        if [[ $PT_LINE != *, ]]; then\n            pt_fail \"Line does not end with ','.\" \"Line: '$PT_LINE'.\"\n        fi\n        x_assert_json_eq \"$expect_json\" \"${PT_LINE%,}\"\n    fi\n    pt_expect_line \"$last_line\" <&${PT_SPAWNED_THINGS_FDS_0[luastatus]}\n    pt_testcase_end\n}\n\nO_NO_SEPARATORS=0\nO_NO_CLICK_EVENTS=0\nO_ALLOW_STOPPING=0\n\nx_testcase_output 'nil' ''\nx_testcase_output '{}' ''\nx_testcase_output '{nil}' ''\nx_testcase_output '{{}}' '[{\"name\":\"0\"}]'\nx_testcase_output '{{},nil,{}}' '[{\"name\":\"0\"},{\"name\":\"0\"}]'\nx_testcase_output '{full_text = \"Hello, world\"}' '[{\"name\":\"0\",\"full_text\":\"Hello, world\"}]'\nx_testcase_output '{{full_text = \"Hello, world\"}}' '[{\"name\":\"0\",\"full_text\":\"Hello, world\"}]'\nx_testcase_output '{{fpval = 1234.5}}' '[{\"name\":\"0\",\"fpval\":1234.5}]'\nx_testcase_output '{{intval = -12345}}' '[{\"name\":\"0\",\"intval\":-12345}]'\nx_testcase_output '{{name = \"abc\"}}' '[{\"name\":\"0\"}]'\nx_testcase_output '{{name = 123}}' '[{\"name\":\"0\"}]'\nx_testcase_output '{{separator = true}}' '[{\"name\":\"0\",\"separator\":true}]'\nx_testcase_output '{{separator = false}}' '[{\"name\":\"0\",\"separator\":false}]'\nx_testcase_output '{{foo = true, bar = false}}' '[{\"name\":\"0\",\"foo\":true,\"bar\":false}]'\n\nO_NO_SEPARATORS=1 x_testcase_output '{{}}' '[{\"name\":\"0\",\"separator\":false}]'\nO_NO_SEPARATORS=1 x_testcase_output '{{separator = true}}' '[{\"name\":\"0\",\"separator\":true}]'\nO_NO_SEPARATORS=1 x_testcase_output '{{separator = false}}' '[{\"name\":\"0\",\"separator\":false}]'\n\nO_NO_CLICK_EVENTS=1 x_testcase_output 'nil' ''\n\nO_ALLOW_STOPPING=1 x_testcase_output 'nil' ''\n"
  },
  {
    "path": "tests/pt_tests/barlib-i3/02-input.lib.bash",
    "content": "x_testcase_input() {\n    pt_testcase_begin\n    pt_add_fifo \"$main_fifo_file\"\n    pt_write_widget_file <<__EOF__\nf = assert(io.open('$main_fifo_file', 'w'))\nf:setvbuf('line')\nlocal function _fmt_x(x)\n    local function _validate_str(s)\n        assert(s:find(\"\\n\") == nil, \"string contains a line break\")\n        assert(s:find(\"\\\"\") == nil, \"string contains a double quote sign\")\n    end\n    local function _fmt_num(n)\n        if n == math.floor(n) then\n            return string.format(\"%d\", n)\n        else\n            return string.format(\"%.4f\", n)\n        end\n    end\n    local t = type(x)\n    if t == \"table\" then\n        local tk = type(next(x))\n        if tk == \"nil\" then\n            return \"{}\"\n        elseif tk == \"number\" then\n            local s = {}\n            for _, v in ipairs(x) do\n                s[#s + 1] = _fmt_x(v)\n            end\n            return string.format(\"{%s}\", table.concat(s, \",\"))\n        elseif tk == \"string\" then\n            local ks = {}\n            for k, _ in pairs(x) do\n                assert(type(k) == \"string\", \"table has mixed-type keys\")\n                _validate_str(k)\n                ks[#ks + 1] = k\n            end\n            table.sort(ks)\n            local s = {}\n            for _, k in ipairs(ks) do\n                local v = x[k]\n                s[#s + 1] = string.format(\"[\\\"%s\\\"]=%s\", k, _fmt_x(v))\n            end\n            return string.format(\"{%s}\", table.concat(s, \",\"))\n        else\n            error(\"table key has unexpected type: \" .. tk)\n        end\n    elseif t == \"string\" then\n        _validate_str(x)\n        return string.format(\"\\\"%s\\\"\", x)\n    elseif t == \"number\" then\n        return _fmt_num(x)\n    else\n        return tostring(x)\n    end\nend\nwidget = {\n    plugin = '$PT_BUILD_DIR/tests/plugin-mock.so',\n    opts = {make_calls = 0},\n    cb = function()\n    end,\n    event = function(t)\n        f:write('event ' .. _fmt_x(t) .. '\\n')\n    end,\n}\n__EOF__\n    x_spawn_luastatus\n    exec {pfd}<\"$main_fifo_file\"\n    pt_expect_line '{\"version\":1,\"click_events\":true,\"stop_signal\":0,\"cont_signal\":0}' <&${PT_SPAWNED_THINGS_FDS_0[luastatus]}\n    pt_expect_line '[' <&${PT_SPAWNED_THINGS_FDS_0[luastatus]}\n    printf '[\\n%s\\n' \"$1\" >&${PT_SPAWNED_THINGS_FDS_1[luastatus]}\n    pt_expect_line \"event $2\" <&$pfd\n    pt_close_fd \"$pfd\"\n    pt_testcase_end\n}\n\nx_testcase_input '{\"name\":\"0\",\"foo\":\"bar\"}' '{[\"foo\"]=\"bar\",[\"name\"]=\"0\"}'\nx_testcase_input '{\"name\":\"1\",\"what\":\"invalid widget index\"},\n{\"name\":\"1234567\",\"what\":\"ivalid widget index\"},\n{\"name\":\"-0\",\"what\":\"widget index is minus zero\"},\n{\"name\":\"-1\",\"what\":\"negative widget index\"},\n{\"name\":\"0\",\"finally ok\":\"yes\"}' '{[\"finally ok\"]=\"yes\",[\"name\"]=\"0\"}'\nx_testcase_input '{\"name\":\"0\",\"foo\":[1,2,3,{\"k\":\"v\"},4]}' '{[\"foo\"]={1,2,3,{[\"k\"]=\"v\"},4},[\"name\"]=\"0\"}'\nx_testcase_input '{\"name\":\"0\",\"foo\":null,\"bar\":true,\"baz\":false}' '{[\"bar\"]=true,[\"baz\"]=false,[\"name\"]=\"0\"}'\n"
  },
  {
    "path": "tests/pt_tests/barlib-i3/03-lfunc-pango-escape.lib.bash",
    "content": "pt_testcase_begin\npt_add_fifo \"$main_fifo_file\"\npt_write_widget_file <<__EOF__\nf = assert(io.open('$main_fifo_file', 'w'))\nf:setvbuf('line')\nlocal function check(arg, expected)\n    local result = luastatus.barlib.pango_escape(arg)\n    if result ~= expected then\n        print('~~~~~~~~~~~~~')\n        print('Argument: ' .. arg)\n        print('Expected: ' .. expected)\n        print('Found:    ' .. result)\n        print('~~~~~~~~~~~~~')\n        error('check failed')\n    end\nend\nwidget = {\n    plugin = '$PT_BUILD_DIR/tests/plugin-mock.so',\n    opts = {make_calls = 1},\n    cb = function()\n        check('', '')\n        check('hello', 'hello')\n        check('hello & world', 'hello &amp; world')\n        check('<>', '&lt;&gt;')\n\n        f:write('ok\\n')\n    end,\n}\n__EOF__\nx_spawn_luastatus\nexec {pfd}<\"$main_fifo_file\"\npt_expect_line 'ok' <&$pfd\npt_close_fd \"$pfd\"\npt_testcase_end\n"
  },
  {
    "path": "tests/pt_tests/barlib-lemonbar/00-common.lib.bash",
    "content": "main_fifo_file=./tmp-fifo-main\n\nx_spawn_luastatus() {\n    pt_spawn_luastatus_for_barlib_test_via_runner \\\n        \"$PT_SOURCE_DIR\"/tests/barlib-runners/runner-redirect-34 \\\n        -b \"$PT_BUILD_DIR\"/barlibs/lemonbar/barlib-lemonbar.so -B in_fd=3 -B out_fd=4 \\\n        \"$@\"\n}\n"
  },
  {
    "path": "tests/pt_tests/barlib-lemonbar/01-output.lib.bash",
    "content": "x_testcase_output() {\n    local lua_expr=$1\n    local expect_line=$2\n    local opts=()\n    if [[ -n $O_SEPARATOR ]]; then\n        opts+=(-B separator=\"$O_SEPARATOR\")\n    fi\n\n    pt_testcase_begin\n    pt_write_widget_file <<__EOF__\nlocal _my_flag = false\nwidget = {\n    plugin = '$PT_BUILD_DIR/tests/plugin-mock.so',\n    opts = {make_calls = 2},\n    cb = function()\n        if _my_flag then\n            error('Boom')\n        end\n        _my_flag = true\n        return ($lua_expr)\n    end,\n}\n__EOF__\n    x_spawn_luastatus \"${opts[@]}\"\n    if [[ -n \"$expect_line\" ]]; then\n        pt_expect_line \"$expect_line\" <&${PT_SPAWNED_THINGS_FDS_0[luastatus]}\n    fi\n    pt_expect_line '%{B#f00}%{F#fff}(Error)%{B-}%{F-}' <&${PT_SPAWNED_THINGS_FDS_0[luastatus]}\n    pt_testcase_end\n}\n\nO_SEPARATOR=\n\nx_testcase_output 'nil' ''\nx_testcase_output '\"\"' ''\nx_testcase_output '\"foo bar\"' 'foo bar'\nx_testcase_output '\"?%%\"' '?%%'\nx_testcase_output '\"abc%{A:mycommand:}def\"' 'abc%{A:0_mycommand:}def'\nx_testcase_output '\"abc%%{A:mycommand:}def\"' 'abc%%{A:mycommand:}def'\nx_testcase_output '{\"foo\", \"bar\", \"baz\", \"quiz\"}' 'foo | bar | baz | quiz'\nO_SEPARATOR='tum' x_testcase_output '{\"foo\", \"bar\", \"baz\", \"quiz\"}' 'footumbartumbaztumquiz'\nx_testcase_output '{}' ''\nx_testcase_output '{nil}' ''\nx_testcase_output '{\"\", \"\", nil, \"\"}' ''\n"
  },
  {
    "path": "tests/pt_tests/barlib-lemonbar/02-input.lib.bash",
    "content": "x_testcase_input() {\n    pt_testcase_begin\n    pt_add_fifo \"$main_fifo_file\"\n    pt_write_widget_file <<__EOF__\nf = assert(io.open('$main_fifo_file', 'w'))\nf:setvbuf('line')\nwidget = {\n    plugin = '$PT_BUILD_DIR/tests/plugin-mock.so',\n    opts = {make_calls = 0},\n    cb = function()\n    end,\n    event = function(t)\n        f:write('event ' .. t .. '\\n')\n    end,\n}\n__EOF__\n    x_spawn_luastatus\n    exec {pfd}<\"$main_fifo_file\"\n    printf '%s\\n' \"$1\" >&${PT_SPAWNED_THINGS_FDS_1[luastatus]}\n    pt_expect_line \"event $2\" <&$pfd\n    pt_close_fd \"$pfd\"\n    pt_testcase_end\n}\n\nx_testcase_input '0_foo bar' 'foo bar'\n\nx_testcase_input '0wtf_is_this\n1_no_such_widget\n1234567_no_such_widget\n0__some_thing' '_some_thing'\n\nx_testcase_input '-0_what\n0_okay' 'okay'\n\nx_testcase_input '0_' ''\n\nx_testcase_input '\n0_previous line is empty' 'previous line is empty'\n"
  },
  {
    "path": "tests/pt_tests/barlib-lemonbar/03-lfunc-pango-escape.lib.bash",
    "content": "pt_testcase_begin\npt_add_fifo \"$main_fifo_file\"\npt_write_widget_file <<__EOF__\nf = assert(io.open('$main_fifo_file', 'w'))\nf:setvbuf('line')\nlocal function check(arg, expected)\n    local result = luastatus.barlib.escape(arg)\n    if result ~= expected then\n        print('~~~~~~~~~~~~~')\n        print('Argument: ' .. arg)\n        print('Expected: ' .. expected)\n        print('Found:    ' .. result)\n        print('~~~~~~~~~~~~~')\n        error('check failed')\n    end\nend\nwidget = {\n    plugin = '$PT_BUILD_DIR/tests/plugin-mock.so',\n    opts = {make_calls = 1},\n    cb = function()\n        check('', '')\n        check('hello', 'hello')\n        check('hello % world', 'hello %% world')\n        check('%%foobar', '%%%%foobar')\n\n        f:write('ok\\n')\n    end,\n}\n__EOF__\nx_spawn_luastatus\nexec {pfd}<\"$main_fifo_file\"\npt_expect_line 'ok' <&$pfd\npt_close_fd \"$pfd\"\npt_testcase_end\n"
  },
  {
    "path": "tests/pt_tests/barlib-stdout/00-common.lib.bash",
    "content": "main_fifo_file=./tmp-fifo-main\nin_fifo_file=./tmp-fifo-main-ev-input\n\nx_spawn_luastatus() {\n    pt_spawn_luastatus_for_barlib_test_via_runner \\\n        \"$PT_SOURCE_DIR\"/tests/barlib-runners/runner-redirect-34 \\\n        -b \"$PT_BUILD_DIR\"/barlibs/stdout/barlib-stdout.so -B out_fd=4 \\\n        \"$@\"\n}\n"
  },
  {
    "path": "tests/pt_tests/barlib-stdout/01-output.lib.bash",
    "content": "x_testcase_output() {\n    local lua_expr=$1\n    local expect_line=$2\n    local expect_error_line='(Error)'\n    local opts=()\n    if [[ -n $O_SEPARATOR ]]; then\n        opts+=(-B separator=\"$O_SEPARATOR\")\n    fi\n    if [[ -n $O_ERROR ]]; then\n        expect_error_line=$O_ERROR\n        opts+=(-B error=\"$O_ERROR\")\n    fi\n\n    pt_testcase_begin\n    pt_write_widget_file <<__EOF__\nlocal _my_flag = false\nwidget = {\n    plugin = '$PT_BUILD_DIR/tests/plugin-mock.so',\n    opts = {make_calls = 2},\n    cb = function()\n        if _my_flag then\n            error('Boom')\n        end\n        _my_flag = true\n        return ($lua_expr)\n    end,\n}\n__EOF__\n    x_spawn_luastatus \"${opts[@]}\"\n    if [[ -n \"$expect_line\" ]]; then\n        pt_expect_line \"$expect_line\" <&${PT_SPAWNED_THINGS_FDS_0[luastatus]}\n    fi\n    pt_expect_line \"$expect_error_line\" <&${PT_SPAWNED_THINGS_FDS_0[luastatus]}\n    pt_testcase_end\n}\n\nO_SEPARATOR=\nO_ERROR=\n\nx_testcase_output 'nil' ''\nx_testcase_output '\"\"' ''\nx_testcase_output '\"foo bar\"' 'foo bar'\nx_testcase_output '\"foo\\nbar\"' 'foobar'\nx_testcase_output '\"?%%\"' '?%%'\nx_testcase_output '{\"foo\", \"bar\", \"baz\", \"quiz\"}' 'foo | bar | baz | quiz'\nO_SEPARATOR='tum' \\\n    x_testcase_output '{\"foo\", \"bar\", \"baz\", \"quiz\"}' 'footumbartumbaztumquiz'\nO_ERROR='dammit…' O_SEPARATOR='•' \\\n    x_testcase_output '{\"\", \"str\", \"\", \"\", \"str2\", \"\"}' 'str•str2'\nO_ERROR=':(' \\\n    x_testcase_output '\"\"' ''\nx_testcase_output '{}' ''\nx_testcase_output '{nil}' ''\nx_testcase_output '{\"\", \"\", nil, \"\"}' ''\n"
  },
  {
    "path": "tests/pt_tests/barlib-stdout/03-input-filename.lib.bash",
    "content": "x_testcase_input() {\n    pt_testcase_begin\n    pt_add_fifo \"$main_fifo_file\"\n    pt_add_fifo \"$in_fifo_file\"\n    pt_write_widget_file <<__EOF__\nf = assert(io.open('$main_fifo_file', 'w'))\nf:setvbuf('line')\nwidget = {\n    plugin = '$PT_BUILD_DIR/tests/plugin-mock.so',\n    opts = {make_calls = 0},\n    cb = function()\n    end,\n    event = function(t)\n        f:write('event ' .. t .. '\\n')\n    end,\n}\n__EOF__\n    x_spawn_luastatus -B in_filename=\"$in_fifo_file\"\n    exec {pfd}<\"$main_fifo_file\"\n    exec {pfd_in}>\"$in_fifo_file\"\n\n    local line\n    for line in \"$@\"; do\n        sleep 1\n        printf '%s\\n' \"$line\" >&${pfd_in}\n        pt_expect_line \"event $line\" <&$pfd\n    done\n\n    pt_close_fd \"$pfd\"\n    pt_testcase_end\n\n    pt_close_fd \"$pfd_in\"\n}\n\nx_testcase_input\n\nx_testcase_input foo\n\nx_testcase_input foo bar\n\nx_testcase_input foo bar baz\n"
  },
  {
    "path": "tests/pt_tests/barlib-stdout/04-input-fd.lib.bash",
    "content": "x_testcase_input_fd() {\n    pt_testcase_begin\n    pt_add_fifo \"$main_fifo_file\"\n    pt_write_widget_file <<__EOF__\nf = assert(io.open('$main_fifo_file', 'w'))\nf:setvbuf('line')\nwidget = {\n    plugin = '$PT_BUILD_DIR/tests/plugin-mock.so',\n    opts = {make_calls = 0},\n    cb = function()\n    end,\n    event = function(t)\n        f:write('event ' .. t .. '\\n')\n    end,\n}\n__EOF__\n    x_spawn_luastatus -B in_fd=3\n    exec {pfd}<\"$main_fifo_file\"\n\n    local line\n    for line in \"$@\"; do\n        sleep 1\n        printf '%s\\n' \"$line\" >&${PT_SPAWNED_THINGS_FDS_1[luastatus]}\n        pt_expect_line \"event $line\" <&$pfd\n    done\n\n    pt_close_fd \"$pfd\"\n    pt_testcase_end\n\n    pt_close_fd \"$pfd_in\"\n}\n\nx_testcase_input\n\nx_testcase_input foo\n\nx_testcase_input foo bar\n\nx_testcase_input foo bar baz\n"
  },
  {
    "path": "tests/pt_tests/luastatus/00-common.lib.bash",
    "content": "main_fifo_file=./tmp-main-fifo\n\nhang_timeout=${PT_HANG_TIMEOUT:-3}\n\nmock_barlib=\"$PT_BUILD_DIR\"/tests/barlib-mock.so\nmock_plugin=\"$PT_BUILD_DIR\"/tests/plugin-mock.so\n\nassert_exits_with_code() {\n    local expected_c=$1\n    shift\n    pt_spawn_luastatus_directly \"$@\"\n    local actual_c=0\n    pt_wait_luastatus || actual_c=$?\n    if (( expected_c != actual_c )); then\n        pt_fail \"Expected exit code $expected_c, found $actual_c.\"\n    fi\n}\n\nassert_succeeds() {\n    assert_exits_with_code 0 \"$@\"\n}\n\nassert_fails() {\n    assert_exits_with_code 1 \"$@\"\n}\n\nassert_hangs() {\n    pt_spawn_luastatus_directly \"$@\"\n    sleep \"$hang_timeout\"\n    pt_kill_thing luastatus\n}\n\nassert_works() {\n    assert_hangs \"$@\"\n    assert_succeeds -e \"$@\"\n}\n\ntestcase_assert_fails() {\n    pt_testcase_begin\n    assert_fails \"$@\"\n    pt_testcase_end\n}\n\ntestcase_assert_succeeds() {\n    pt_testcase_begin\n    assert_succeeds \"$@\"\n    pt_testcase_end\n}\n\ntestcase_assert_works() {\n    pt_testcase_begin\n    assert_works \"$@\"\n    pt_testcase_end\n}\n"
  },
  {
    "path": "tests/pt_tests/luastatus/01-misc.lib.bash",
    "content": "testcase_assert_fails\ntestcase_assert_fails /dev/null\ntestcase_assert_fails /dev/nil\ntestcase_assert_fails -e\ntestcase_assert_fails -TheseFlagsDoNotExist\ntestcase_assert_fails -l ''\ntestcase_assert_fails -l nosuchloglevel\ntestcase_assert_fails -l '•÷¢˝Q⅓'\ntestcase_assert_fails -l\ntestcase_assert_fails -l -l\ntestcase_assert_fails -ы\ntestcase_assert_fails -v\ntestcase_assert_fails -b\n\ntestcase_assert_fails -b '§n”™°£'\ntestcase_assert_fails -b '/'\ntestcase_assert_fails -b 'nosuchbarlibforsure'\n\ntestcase_assert_succeeds -b \"$mock_barlib\" -eeeeeeee -e\n\ntestcase_assert_works -b \"$mock_barlib\"\ntestcase_assert_works -b \"$mock_barlib\" .\ntestcase_assert_works -b \"$mock_barlib\" . . . . .\ntestcase_assert_works -b \"$mock_barlib\" /dev/null\ntestcase_assert_works -b \"$mock_barlib\" /dev/null /dev/null /dev/null\n\npt_testcase_begin\npt_write_widget_file <<__EOF__\nluastatus = nil\n__EOF__\nassert_works -b \"$mock_barlib\"\npt_testcase_end\n\npt_testcase_begin\npt_write_widget_file <<__EOF__\nwidget = setmetatable(\n    {\n        plugin = setmetatable({}, {\n            __tostring = function() error(\"hi there (__tostring)\") end,\n        }),\n        cb = function() end,\n    }, {\n        __index = function() error(\"hi there (__index)\") end,\n        __pairs = function() error(\"hi there (__pairs)\") end,\n    }\n)\n__EOF__\nassert_works -b \"$mock_barlib\"\npt_testcase_end\n\npt_testcase_begin\npt_write_widget_file <<__EOF__\nwidget = {\n    plugin = '$mock_plugin',\n    cb = function()\n    end,\n}\n__EOF__\nassert_works -b \"$mock_barlib\"\npt_testcase_end\n"
  },
  {
    "path": "tests/pt_tests/luastatus/02-getenv.lib.bash",
    "content": "pt_testcase_begin\npt_add_fifo \"$main_fifo_file\"\n\nunset_env_var=CzRbFEXexi\nwhile [[ -v $unset_env_var ]]; do\n    unset_env_var+='a'\ndone\n\npt_write_widget_file <<__EOF__\nf = assert(io.open('$main_fifo_file', 'w'))\nf:setvbuf('line')\nfunction dump(varname)\n    local r = os.getenv(varname)\n    if r then\n        f:write(string.format('%s: %s\\n', varname, r))\n    else\n        f:write(string.format('%s is not set\\n', varname))\n    end\nend\ndump('MY_ENV_VAR')\ndump('$unset_env_var')\n__EOF__\n\nMY_ENV_VAR='<>?foo  bar~~~' pt_spawn_luastatus_directly -b \"$mock_barlib\"\n\nexec {pfd}<\"$main_fifo_file\"\npt_expect_line 'MY_ENV_VAR: <>?foo  bar~~~' <&$pfd\npt_expect_line \"$unset_env_var is not set\" <&$pfd\npt_close_fd \"$pfd\"\n\npt_testcase_end\n"
  },
  {
    "path": "tests/pt_tests/luastatus/03-exit.lib.bash",
    "content": "pt_testcase_begin\npt_write_widget_file <<__EOF__\nos.exit(27)\n__EOF__\nassert_exits_with_code 27 -b \"$mock_barlib\"\npt_testcase_end\n\npt_testcase_begin\npt_write_widget_file <<__EOF__\nos.exit()\n__EOF__\nassert_exits_with_code 0 -b \"$mock_barlib\"\npt_testcase_end\n"
  },
  {
    "path": "tests/pt_tests/luastatus/04-setlocale.lib.bash",
    "content": "pt_testcase_begin\npt_write_widget_file <<__EOF__\nassert(os.setlocale() == nil)\nassert(os.setlocale('C') == nil)\nos.exit(0)\n__EOF__\nassert_exits_with_code 0 -b \"$mock_barlib\"\npt_testcase_end\n"
  },
  {
    "path": "tests/pt_tests/luastatus/05-libwidechar.lib.bash",
    "content": "pt_testcase_begin\npt_add_fifo \"$main_fifo_file\"\n\npt_write_widget_file <<__EOF__\nf = assert(io.open('$main_fifo_file', 'w'))\nf:setvbuf('line')\n\nfunction my_to_str(x)\n    if type(x) == 'number' then\n        return string.format('%d', x)\n    end\n    if type(x) == 'boolean' then\n        return x and 'TRUE' or 'FALSE'\n    end\n    if x == nil then\n        return 'nil'\n    end\n    return '<<' .. x .. '>>'\nend\n\nfunction dump(x)\n    f:write(my_to_str(x) .. '\\\\n')\nend\n\nfunction dump2(x, y)\n    f:write(string.format('%s %s\\\\n', my_to_str(x), my_to_str(y)))\nend\n\nWIDTH = luastatus.libwidechar.width\nTRUNC = luastatus.libwidechar.truncate_to_width\nMKVALID = luastatus.libwidechar.make_valid_and_printable\n\nSTR = \"Test\"\ndump(WIDTH(STR))\ndump2(TRUNC(STR, 3))\ndump(MKVALID(STR, \"?\"))\n\nSTR = \"$(printf \\\\xFF) Test\"\ndump(WIDTH(STR))\ndump2(TRUNC(STR, 3))\ndump(MKVALID(STR, \"?\"))\n\nSTR = \"ЮЩЛЫ\"\ndump(WIDTH(STR))\ndump2(TRUNC(STR, 3))\ndump(MKVALID(STR, \"?\"))\n\nSTR = \"t$(printf \\\\x01)est\"\ndump(WIDTH(STR))\ndump2(TRUNC(STR, 3))\ndump(MKVALID(STR, \"?\"))\n\nSTR = \"ABCDEFGHI\"\n\ndump(WIDTH(STR, 2))\ndump(WIDTH(STR, 2, 3))\n\ndump(TRUNC(STR, 1, 2))\ndump(TRUNC(STR, 1, 3, 4))\ndump(TRUNC(STR, 1, 3, 128))\ndump(TRUNC(STR, 100, 3, 8))\ndump(TRUNC(STR, 100, 3, 9))\ndump(TRUNC(STR, 100, 3, 10))\ndump(TRUNC(STR, 100, 3, 11))\n\ndump(MKVALID(STR, '?', 3))\ndump(MKVALID(STR, '?', 3, 5))\n\ndump(luastatus.libwidechar.is_dummy_implementation())\n\n__EOF__\n\npt_spawn_luastatus_directly -b \"$mock_barlib\"\n\nexec {pfd}<\"$main_fifo_file\"\n\npt_expect_line '4' <&$pfd\npt_expect_line '<<Tes>> 3' <&$pfd\npt_expect_line '<<Test>>' <&$pfd\n\npt_expect_line 'nil' <&$pfd\npt_expect_line 'nil nil' <&$pfd\npt_expect_line '<<? Test>>' <&$pfd\n\npt_expect_line '4' <&$pfd\npt_expect_line '<<ЮЩЛ>> 3' <&$pfd\npt_expect_line '<<ЮЩЛЫ>>' <&$pfd\n\npt_expect_line 'nil' <&$pfd\npt_expect_line 'nil nil' <&$pfd\npt_expect_line '<<t?est>>' <&$pfd\n\npt_expect_line '8' <&$pfd\npt_expect_line '2' <&$pfd\n\npt_expect_line '<<B>>' <&$pfd\npt_expect_line '<<C>>' <&$pfd\npt_expect_line '<<C>>' <&$pfd\npt_expect_line '<<CDEFGH>>' <&$pfd\npt_expect_line '<<CDEFGHI>>' <&$pfd\npt_expect_line '<<CDEFGHI>>' <&$pfd\npt_expect_line '<<CDEFGHI>>' <&$pfd\n\npt_expect_line '<<CDEFGHI>>' <&$pfd\npt_expect_line '<<CDE>>' <&$pfd\n\npt_expect_line 'FALSE' <&$pfd\n\npt_close_fd \"$pfd\"\n\npt_testcase_end\n"
  },
  {
    "path": "tests/pt_tests/luastatus/06-execute.lib.bash",
    "content": "pt_testcase_begin\npt_add_fifo \"$main_fifo_file\"\n\npt_write_widget_file <<__EOF__\nf = assert(io.open('$main_fifo_file', 'w'))\nf:setvbuf('line')\nif rawlen == nil then\n    -- Lua 5.1\n    assert(os.execute() ~= 0)\n    assert(type(os.execute('exit 42')) == 'number')\n    f:write('ok\\n')\nelse\n    -- Lua >=5.2\n    assert(os.execute() == true)\n    local is_ok, what, code\n\n    is_ok, what, code = os.execute('exit 42')\n    assert(is_ok == nil)\n    assert(what == 'exit')\n    assert(code == 42)\n\n    is_ok, what, code = os.execute('exit 0')\n    assert(is_ok == true)\n    assert(what == 'exit')\n    assert(code == 0)\n\n    is_ok, what, code = os.execute('kill -9 \\$\\$')\n    assert(is_ok == nil)\n    assert(what == 'signal')\n    assert(code == 9)\n\n    f:write('ok\\n')\nend\n__EOF__\n\npt_spawn_luastatus_directly -b \"$mock_barlib\"\n\nexec {pfd}<\"$main_fifo_file\"\npt_expect_line 'ok' <&$pfd\npt_close_fd \"$pfd\"\n\npt_testcase_end\n"
  },
  {
    "path": "tests/pt_tests/plugin-battery-linux/00-common.lib.bash",
    "content": "pt_require_tools mktemp\n\nmain_fifo_file=./tmp-fifo-main\n\nbattery_testcase() {\n    local expect_str=$1\n    local uevent_content=$2 # empty string here means remove the file\n\n    pt_testcase_begin\n    pt_add_fifo \"$main_fifo_file\"\n    local batdev_dir; batdev_dir=$(mktemp -d) || pt_fail \"'mktemp -d' failed\"\n    pt_add_dir_to_remove \"$batdev_dir\"\n    local uevent_file=$batdev_dir/uevent\n    if [[ -n \"$uevent_content\" ]]; then\n        printf '%s' \"$uevent_content\" > \"$uevent_file\" || pt_fail \"cannot write uevent_file\"\n    else\n        rm -f \"$uevent_file\"\n    fi\n    pt_add_file_to_remove \"$uevent_file\"\n    pt_write_widget_file <<__EOF__\nf = assert(io.open('$main_fifo_file', 'w'))\nf:setvbuf('line')\nf:write('init\\n')\nfunction my_event_func() end\nx = dofile('$PT_SOURCE_DIR/plugins/battery-linux/battery-linux.lua')\nwidget = x.widget{\n    _devpath = '$batdev_dir',\n    cb = function(t)\n        f:write('cb')\n        if t.status then\n            f:write(string.format(' status=\"%s\"', t.status))\n        end\n        if t.capacity then\n            f:write(string.format(' capacity=%.0f', t.capacity))\n        end\n        if t.consumption then\n            f:write(string.format(' consumption=%.1f', t.consumption))\n        end\n        if t.rem_time then\n            f:write(string.format(' rem_time=%.1f', t.rem_time))\n        end\n        f:write('\\n')\n    end,\n    event = my_event_func,\n}\nwidget.plugin = ('$PT_BUILD_DIR/plugins/{}/plugin-{}.so'):gsub('{}', widget.plugin)\nassert(widget.event == my_event_func)\n__EOF__\n    pt_spawn_luastatus\n    exec {pfd}<\"$main_fifo_file\"\n    pt_expect_line 'init' <&$pfd\n    pt_expect_line \"$expect_str\" <&$pfd\n    pt_close_fd \"$pfd\"\n    pt_testcase_end\n}\n"
  },
  {
    "path": "tests/pt_tests/plugin-battery-linux/01-full-status-full.lib.bash",
    "content": "battery_testcase 'cb status=\"Full\" capacity=100' \"\\\nPOWER_SUPPLY_NAME=BAT0 POWER_SUPPLY_TYPE=Battery\nPOWER_SUPPLY_STATUS=Full\nPOWER_SUPPLY_PRESENT=1\nPOWER_SUPPLY_TECHNOLOGY=Li-ion\nPOWER_SUPPLY_CYCLE_COUNT=0\nPOWER_SUPPLY_VOLTAGE_MIN_DESIGN=15200000\nPOWER_SUPPLY_VOLTAGE_NOW=17074000\nPOWER_SUPPLY_CURRENT_NOW=0\nPOWER_SUPPLY_CHARGE_FULL_DESIGN=3030000\nPOWER_SUPPLY_CHARGE_FULL=3289000\nPOWER_SUPPLY_CHARGE_NOW=3285000\nPOWER_SUPPLY_CAPACITY=99\nPOWER_SUPPLY_CAPACITY_LEVEL=Normal\nPOWER_SUPPLY_MODEL_NAME=R14B01W\nPOWER_SUPPLY_MANUFACTURER=Coslight\nPOWER_SUPPLY_SERIAL_NUMBER=33468\n\"\n"
  },
  {
    "path": "tests/pt_tests/plugin-battery-linux/02-full-status-not-charging.lib.bash",
    "content": "battery_testcase 'cb status=\"Not charging\" capacity=100' \"\\\nPOWER_SUPPLY_NAME=BAT0\nPOWER_SUPPLY_TYPE=Battery\nPOWER_SUPPLY_STATUS=Not charging\nPOWER_SUPPLY_PRESENT=1\nPOWER_SUPPLY_TECHNOLOGY=Li-ion\nPOWER_SUPPLY_CYCLE_COUNT=0\nPOWER_SUPPLY_VOLTAGE_MIN_DESIGN=15200000\nPOWER_SUPPLY_VOLTAGE_NOW=17074000\nPOWER_SUPPLY_CURRENT_NOW=0\nPOWER_SUPPLY_CHARGE_FULL_DESIGN=3030000\nPOWER_SUPPLY_CHARGE_FULL=3289000\nPOWER_SUPPLY_CHARGE_NOW=3285000\nPOWER_SUPPLY_CAPACITY=99\nPOWER_SUPPLY_CAPACITY_LEVEL=Normal\nPOWER_SUPPLY_MODEL_NAME=R14B01W\nPOWER_SUPPLY_MANUFACTURER=Coslight\nPOWER_SUPPLY_SERIAL_NUMBER=33468\n\"\n"
  },
  {
    "path": "tests/pt_tests/plugin-battery-linux/03-dischaging.lib.bash",
    "content": "battery_testcase 'cb status=\"Discharging\" capacity=99 consumption=18.6 rem_time=2.9' \"\\\nPOWER_SUPPLY_NAME=BAT0\nPOWER_SUPPLY_TYPE=Battery\nPOWER_SUPPLY_STATUS=Discharging\nPOWER_SUPPLY_PRESENT=1\nPOWER_SUPPLY_TECHNOLOGY=Li-ion\nPOWER_SUPPLY_CYCLE_COUNT=0\nPOWER_SUPPLY_VOLTAGE_MIN_DESIGN=15200000\nPOWER_SUPPLY_VOLTAGE_NOW=16806000\nPOWER_SUPPLY_CURRENT_NOW=1107000\nPOWER_SUPPLY_CHARGE_FULL_DESIGN=3030000\nPOWER_SUPPLY_CHARGE_FULL=3285000\nPOWER_SUPPLY_CHARGE_NOW=3247000\nPOWER_SUPPLY_CAPACITY=98\nPOWER_SUPPLY_CAPACITY_LEVEL=Normal\nPOWER_SUPPLY_MODEL_NAME=R14B01W\nPOWER_SUPPLY_MANUFACTURER=Coslight\nPOWER_SUPPLY_SERIAL_NUMBER=33468\n\"\n"
  },
  {
    "path": "tests/pt_tests/plugin-battery-linux/04-charging.lib.bash",
    "content": "battery_testcase 'cb status=\"Charging\" capacity=94 consumption=12.7 rem_time=0.3' \"\\\nPOWER_SUPPLY_NAME=BAT0\nPOWER_SUPPLY_TYPE=Battery\nPOWER_SUPPLY_STATUS=Charging\nPOWER_SUPPLY_PRESENT=1\nPOWER_SUPPLY_TECHNOLOGY=Li-ion\nPOWER_SUPPLY_CYCLE_COUNT=0\nPOWER_SUPPLY_VOLTAGE_MIN_DESIGN=15200000\nPOWER_SUPPLY_VOLTAGE_NOW=17131000\nPOWER_SUPPLY_CURRENT_NOW=743000\nPOWER_SUPPLY_CHARGE_FULL_DESIGN=3030000\nPOWER_SUPPLY_CHARGE_FULL=3298000\nPOWER_SUPPLY_CHARGE_NOW=3096000\nPOWER_SUPPLY_CAPACITY=93\nPOWER_SUPPLY_CAPACITY_LEVEL=Normal\nPOWER_SUPPLY_MODEL_NAME=R14B01W\nPOWER_SUPPLY_MANUFACTURER=Coslight\nPOWER_SUPPLY_SERIAL_NUMBER=33468\n\"\n"
  },
  {
    "path": "tests/pt_tests/plugin-battery-linux/05-nofile.lib.bash",
    "content": "battery_testcase 'cb' ''\n"
  },
  {
    "path": "tests/pt_tests/plugin-cpu-freq-linux/00-common.lib.bash",
    "content": "pt_require_tools mktemp\n\nmain_fifo_file=./tmp-fifo-main\n\ncpu_dir=\nx_testcase_begin() {\n    cpu_dir=$(mktemp -d) || pt_fail 'mktemp -d failed'\n}\n\nx_add_or_set_cpu() {\n    local i=${1?}; shift\n    local max=${1?}; shift\n    local min=${1?}; shift\n    local cur=${1?}; shift\n\n    if ! [[ -d \"$cpu_dir\"/cpu\"$i\" ]]; then\n\n        mkdir \"$cpu_dir\"/cpu\"$i\" || return $?\n        mkdir \"$cpu_dir\"/cpu\"$i\"/cpufreq || return $?\n\n        pt_add_dirs_to_remove_inorder \\\n            \"$cpu_dir\"/cpu\"$i\"/cpufreq \\\n            \"$cpu_dir\"/cpu\"$i\" \\\n            || return $?\n\n        pt_add_file_to_remove \"$cpu_dir\"/cpu\"$i\"/cpufreq/cpuinfo_max_freq || return $?\n        pt_add_file_to_remove \"$cpu_dir\"/cpu\"$i\"/cpufreq/cpuinfo_min_freq || return $?\n        pt_add_file_to_remove \"$cpu_dir\"/cpu\"$i\"/cpufreq/scaling_cur_freq || return $?\n    fi\n\n    echo \"$max\" > \"$cpu_dir\"/cpu\"$i\"/cpufreq/cpuinfo_max_freq || return $?\n    echo \"$min\" > \"$cpu_dir\"/cpu\"$i\"/cpufreq/cpuinfo_min_freq || return $?\n    echo \"$cur\" > \"$cpu_dir\"/cpu\"$i\"/cpufreq/scaling_cur_freq || return $?\n}\n\nx_testcase() {\n    local reload_each_time=${1?}; shift\n    local callback=${1?}; shift\n\n    local reload_code=\n    if (( reload_each_time )); then\n        reload_code='data.please_reload = true;'\n    fi\n\n    pt_testcase_begin\n    pt_add_fifo \"$main_fifo_file\"\n\n    pt_write_widget_file <<__EOF__\nf = assert(io.open('$main_fifo_file', 'w'))\nf:setvbuf('line')\nf:write('init\\n')\nfunction my_event_func() end\nx = dofile('$PT_SOURCE_DIR/plugins/cpu-freq-linux/cpu-freq-linux.lua')\n\nfunction format_result_entry(x)\n    return string.format('max=%d,min=%d,cur=%d', x.max, x.min, x.cur)\nend\n\nfunction format_result(arr)\n    local chunks = {}\n    for _, x in ipairs(arr) do\n        table.insert(chunks, format_result_entry(x))\n    end\n    return table.concat(chunks, ' ')\nend\n\ndata = {_cpu_dir = '$cpu_dir'}\nwidget = x.widget({\n    timer_opts = {\n        period = 0.1,\n    },\n    cb = function(t)\n        $reload_code\n        if type(t) == 'table' then\n            f:write('cb ' .. format_result(t) .. '\\n')\n        else\n            assert(t == nil)\n            f:write('cb nil\\n')\n        end\n    end,\n    event = my_event_func,\n}, data)\nwidget.plugin = ('$PT_BUILD_DIR/plugins/{}/plugin-{}.so'):gsub('{}', widget.plugin)\nassert(widget.event == my_event_func)\n__EOF__\n    pt_spawn_luastatus\n    exec {pfd}<\"$main_fifo_file\"\n    pt_expect_line 'init' <&$pfd\n\n    local i\n    for (( i = 0; i < $#; ++i )); do\n        pt_check $callback $i\n        local j=$(( i + 1 ))\n        pt_expect_line \"${!j}\" <&$pfd\n    done\n\n    pt_close_fd \"$pfd\"\n    pt_testcase_end\n}\n\nx_testcase_end() {\n    pt_add_dir_to_remove \"$cpu_dir\"\n    cpu_dir=\n}\n"
  },
  {
    "path": "tests/pt_tests/plugin-cpu-freq-linux/01-simple.lib.bash",
    "content": "x_testcase_begin\n\nx_add_or_set_cpu 0 1000000 500000 750000\n\nx_my_callback() {\n    case \"$1\" in\n    0)\n        true\n        ;;\n    1)\n        pt_check x_add_or_set_cpu 0 20 10 30\n        ;;\n    2)\n        pt_check x_add_or_set_cpu 0 9000000 3000000 2000000\n        ;;\n    3)\n        pt_check x_add_or_set_cpu 0 20 10 15\n        pt_check x_add_or_set_cpu 1 50 30 40\n        ;;\n    esac\n}\n\nx_testcase \\\n    0 \\\n    x_my_callback \\\n    'cb max=1000000,min=500000,cur=750000' \\\n    'cb max=1000000,min=500000,cur=500000' \\\n    'cb max=1000000,min=500000,cur=1000000' \\\n    'cb max=1000000,min=500000,cur=500000'\n\nx_testcase_end\n"
  },
  {
    "path": "tests/pt_tests/plugin-cpu-freq-linux/02-please-reload.lib.bash",
    "content": "x_testcase_begin\n\nx_add_or_set_cpu 0 1000000 500000 750000\n\nx_my_callback() {\n    case \"$1\" in\n    0)\n        true\n        ;;\n    1)\n        pt_check x_add_or_set_cpu 0 20 10 30\n        ;;\n    2)\n        pt_check x_add_or_set_cpu 0 9000000 3000000 2000000\n        ;;\n    3)\n        pt_check x_add_or_set_cpu 0 20 10 15\n        pt_check x_add_or_set_cpu 1 50 30 40\n        ;;\n    esac\n}\n\nx_testcase \\\n    1 \\\n    x_my_callback \\\n    'cb max=1000000,min=500000,cur=750000' \\\n    'cb max=20,min=10,cur=20' \\\n    'cb max=9000000,min=3000000,cur=3000000' \\\n    'cb max=20,min=10,cur=15 max=50,min=30,cur=40'\n\nx_testcase_end\n"
  },
  {
    "path": "tests/pt_tests/plugin-cpu-usage-linux/00-common.lib.bash",
    "content": "pt_require_tools mktemp\n\nmain_fifo_file=./tmp-fifo-main\n\nstat_content_1=\"\\\ncpu  32855780 19344 19217634 468500203 300653 0 273158 0 0 0\ncpu0 8358858 4763 5675358 116643347 61382 0 4672 0 0 0\ncpu1 8301511 4836 5853008 116508899 59664 0 25225 0 0 0\ncpu2 7952134 4371 3472358 117560453 113665 0 221818 0 0 0\ncpu3 8243275 5374 4216907 117787501 65941 0 21442 0 0 0\n\"\n\nstat_content_2=\"\\\ncpu  32855819 19344 19217652 468500542 300653 0 273158 0 0 0\ncpu0 8358868 4763 5675362 116643433 61382 0 4672 0 0 0\ncpu1 8301519 4836 5853012 116508986 59664 0 25225 0 0 0\ncpu2 7952146 4371 3472364 117560535 113665 0 221818 0 0 0\ncpu3 8243285 5374 4216913 117787586 65941 0 21442 0 0 0\n\"\n\ncpu_usage_testcase() {\n    local cpu_option=$1\n    local expect_str=$2\n\n    pt_testcase_begin\n    pt_add_fifo \"$main_fifo_file\"\n\n    local proc_dir; proc_dir=$(mktemp -d) || pt_fail \"'mktemp -d' failed\"\n    pt_add_dir_to_remove \"$proc_dir\"\n    local stat_file=$proc_dir/stat\n    printf '%s' \"$stat_content_1\" > \"$stat_file\" || pt_fail 'cannot write stat_file'\n    pt_add_file_to_remove \"$stat_file\"\n    pt_write_widget_file <<__EOF__\nf = assert(io.open('$main_fifo_file', 'w'))\nf:setvbuf('line')\nf:write('init\\n')\nfunction my_event_func() end\nx = dofile('$PT_SOURCE_DIR/plugins/cpu-usage-linux/cpu-usage-linux.lua')\nwidget = x.widget{\n    _procpath = '$proc_dir',\n    cpu = $cpu_option,\n    cb = function(t)\n        if t then\n            f:write(string.format('cb %.1f\\n', t))\n        else\n            f:write('cb nil\\n')\n        end\n    end,\n    event = my_event_func,\n}\nwidget.plugin = ('$PT_BUILD_DIR/plugins/{}/plugin-{}.so'):gsub('{}', widget.plugin)\nassert(widget.event == my_event_func)\n__EOF__\n    pt_spawn_luastatus\n    exec {pfd}<\"$main_fifo_file\"\n    pt_expect_line 'init' <&$pfd\n    pt_expect_line 'cb nil' <&$pfd\n    printf '%s' \"$stat_content_2\" > \"$stat_file\"\n    pt_expect_line \"$expect_str\" <&$pfd\n    pt_close_fd \"$pfd\"\n    pt_testcase_end\n}\n"
  },
  {
    "path": "tests/pt_tests/plugin-cpu-usage-linux/01-total.lib.bash",
    "content": "cpu_usage_testcase 'nil' 'cb 0.1'\n"
  },
  {
    "path": "tests/pt_tests/plugin-cpu-usage-linux/02-cpu1.lib.bash",
    "content": "cpu_usage_testcase '1' 'cb 0.1'\n"
  },
  {
    "path": "tests/pt_tests/plugin-cpu-usage-linux/03-cpu2.lib.bash",
    "content": "cpu_usage_testcase '2' 'cb 0.1'\n"
  },
  {
    "path": "tests/pt_tests/plugin-cpu-usage-linux/04-cpu3.lib.bash",
    "content": "cpu_usage_testcase '3' 'cb 0.2'\n"
  },
  {
    "path": "tests/pt_tests/plugin-cpu-usage-linux/05-cpu4.lib.bash",
    "content": "cpu_usage_testcase '4' 'cb 0.2'\n"
  },
  {
    "path": "tests/pt_tests/plugin-dbus/00-common.lib.bash",
    "content": "main_fifo_file=./tmp-fifo-main\n\nx_dbus_begin() {\n    pt_dbus_daemon_spawn --session\n}\n\nx_dbus_end() {\n    pt_dbus_daemon_kill\n}\n\ndbus_srv_py_fifo=./tmp-fifo-for-dbus-srv-py\n\nx_dbus_spawn_dbus_srv_py() {\n    pt_add_fifo \"$dbus_srv_py_fifo\"\n    pt_spawn_thing dbus_srv_py ./optional/dbus_srv.py \"$dbus_srv_py_fifo\"\n    pt_expect_line 'running' < \"$dbus_srv_py_fifo\"\n    sleep 1\n}\n\nx_dbus_kill_dbus_srv_py() {\n    pt_kill_thing dbus_srv_py\n}\n\npreface='\nlocal function _fmt_x(x)\n    local function _validate_str(s)\n        assert(s:find(\"\\n\") == nil, \"string contains a line break\")\n        assert(s:find(\"\\\"\") == nil, \"string contains a double quote sign\")\n    end\n    local function _fmt_num(n)\n        if n == math.floor(n) then\n            return string.format(\"%d\", n)\n        else\n            return string.format(\"%.4f\", n)\n        end\n    end\n    local t = type(x)\n    if t == \"table\" then\n        local tk = type(next(x))\n        if tk == \"nil\" then\n            return \"{}\"\n        elseif tk == \"number\" then\n            local s = {}\n            for _, v in ipairs(x) do\n                s[#s + 1] = _fmt_x(v)\n            end\n            return string.format(\"{%s}\", table.concat(s, \",\"))\n        elseif tk == \"string\" then\n            local ks = {}\n            for k, _ in pairs(x) do\n                assert(type(k) == \"string\", \"table has mixed-type keys\")\n                _validate_str(k)\n                ks[#ks + 1] = k\n            end\n            table.sort(ks)\n            local s = {}\n            for _, k in ipairs(ks) do\n                local v = x[k]\n                s[#s + 1] = string.format(\"[\\\"%s\\\"]=%s\", k, _fmt_x(v))\n            end\n            return string.format(\"{%s}\", table.concat(s, \",\"))\n        else\n            error(\"table key has unexpected type: \" .. tk)\n        end\n    elseif t == \"string\" then\n        _validate_str(x)\n        return string.format(\"\\\"%s\\\"\", x)\n    elseif t == \"number\" then\n        return _fmt_num(x)\n    else\n        return tostring(x)\n    end\nend\n\nlocal function unpack1(t)\n    assert(type(t) == \"table\")\n    assert(#t == 1)\n    return t[1]\nend\n\nlocal function unpack2(t)\n    assert(type(t) == \"table\")\n    assert(#t == 2)\n    return t[1], t[2]\nend\n'\n"
  },
  {
    "path": "tests/pt_tests/plugin-dbus/01-simple.lib.bash",
    "content": "pt_require_tools dbus-send\n\nx_dbus_begin\n\npt_testcase_begin\npt_add_fifo \"$main_fifo_file\"\npt_write_widget_file <<__EOF__\nf = assert(io.open('$main_fifo_file', 'w'))\nf:setvbuf('line')\nf:write('init\\n')\n$preface\nwidget = {\n    plugin = '$PT_BUILD_DIR/plugins/dbus/plugin-dbus.so',\n    opts = {\n        signals = {\n            {\n                object_path = '/org/luastatus/sample/object/name',\n                interface = 'org.luastatus.ExampleInterface',\n                bus = 'session',\n            },\n        },\n    },\n    cb = function(t)\n        if t.what == 'signal' then\n            assert(type(t.sender) == 'string')\n            t.sender = '(non-deterministic)'\n        end\n        f:write('cb ' .. _fmt_x(t) .. '\\n')\n    end,\n}\n__EOF__\npt_spawn_luastatus\nexec {pfd}<\"$main_fifo_file\"\npt_expect_line 'init' <&$pfd\n\nmy_sender_process() {\n    while true; do\n        dbus-send \\\n            --session \\\n            /org/luastatus/sample/object/name \\\n            org.luastatus.ExampleInterface.ExampleMethod \\\n            int32:47 string:'hello world' double:63.125 \\\n            array:string:\"1st item\",\"next item\",\"last item\" \\\n            dict:string:int32:\"one\",1,\"two\",2,\"three\",3 \\\n            variant:int32:-8 \\\n            objpath:/org/luastatus/sample/object/name \\\n                || exit $?\n        sleep 2\n    done\n}\n\npt_spawn_thing dbus_sender my_sender_process\n\npt_expect_line 'cb {[\"bus\"]=\"session\",[\"interface\"]=\"org.luastatus.ExampleInterface\",[\"object_path\"]=\"/org/luastatus/sample/object/name\",[\"parameters\"]={\"47\",\"hello world\",63.1250,{\"1st item\",\"next item\",\"last item\"},{{\"one\",\"1\"},{\"two\",\"2\"},{\"three\",\"3\"}},\"-8\",\"/org/luastatus/sample/object/name\"},[\"sender\"]=\"(non-deterministic)\",[\"signal\"]=\"ExampleMethod\",[\"what\"]=\"signal\"}' <&$pfd\n\npt_close_fd \"$pfd\"\npt_testcase_end\n\npt_kill_thing dbus_sender\n\nx_dbus_end\n"
  },
  {
    "path": "tests/pt_tests/plugin-dbus/02-timeout.lib.bash",
    "content": "pt_require_tools dbus-send\n\nx_dbus_begin\n\npt_testcase_begin\nusing_measure\n\npt_add_fifo \"$main_fifo_file\"\npt_write_widget_file <<__EOF__\nf = assert(io.open('$main_fifo_file', 'w'))\nf:setvbuf('line')\nf:write('init\\n')\n$preface\nwidget = {\n    plugin = '$PT_BUILD_DIR/plugins/dbus/plugin-dbus.so',\n    opts = {\n        signals = {\n            {\n                object_path = '/org/luastatus/sample/object/name',\n                interface = 'org.luastatus.ExampleInterface',\n                bus = 'session',\n            },\n        },\n        timeout = 2,\n        greet = true,\n    },\n    cb = function(t)\n        if t.what == 'signal' then\n            assert(type(t.sender) == 'string')\n            t.sender = '(non-deterministic)'\n        end\n        f:write('cb ' .. _fmt_x(t) .. '\\n')\n    end,\n}\n__EOF__\npt_spawn_luastatus\nexec {pfd}<\"$main_fifo_file\"\npt_expect_line 'init' <&$pfd\npt_expect_line 'cb {[\"what\"]=\"hello\"}' <&$pfd\n\nmeasure_start\npt_expect_line 'cb {[\"what\"]=\"timeout\"}' <&$pfd\nmeasure_check_ms 2000\n\nfor (( i = 0; i < 3; ++i )); do\n    pt_check dbus-send \\\n        --session \\\n        /org/luastatus/sample/object/name \\\n        org.luastatus.ExampleInterface.ExampleMethod \\\n        int32:47 string:'hello world' double:63.125 \\\n        array:string:\"1st item\",\"next item\",\"last item\" \\\n        dict:string:int32:\"one\",1,\"two\",2,\"three\",3 \\\n        variant:int32:-8 \\\n        objpath:/org/luastatus/sample/object/name\n\n    measure_start\n    pt_expect_line 'cb {[\"bus\"]=\"session\",[\"interface\"]=\"org.luastatus.ExampleInterface\",[\"object_path\"]=\"/org/luastatus/sample/object/name\",[\"parameters\"]={\"47\",\"hello world\",63.1250,{\"1st item\",\"next item\",\"last item\"},{{\"one\",\"1\"},{\"two\",\"2\"},{\"three\",\"3\"}},\"-8\",\"/org/luastatus/sample/object/name\"},[\"sender\"]=\"(non-deterministic)\",[\"signal\"]=\"ExampleMethod\",[\"what\"]=\"signal\"}' <&$pfd\n    measure_check_ms 0\n\n    pt_expect_line 'cb {[\"what\"]=\"timeout\"}' <&$pfd\n    measure_check_ms 2000\n\n    pt_expect_line 'cb {[\"what\"]=\"timeout\"}' <&$pfd\n    measure_check_ms 2000\ndone\n\npt_close_fd \"$pfd\"\npt_testcase_end\n\nx_dbus_end\n"
  },
  {
    "path": "tests/pt_tests/plugin-dbus/03-greet.lib.bash",
    "content": "pt_require_tools dbus-send\n\nx_dbus_begin\n\npt_testcase_begin\npt_add_fifo \"$main_fifo_file\"\npt_write_widget_file <<__EOF__\nf = assert(io.open('$main_fifo_file', 'w'))\nf:setvbuf('line')\nf:write('init\\n')\n$preface\nwidget = {\n    plugin = '$PT_BUILD_DIR/plugins/dbus/plugin-dbus.so',\n    opts = {\n        signals = {\n            {\n                object_path = '/org/luastatus/sample/object/name',\n                interface = 'org.luastatus.ExampleInterface',\n                bus = 'session',\n            },\n        },\n        greet = true,\n    },\n    cb = function(t)\n        if t.what == 'signal' then\n            assert(type(t.sender) == 'string')\n            t.sender = '(non-deterministic)'\n        end\n        f:write('cb ' .. _fmt_x(t) .. '\\n')\n    end,\n}\n__EOF__\npt_spawn_luastatus\nexec {pfd}<\"$main_fifo_file\"\npt_expect_line 'init' <&$pfd\npt_expect_line 'cb {[\"what\"]=\"hello\"}' <&$pfd\n\nsleep 1\n\npt_check dbus-send \\\n    --session \\\n    /org/luastatus/sample/object/name \\\n    org.luastatus.ExampleInterface.ExampleMethod \\\n    int32:47 string:'hello world' double:63.125 \\\n    array:string:\"1st item\",\"next item\",\"last item\" \\\n    dict:string:int32:\"one\",1,\"two\",2,\"three\",3 \\\n    variant:int32:-8 \\\n    objpath:/org/luastatus/sample/object/name\n\npt_expect_line 'cb {[\"bus\"]=\"session\",[\"interface\"]=\"org.luastatus.ExampleInterface\",[\"object_path\"]=\"/org/luastatus/sample/object/name\",[\"parameters\"]={\"47\",\"hello world\",63.1250,{\"1st item\",\"next item\",\"last item\"},{{\"one\",\"1\"},{\"two\",\"2\"},{\"three\",\"3\"}},\"-8\",\"/org/luastatus/sample/object/name\"},[\"sender\"]=\"(non-deterministic)\",[\"signal\"]=\"ExampleMethod\",[\"what\"]=\"signal\"}' <&$pfd\n\npt_close_fd \"$pfd\"\npt_testcase_end\n\nx_dbus_end\n"
  },
  {
    "path": "tests/pt_tests/plugin-dbus/04-get-prop.lib.bash",
    "content": "x_dbus_begin\n\npt_testcase_begin\npt_add_fifo \"$main_fifo_file\"\npt_write_widget_file <<__EOF__\nf = assert(io.open('$main_fifo_file', 'w'))\nf:setvbuf('line')\nf:write('init\\n')\n$preface\nwidget = {\n    plugin = '$PT_BUILD_DIR/plugins/dbus/plugin-dbus.so',\n    opts = {\n        signals = {},\n        greet = true,\n    },\n    cb = function(t)\n        assert(t.what == 'hello', 't.what is not \"hello\"')\n\n        local is_ok, res = luastatus.plugin.get_property({\n            bus = \"session\",\n            dest = \"org.freedesktop.DBus\",\n            object_path = \"/org/freedesktop/DBus\",\n            interface = \"org.freedesktop.DBus\",\n            property_name = \"Features\",\n        })\n        assert(is_ok, res)\n\n        local features = unpack1(res)\n        assert(type(features) == 'table', 'features is not an array')\n        for _, x in ipairs(features) do\n            print('Feature:', x)\n            assert(type(x) == 'string', 'feature is not a string')\n        end\n\n        f:write('ok\\n')\n    end,\n}\n__EOF__\npt_spawn_luastatus\nexec {pfd}<\"$main_fifo_file\"\npt_expect_line 'init' <&$pfd\n\npt_expect_line 'ok' <&$pfd\n\npt_close_fd \"$pfd\"\npt_testcase_end\n\nx_dbus_end\n"
  },
  {
    "path": "tests/pt_tests/plugin-dbus/05-get-all-prop.lib.bash",
    "content": "x_dbus_begin\n\npt_testcase_begin\npt_add_fifo \"$main_fifo_file\"\npt_write_widget_file <<__EOF__\nf = assert(io.open('$main_fifo_file', 'w'))\nf:setvbuf('line')\nf:write('init\\n')\n$preface\nwidget = {\n    plugin = '$PT_BUILD_DIR/plugins/dbus/plugin-dbus.so',\n    opts = {\n        signals = {},\n        greet = true,\n    },\n    cb = function(t)\n        assert(t.what == 'hello', 't.what is not \"hello\"')\n\n        local is_ok, res = luastatus.plugin.get_all_properties({\n            bus = \"session\",\n            dest = \"org.freedesktop.DBus\",\n            object_path = \"/org/freedesktop/DBus\",\n            interface = \"org.freedesktop.DBus\",\n        })\n        assert(is_ok, res)\n\n        local arr = unpack1(res)\n\n        assert(type(arr) == 'table', 'result is not a table')\n\n        local props_set = {}\n        for _, x in ipairs(arr) do\n            local name, value = unpack2(x)\n            print('Property:', name, value)\n            assert(type(name) == 'string', 'property name is not a string')\n            props_set[name] = true\n        end\n\n        assert(props_set['Features'], 'no \"Features\" property')\n        assert(props_set['Interfaces'], 'no \"Interfaces\" property')\n\n        f:write('ok\\n')\n    end,\n}\n__EOF__\npt_spawn_luastatus\nexec {pfd}<\"$main_fifo_file\"\npt_expect_line 'init' <&$pfd\n\npt_expect_line 'ok' <&$pfd\n\npt_close_fd \"$pfd\"\npt_testcase_end\n\nx_dbus_end\n"
  },
  {
    "path": "tests/pt_tests/plugin-dbus/06-OPTIONAL-getset-prop.lib.bash",
    "content": "if (( ! PLUGIN_DBUS_OPTIONAL )); then\n    return 0\nfi\n\nx_dbus_begin\nx_dbus_spawn_dbus_srv_py\n\npt_testcase_begin\npt_add_fifo \"$main_fifo_file\"\npt_write_widget_file <<__EOF__\nf = assert(io.open('$main_fifo_file', 'w'))\nf:setvbuf('line')\nf:write('init\\n')\n$preface\n\nlocal function do_call_plugin_function(f, params)\n    local is_ok, res = f(params)\n    assert(is_ok, res)\n    return res\nend\n\nlocal function test_get_set_prop()\n    do_call_plugin_function(luastatus.plugin.set_property_str, {\n        bus = \"session\",\n        dest = \"io.github.shdown.luastatus.test\",\n        object_path = \"/io/github/shdown/luastatus/test/MyObject\",\n        interface = \"io.github.shdown.luastatus.test\",\n        property_name = \"MyProperty\",\n        value_str = \"hi there\",\n    })\n\n    local prop = unpack1(do_call_plugin_function(luastatus.plugin.get_property, {\n        bus = \"session\",\n        dest = \"io.github.shdown.luastatus.test\",\n        object_path = \"/io/github/shdown/luastatus/test/MyObject\",\n        interface = \"io.github.shdown.luastatus.test\",\n        property_name = \"MyProperty\",\n    }))\n\n    if prop ~= \"hi there\" then\n        error(string.format(\"invalid property value after Set(): %s\", prop))\n    end\nend\n\nwidget = {\n    plugin = '$PT_BUILD_DIR/plugins/dbus/plugin-dbus.so',\n    opts = {\n        signals = {},\n        greet = true,\n    },\n    cb = function(t)\n        test_get_set_prop()\n        f:write('ok\\n')\n    end,\n}\n__EOF__\npt_spawn_luastatus\nexec {pfd}<\"$main_fifo_file\"\npt_expect_line 'init' <&$pfd\n\npt_expect_line 'ok' <&$pfd\n\npt_close_fd \"$pfd\"\npt_testcase_end\n\nx_dbus_kill_dbus_srv_py\nx_dbus_end\n"
  },
  {
    "path": "tests/pt_tests/plugin-dbus/07-OPTIONAL-getall-prop.lib.bash",
    "content": "if (( ! PLUGIN_DBUS_OPTIONAL )); then\n    return 0\nfi\n\nx_dbus_begin\nx_dbus_spawn_dbus_srv_py\n\npt_testcase_begin\npt_add_fifo \"$main_fifo_file\"\npt_write_widget_file <<__EOF__\nf = assert(io.open('$main_fifo_file', 'w'))\nf:setvbuf('line')\nf:write('init\\n')\n$preface\n\nlocal function do_call_plugin_function(f, params)\n    local is_ok, res = f(params)\n    assert(is_ok, res)\n    return res\nend\n\nlocal function test_get_all_props()\n    do_call_plugin_function(luastatus.plugin.set_property_str, {\n        bus = \"session\",\n        dest = \"io.github.shdown.luastatus.test\",\n        object_path = \"/io/github/shdown/luastatus/test/MyObject\",\n        interface = \"io.github.shdown.luastatus.test\",\n        property_name = \"MyProperty\",\n        value_str = \"test... test... test...\",\n    })\n\n    local res = do_call_plugin_function(luastatus.plugin.get_all_properties, {\n        bus = \"session\",\n        dest = \"io.github.shdown.luastatus.test\",\n        object_path = \"/io/github/shdown/luastatus/test/MyObject\",\n        interface = \"io.github.shdown.luastatus.test\",\n    })\n    local k, v = unpack2(unpack1(unpack1(res)))\n    assert(k == 'MyProperty')\n    assert(v == 'test... test... test...')\nend\n\nwidget = {\n    plugin = '$PT_BUILD_DIR/plugins/dbus/plugin-dbus.so',\n    opts = {\n        signals = {},\n        greet = true,\n    },\n    cb = function(t)\n        test_get_all_props()\n        f:write('ok\\n')\n    end,\n}\n__EOF__\npt_spawn_luastatus\nexec {pfd}<\"$main_fifo_file\"\npt_expect_line 'init' <&$pfd\n\npt_expect_line 'ok' <&$pfd\n\npt_close_fd \"$pfd\"\npt_testcase_end\n\nx_dbus_kill_dbus_srv_py\nx_dbus_end\n"
  },
  {
    "path": "tests/pt_tests/plugin-dbus/08-OPTIONAL-call-method-str.lib.bash",
    "content": "if (( ! PLUGIN_DBUS_OPTIONAL )); then\n    return 0\nfi\n\nx_dbus_begin\nx_dbus_spawn_dbus_srv_py\n\npt_testcase_begin\npt_add_fifo \"$main_fifo_file\"\npt_write_widget_file <<__EOF__\nf = assert(io.open('$main_fifo_file', 'w'))\nf:setvbuf('line')\nf:write('init\\n')\n$preface\n\nlocal function do_call_plugin_function(f, params)\n    local is_ok, res = f(params)\n    assert(is_ok, res)\n    return res\nend\n\nlocal function test_call_method_upcase()\n    local raw_res = do_call_plugin_function(luastatus.plugin.call_method_str, {\n        bus = \"session\",\n        dest = \"io.github.shdown.luastatus.test\",\n        object_path = \"/io/github/shdown/luastatus/test/MyObject\",\n        interface = \"io.github.shdown.luastatus.test\",\n        method = \"Upcase\",\n        arg_str = \"Please upcase this\",\n    })\n    local res = unpack1(raw_res)\n    assert(res == 'PLEASE UPCASE THIS')\nend\n\nlocal function test_call_method_ret42()\n    local raw_res = do_call_plugin_function(luastatus.plugin.call_method_str, {\n        bus = \"session\",\n        dest = \"io.github.shdown.luastatus.test\",\n        object_path = \"/io/github/shdown/luastatus/test/MyObject\",\n        interface = \"io.github.shdown.luastatus.test\",\n        method = \"ReturnFortyTwo\",\n        -- no \"arg_str\"\n    })\n    local res = unpack1(raw_res)\n    assert(res == '42')\nend\n\nwidget = {\n    plugin = '$PT_BUILD_DIR/plugins/dbus/plugin-dbus.so',\n    opts = {\n        signals = {},\n        greet = true,\n    },\n    cb = function(t)\n        test_call_method_upcase()\n        test_call_method_ret42()\n        f:write('ok\\n')\n    end,\n}\n__EOF__\npt_spawn_luastatus\nexec {pfd}<\"$main_fifo_file\"\npt_expect_line 'init' <&$pfd\n\npt_expect_line 'ok' <&$pfd\n\npt_close_fd \"$pfd\"\npt_testcase_end\n\nx_dbus_kill_dbus_srv_py\nx_dbus_end\n"
  },
  {
    "path": "tests/pt_tests/plugin-dbus/09-OPTIONAL-call-method-DTLL.lib.bash",
    "content": "if (( ! PLUGIN_DBUS_OPTIONAL )); then\n    return 0\nfi\n\n# DTLL = dbustypes_lowlevel\n\nx_dbus_begin\nx_dbus_spawn_dbus_srv_py\n\npt_testcase_begin\npt_add_fifo \"$main_fifo_file\"\npt_write_widget_file <<__EOF__\nf = assert(io.open('$main_fifo_file', 'w'))\nf:setvbuf('line')\nf:write('init\\n')\n$preface\n\nlocal function do_call_plugin_function(f, params)\n    local is_ok, res = f(params)\n    assert(is_ok, res)\n    return res\nend\n\nlocal function test_call_method()\n    local DTLL = luastatus.plugin.dbustypes_lowlevel\n    local DTLL_str = DTLL.mktype_simple('s')\n\n    local S = function(s) return DTLL.mkval_simple(DTLL_str, s) end\n\n    local the_arg = DTLL.mkval_array(DTLL_str, {\n        S('one'),\n        S('two'),\n        S('three'),\n    })\n\n    local args = DTLL.mkval_tuple({the_arg})\n\n    local raw_res = do_call_plugin_function(luastatus.plugin.call_method, {\n        bus = \"session\",\n        dest = \"io.github.shdown.luastatus.test\",\n        object_path = \"/io/github/shdown/luastatus/test/MyObject\",\n        interface = \"io.github.shdown.luastatus.test\",\n        method = \"ConvertArrayToDictHexify\",\n        args = args,\n    })\n    local res = unpack1(raw_res)\n\n    assert(type(res) == 'table')\n    local outputs = {}\n    for _, kv in ipairs(res) do\n        table.insert(outputs, string.format('%s:%s', kv[1], kv[2]))\n    end\n    table.sort(outputs)\n    local output = table.concat(outputs, ' ')\n    assert(output == '30:6F6E65 31:74776F 32:7468726565')\nend\n\nwidget = {\n    plugin = '$PT_BUILD_DIR/plugins/dbus/plugin-dbus.so',\n    opts = {\n        signals = {},\n        greet = true,\n    },\n    cb = function(t)\n        test_call_method()\n        f:write('ok\\n')\n    end,\n}\n__EOF__\npt_spawn_luastatus\nexec {pfd}<\"$main_fifo_file\"\npt_expect_line 'init' <&$pfd\n\npt_expect_line 'ok' <&$pfd\n\npt_close_fd \"$pfd\"\npt_testcase_end\n\nx_dbus_kill_dbus_srv_py\nx_dbus_end\n"
  },
  {
    "path": "tests/pt_tests/plugin-dbus/10-OPTIONAL-call-method-arr.lib.bash",
    "content": "if (( ! PLUGIN_DBUS_OPTIONAL )); then\n    return 0\nfi\n\nx_dbus_begin\nx_dbus_spawn_dbus_srv_py\n\npt_testcase_begin\npt_add_fifo \"$main_fifo_file\"\npt_write_widget_file <<__EOF__\nf = assert(io.open('$main_fifo_file', 'w'))\nf:setvbuf('line')\nf:write('init\\n')\n$preface\n\nlocal function do_call_plugin_function(f, params)\n    local is_ok, res = f(params)\n    assert(is_ok, res)\n    return res\nend\n\nlocal function test_call_method()\n    local args = luastatus.plugin.dbustypes.mkval_from_fmt('(as)', {{\n        'one', 'two', 'three'\n    }})\n    local raw_res = do_call_plugin_function(luastatus.plugin.call_method, {\n        bus = \"session\",\n        dest = \"io.github.shdown.luastatus.test\",\n        object_path = \"/io/github/shdown/luastatus/test/MyObject\",\n        interface = \"io.github.shdown.luastatus.test\",\n        method = \"ConvertArrayToDictHexify\",\n        args = args,\n    })\n    local res = unpack1(raw_res)\n\n    assert(type(res) == 'table')\n    local outputs = {}\n    for _, kv in ipairs(res) do\n        table.insert(outputs, string.format('%s:%s', kv[1], kv[2]))\n    end\n    table.sort(outputs)\n    local output = table.concat(outputs, ' ')\n    assert(output == '30:6F6E65 31:74776F 32:7468726565')\nend\n\nwidget = {\n    plugin = '$PT_BUILD_DIR/plugins/dbus/plugin-dbus.so',\n    opts = {\n        signals = {},\n        greet = true,\n    },\n    cb = function(t)\n        test_call_method()\n        f:write('ok\\n')\n    end,\n}\n__EOF__\npt_spawn_luastatus\nexec {pfd}<\"$main_fifo_file\"\npt_expect_line 'init' <&$pfd\n\npt_expect_line 'ok' <&$pfd\n\npt_close_fd \"$pfd\"\npt_testcase_end\n\nx_dbus_kill_dbus_srv_py\nx_dbus_end\n"
  },
  {
    "path": "tests/pt_tests/plugin-dbus/11-OPTIONAL-call-method-dict.lib.bash",
    "content": "if (( ! PLUGIN_DBUS_OPTIONAL )); then\n    return 0\nfi\n\nx_dbus_begin\nx_dbus_spawn_dbus_srv_py\n\npt_testcase_begin\npt_add_fifo \"$main_fifo_file\"\npt_write_widget_file <<__EOF__\nf = assert(io.open('$main_fifo_file', 'w'))\nf:setvbuf('line')\nf:write('init\\n')\n$preface\n\nlocal function do_call_plugin_function(f, params)\n    local is_ok, res = f(params)\n    assert(is_ok, res)\n    return res\nend\n\nlocal function test_call_method()\n    local args = luastatus.plugin.dbustypes.mkval_from_fmt('(a{ss})', {{\n        {'key1', 'value1'},\n        {'key2', 'value2'},\n        {'key3', 'value3'},\n    }})\n    local raw_res = do_call_plugin_function(luastatus.plugin.call_method, {\n        bus = \"session\",\n        dest = \"io.github.shdown.luastatus.test\",\n        object_path = \"/io/github/shdown/luastatus/test/MyObject\",\n        interface = \"io.github.shdown.luastatus.test\",\n        method = \"ConvertDictToString\",\n        args = args,\n    })\n    local res = unpack1(raw_res)\n    assert(type(res) == 'string')\n    assert(res == 'key1:value1,key2:value2,key3:value3')\nend\n\nwidget = {\n    plugin = '$PT_BUILD_DIR/plugins/dbus/plugin-dbus.so',\n    opts = {\n        signals = {},\n        greet = true,\n    },\n    cb = function(t)\n        test_call_method()\n        f:write('ok\\n')\n    end,\n}\n__EOF__\npt_spawn_luastatus\nexec {pfd}<\"$main_fifo_file\"\npt_expect_line 'init' <&$pfd\n\npt_expect_line 'ok' <&$pfd\n\npt_close_fd \"$pfd\"\npt_testcase_end\n\nx_dbus_kill_dbus_srv_py\nx_dbus_end\n"
  },
  {
    "path": "tests/pt_tests/plugin-dbus/12-OPTIONAL-call-method-tuple.lib.bash",
    "content": "if (( ! PLUGIN_DBUS_OPTIONAL )); then\n    return 0\nfi\n\nx_dbus_begin\nx_dbus_spawn_dbus_srv_py\n\npt_testcase_begin\npt_add_fifo \"$main_fifo_file\"\npt_write_widget_file <<__EOF__\nf = assert(io.open('$main_fifo_file', 'w'))\nf:setvbuf('line')\nf:write('init\\n')\n$preface\n\nlocal function do_call_plugin_function(f, params)\n    local is_ok, res = f(params)\n    assert(is_ok, res)\n    return res\nend\n\nlocal function do_test_once(method, args, expected_result)\n    local raw_res = do_call_plugin_function(luastatus.plugin.call_method, {\n        bus = \"session\",\n        dest = \"io.github.shdown.luastatus.test\",\n        object_path = \"/io/github/shdown/luastatus/test/MyObject\",\n        interface = \"io.github.shdown.luastatus.test\",\n        method = method,\n        args = args,\n    })\n    local res = unpack1(raw_res)\n    assert(type(res) == 'string')\n    assert(res == expected_result)\nend\n\nlocal function test_all()\n    do_test_once(\n        'RecvTuple0',\n        luastatus.plugin.dbustypes.mkval_from_fmt('()', {}),\n        'Empty'\n    )\n    do_test_once(\n        'RecvTuple1',\n        luastatus.plugin.dbustypes.mkval_from_fmt('(s)', {'hello'}),\n        'hello'\n    )\n    do_test_once(\n        'RecvTuple2',\n        luastatus.plugin.dbustypes.mkval_from_fmt('(ss)', {'hello', 'world'}),\n        'helloworld'\n    )\n    do_test_once(\n        'RecvTuple3',\n        luastatus.plugin.dbustypes.mkval_from_fmt('(sss)', {'hello', 'world', '!'}),\n        'helloworld!'\n    )\nend\n\nwidget = {\n    plugin = '$PT_BUILD_DIR/plugins/dbus/plugin-dbus.so',\n    opts = {\n        signals = {},\n        greet = true,\n    },\n    cb = function(t)\n        test_all()\n        f:write('ok\\n')\n    end,\n}\n__EOF__\npt_spawn_luastatus\nexec {pfd}<\"$main_fifo_file\"\npt_expect_line 'init' <&$pfd\n\npt_expect_line 'ok' <&$pfd\n\npt_close_fd \"$pfd\"\npt_testcase_end\n\nx_dbus_kill_dbus_srv_py\nx_dbus_end\n"
  },
  {
    "path": "tests/pt_tests/plugin-dbus/13-OPTIONAL-call-method-misc.lib.bash",
    "content": "if (( ! PLUGIN_DBUS_OPTIONAL )); then\n    return 0\nfi\n\nx_dbus_begin\nx_dbus_spawn_dbus_srv_py\n\npt_testcase_begin\npt_add_fifo \"$main_fifo_file\"\npt_write_widget_file <<__EOF__\nf = assert(io.open('$main_fifo_file', 'w'))\nf:setvbuf('line')\nf:write('init\\n')\n$preface\n\nlocal function do_call_plugin_function(f, params)\n    local is_ok, res = f(params)\n    assert(is_ok, res)\n    return res\nend\n\nlocal function do_test_once(fmt, value, expected_result)\n    local raw_res = do_call_plugin_function(luastatus.plugin.call_method, {\n        bus = \"session\",\n        dest = \"io.github.shdown.luastatus.test\",\n        object_path = \"/io/github/shdown/luastatus/test/MyObject\",\n        interface = \"io.github.shdown.luastatus.test\",\n        method = \"RecvVariant\",\n        args = luastatus.plugin.dbustypes.mkval_from_fmt(fmt, value),\n    })\n    local res = unpack1(raw_res)\n    assert(type(res) == 'string')\n    assert(res == expected_result)\nend\n\nlocal function test_all()\n    do_test_once('(b)', {true}, 'bool')\n\n    do_test_once('(y)', {42}, 'byte')\n    do_test_once('(y)', {'x'}, 'byte')\n\n    do_test_once('(d)', {1.25}, 'double')\n\n    do_test_once('(s)', {'hello'}, 'string')\n    do_test_once('(o)', {'/io/github/shdown/luastatus/foo/bar'}, 'object_path')\n    do_test_once('(g)', {'(susssasa{sv}i)'}, 'signature')\n\n    local wrapped = luastatus.plugin.dbustypes.mkval_from_fmt('s', 'hi there')\n    do_test_once('(v)', {wrapped}, 'variant')\n\n    local INT_TYPES = {\n        {'(n)', 'i16'},\n        {'(q)', 'u16'},\n        {'(i)', 'i32'},\n        {'(u)', 'u32'},\n        {'(x)', 'i64'},\n        {'(t)', 'u64'},\n    }\n    for _, pq in ipairs(INT_TYPES) do\n        local fmt, expected_result = pq[1], pq[2]\n        do_test_once(fmt, {53}, expected_result)\n        do_test_once(fmt, {'58'}, expected_result)\n    end\nend\n\nwidget = {\n    plugin = '$PT_BUILD_DIR/plugins/dbus/plugin-dbus.so',\n    opts = {\n        signals = {},\n        greet = true,\n    },\n    cb = function(t)\n        test_all()\n        f:write('ok\\n')\n    end,\n}\n__EOF__\npt_spawn_luastatus\nexec {pfd}<\"$main_fifo_file\"\npt_expect_line 'init' <&$pfd\n\npt_expect_line 'ok' <&$pfd\n\npt_close_fd \"$pfd\"\npt_testcase_end\n\nx_dbus_kill_dbus_srv_py\nx_dbus_end\n"
  },
  {
    "path": "tests/pt_tests/plugin-dbus/14-OPTIONAL-call-method-handle.lib.bash",
    "content": "if (( ! PLUGIN_DBUS_OPTIONAL )); then\n    return 0\nfi\n\nx_dbus_begin\nx_dbus_spawn_dbus_srv_py\n\npt_testcase_begin\npt_add_fifo \"$main_fifo_file\"\npt_write_widget_file <<__EOF__\nf = assert(io.open('$main_fifo_file', 'w'))\nf:setvbuf('line')\nf:write('init\\n')\n$preface\n\nlocal function do_test()\n    local is_ok, err_msg = pcall(luastatus.plugin.dbustypes.mkval_from_fmt, 'h', 42)\n    assert(not is_ok, 'expected error to be thrown from \"mkval_from_fmt\"')\n    assert(err_msg:find('creation of handles is not supported') ~= nil)\nend\n\nwidget = {\n    plugin = '$PT_BUILD_DIR/plugins/dbus/plugin-dbus.so',\n    opts = {\n        signals = {},\n        greet = true,\n    },\n    cb = function(t)\n        do_test()\n        f:write('ok\\n')\n    end,\n}\n__EOF__\npt_spawn_luastatus\nexec {pfd}<\"$main_fifo_file\"\npt_expect_line 'init' <&$pfd\n\npt_expect_line 'ok' <&$pfd\n\npt_close_fd \"$pfd\"\npt_testcase_end\n\nx_dbus_kill_dbus_srv_py\nx_dbus_end\n"
  },
  {
    "path": "tests/pt_tests/plugin-dbus/15-report-when-ready.lib.bash",
    "content": "pt_require_tools dbus-send\n\nx_dbus_begin\n\npt_testcase_begin\npt_add_fifo \"$main_fifo_file\"\npt_write_widget_file <<__EOF__\nf = assert(io.open('$main_fifo_file', 'w'))\nf:setvbuf('line')\nf:write('init\\n')\n$preface\nwidget = {\n    plugin = '$PT_BUILD_DIR/plugins/dbus/plugin-dbus.so',\n    opts = {\n        signals = {\n            {\n                object_path = '/org/luastatus/sample/object/name',\n                interface = 'org.luastatus.ExampleInterface',\n                bus = 'session',\n            },\n        },\n        report_when_ready = true,\n    },\n    cb = function(t)\n        if t.what == 'signal' then\n            assert(type(t.sender) == 'string')\n            t.sender = '(non-deterministic)'\n        end\n        f:write('cb ' .. _fmt_x(t) .. '\\n')\n    end,\n}\n__EOF__\npt_spawn_luastatus\nexec {pfd}<\"$main_fifo_file\"\npt_expect_line 'init' <&$pfd\npt_expect_line 'cb {[\"what\"]=\"ready\"}' <&$pfd\n\nsleep 1\n\npt_check dbus-send \\\n    --session \\\n    /org/luastatus/sample/object/name \\\n    org.luastatus.ExampleInterface.ExampleMethod \\\n    int32:47 string:'hello world' double:63.125 \\\n    array:string:\"1st item\",\"next item\",\"last item\" \\\n    dict:string:int32:\"one\",1,\"two\",2,\"three\",3 \\\n    variant:int32:-8 \\\n    objpath:/org/luastatus/sample/object/name\n\npt_expect_line 'cb {[\"bus\"]=\"session\",[\"interface\"]=\"org.luastatus.ExampleInterface\",[\"object_path\"]=\"/org/luastatus/sample/object/name\",[\"parameters\"]={\"47\",\"hello world\",63.1250,{\"1st item\",\"next item\",\"last item\"},{{\"one\",\"1\"},{\"two\",\"2\"},{\"three\",\"3\"}},\"-8\",\"/org/luastatus/sample/object/name\"},[\"sender\"]=\"(non-deterministic)\",[\"signal\"]=\"ExampleMethod\",[\"what\"]=\"signal\"}' <&$pfd\n\npt_close_fd \"$pfd\"\npt_testcase_end\n\nx_dbus_end\n"
  },
  {
    "path": "tests/pt_tests/plugin-dbus/skip-me-if.lib.bash",
    "content": "# It does not pass under TSAN and helgrind. Because glib is crazy or something like that.\n\nif [[ $PT_TOOL == helgrind ]]; then\n    return $PT_SKIP_ME_YES\nfi\n\nif pt_cmake_opt_enabled WITH_TSAN; then\n    return $PT_SKIP_ME_YES\nfi\n\nreturn $PT_SKIP_ME_NO\n"
  },
  {
    "path": "tests/pt_tests/plugin-disk-io-linux/00-common.lib.bash",
    "content": "pt_require_tools mktemp\n\nmain_fifo_file=./tmp-fifo-main\n\ndisk_io_linux_testcase() {\n    local expect_str_1=$1\n    local expect_str_2=$2\n    local content_1=$3\n    local content_2=$4\n\n    pt_testcase_begin\n    pt_add_fifo \"$main_fifo_file\"\n    local proc_dir; proc_dir=$(mktemp -d) || pt_fail \"'mktemp -d' failed\"\n    pt_add_dir_to_remove \"$proc_dir\"\n    local diskstats_file=$proc_dir/diskstats\n\n    pt_add_file_to_remove \"$diskstats_file\"\n    pt_write_widget_file <<__EOF__\nf = assert(io.open('$main_fifo_file', 'w'))\nf:setvbuf('line')\nf:write('init\\n')\nfunction my_event_func() end\nx = dofile('$PT_SOURCE_DIR/plugins/disk-io-linux/disk-io-linux.lua')\nwidget = x.widget{\n    _proc_path = '$proc_dir',\n    cb = function(t)\n        f:write('cb')\n\n        table.sort(t, function(a, b)\n            return a.name < b.name\n        end)\n\n        for _, x in ipairs(t) do\n            f:write(string.format(\n                \" (%d,%d name=>%s r=>%d w=>%d)\",\n                x.num_major,\n                x.num_minor,\n                x.name,\n                x.read_bytes,\n                x.written_bytes\n            ))\n        end\n\n        if #t == 0 then\n            f:write(' <none>')\n        end\n\n        f:write('\\n')\n    end,\n    event = my_event_func,\n}\nwidget.plugin = ('$PT_BUILD_DIR/plugins/{}/plugin-{}.so'):gsub('{}', widget.plugin)\nassert(widget.event == my_event_func)\n__EOF__\n\n    printf '%s' \"$content_1\" > \"$diskstats_file\" || pt_fail \"cannot write diskstats_file\"\n\n    pt_spawn_luastatus\n    exec {pfd}<\"$main_fifo_file\"\n    pt_expect_line 'init' <&$pfd\n\n    pt_expect_line \"$expect_str_1\" <&$pfd\n\n    printf '%s' \"$content_2\" > \"$diskstats_file\" || pt_fail \"cannot write diskstats_file\"\n\n    pt_expect_line \"$expect_str_2\" <&$pfd\n\n    pt_close_fd \"$pfd\"\n    pt_testcase_end\n}\n"
  },
  {
    "path": "tests/pt_tests/plugin-disk-io-linux/01-simple.lib.bash",
    "content": "content1=\"\\\n 259       0 nvme0n1 137275 50367 21312112 79036 259141 59459 47106050 879950 0 101996 979461 0 0 0 0 5700 20474\n 259       1 nvme0n1p1 187 0 7679 88 10 0 10 17 0 100 106 0 0 0 0 0 0\n 259       2 nvme0n1p2 25027 8539 2421066 15822 3282 1336 210032 22593 0 17612 38416 0 0 0 0 0 0\n 259       3 nvme0n1p3 111967 41828 18880887 63111 255847 58123 46896008 857339 43 90680 920450 0 0 0 0 0 0\n 254       0 dm-0 153758 0 18880082 102332 313970 0 46896008 5605212 44 95076 5707544 0 0 0 0 0 0\n\"\n\ncontent2=\"\\\n 259       0 nvme0n1 141523 50367 22387160 81628 263894 59459 48322570 900083 0 102748 1002186 0 0 0 0 5700 20474\n 259       1 nvme0n1p1 187 0 7679 88 10 0 10 17 0 100 106 0 0 0 0 0 0\n 259       2 nvme0n1p2 25027 8539 2421066 15822 3282 1336 210032 22593 0 17612 38416 0 0 0 0 0 0\n 259       3 nvme0n1p3 116215 41828 19955935 65702 260600 58123 48112528 877472 74 91556 943175 0 0 0 0 0 0\n 254       0 dm-0 158006 0 19955130 105076 318723 0 48112528 5662204 75 96024 5767280 0 0 0 0 0 0\n\"\n\nawk_program='\nBEGIN {\n    SECTOR_SIZE = 512\n}\n$0 != \"\" {\n    disk_id = $1 \",\" $2 \" \" $3\n    r = $6\n    w = $10\n    if (!seen[disk_id]) {\n        seen[disk_id] = 1\n        seen_r[disk_id] = r\n        seen_w[disk_id] = w\n    } else {\n        delta_r = r - seen_r[disk_id]\n        delta_w = w - seen_w[disk_id]\n        print(disk_id, delta_r * SECTOR_SIZE, delta_w * SECTOR_SIZE)\n    }\n}'\n\nexpect_str=\n\ngen_expect_str() {\n    expect_str='cb'\n\n    local maj_min\n    local name\n    local delta_r\n    local delta_w\n    while read maj_min name delta_r delta_w; do\n        expect_str+=\" (${maj_min} name=>${name} r=>${delta_r} w=>${delta_w})\"\n    done < <(\n        printf '%s\\n%s\\n' \"${content1}${content2}\" \\\n            | awk \"$awk_program\" \\\n            | LC_ALL=C sort -k2,2\n    )\n}\n\ngen_expect_str\n\ndisk_io_linux_testcase 'cb <none>' \"$expect_str\" \"$content1\" \"$content2\"\n"
  },
  {
    "path": "tests/pt_tests/plugin-file-contents-linux/00-common.lib.bash",
    "content": "main_fifo_file=./tmp-fifo-main\n"
  },
  {
    "path": "tests/pt_tests/plugin-file-contents-linux/01-simple.lib.bash",
    "content": "pt_require_tools mktemp\n\npt_testcase_begin\npt_add_fifo \"$main_fifo_file\"\n\nmyfile=$(mktemp) || pt_fail 'mktemp failed'\npt_add_file_to_remove \"$myfile\"\n\npt_write_widget_file <<__EOF__\nf = assert(io.open('$main_fifo_file', 'w'))\nf:setvbuf('line')\nf:write('init\\n')\nfunction my_event_func() end\nx = dofile('$PT_SOURCE_DIR/plugins/file-contents-linux/file-contents-linux.lua')\nwidget = x.widget{\n    filename = '$myfile',\n    cb = function(myfile)\n        local s = assert(myfile:read('*all'))\n        f:write('update ' .. s:gsub('\\n', ';') .. '\\n')\n    end,\n    event = my_event_func,\n}\nwidget.plugin = ('$PT_BUILD_DIR/plugins/{}/plugin-{}.so'):gsub('{}', widget.plugin)\nassert(widget.event == my_event_func)\n__EOF__\n\npt_spawn_luastatus\nexec {pfd}<\"$main_fifo_file\"\npt_expect_line 'init' <&$pfd\npt_expect_line 'update ' <&$pfd\n\necho hello > \"$myfile\"\npt_read_line <&$pfd\npt_expect_line 'update hello;' <&$pfd\n\necho bye >> \"$myfile\"\npt_read_line <&$pfd\npt_expect_line 'update hello;bye;' <&$pfd\n\necho -n nonl > \"$myfile\"\npt_read_line <&$pfd\npt_expect_line 'update nonl' <&$pfd\n\npt_close_fd \"$pfd\"\npt_testcase_end\n"
  },
  {
    "path": "tests/pt_tests/plugin-fs/00-common.lib.bash",
    "content": "main_fifo_file=./tmp-fifo-main\nwakeup_fifo_file=./tmp-fifo-wakeup\n\npreface='\nlocal function _validate_t(t, ks)\n    local function _assert_for_k(cond, k)\n        if not cond then\n            error(string.format(\"assertion failed for entry with key <%s> (see the stacktrace)\", k))\n        end\n    end\n    local function _validate_num_for_k(n, k)\n        _assert_for_k(type(n) == \"number\", k)\n        _assert_for_k(n >= 0, k)\n        _assert_for_k(n ~= math.huge, k)\n    end\n    for _, k in ipairs(ks) do\n        local x = t[k]\n        _assert_for_k(x ~= nil, k)\n        _validate_num_for_k(x.total, k)\n        _validate_num_for_k(x.free, k)\n        _validate_num_for_k(x.avail, k)\n        _assert_for_k(x.free <= x.total, k)\n        _assert_for_k(x.avail <= x.total, k)\n        t[k] = nil\n    end\n    local k = next(t)\n    if k ~= nil then\n        error(string.format(\"unexpected entry with key <%s>\", k))\n    end\nend\n'\n"
  },
  {
    "path": "tests/pt_tests/plugin-fs/01-default.lib.bash",
    "content": "pt_testcase_begin\npt_add_fifo \"$main_fifo_file\"\npt_write_widget_file <<__EOF__\nf = assert(io.open('$main_fifo_file', 'w'))\nf:setvbuf('line')\nf:write('init\\n')\n$preface\nwidget = {\n    plugin = '$PT_BUILD_DIR/plugins/fs/plugin-fs.so',\n    cb = function(t)\n        _validate_t(t, {})\n        f:write('cb\\n')\n    end,\n}\n__EOF__\npt_spawn_luastatus\nexec {pfd}<\"$main_fifo_file\"\npt_expect_line 'init' <&$pfd\npt_expect_line 'cb' <&$pfd\npt_close_fd \"$pfd\"\npt_testcase_end\n"
  },
  {
    "path": "tests/pt_tests/plugin-fs/02-root.lib.bash",
    "content": "pt_testcase_begin\nusing_measure\npt_add_fifo \"$main_fifo_file\"\npt_write_widget_file <<__EOF__\nf = assert(io.open('$main_fifo_file', 'w'))\nf:setvbuf('line')\nf:write('init\\n')\n$preface\nwidget = {\n    plugin = '$PT_BUILD_DIR/plugins/fs/plugin-fs.so',\n    opts = {\n        paths = {'/'},\n        period = 0.25,\n    },\n    cb = function(t)\n        _validate_t(t, {'/'})\n        f:write('cb ok\\n')\n    end,\n}\n__EOF__\npt_spawn_luastatus\nexec {pfd}<\"$main_fifo_file\"\npt_expect_line 'init' <&$pfd\nmeasure_start\npt_expect_line 'cb ok' <&$pfd\nmeasure_check_ms 0\npt_expect_line 'cb ok' <&$pfd\nmeasure_check_ms 250\npt_expect_line 'cb ok' <&$pfd\nmeasure_check_ms 250\npt_close_fd \"$pfd\"\npt_testcase_end\n"
  },
  {
    "path": "tests/pt_tests/plugin-fs/03-glob-yesmatch.lib.bash",
    "content": "pt_require_tools mktemp\n\npt_testcase_begin\npt_add_fifo \"$main_fifo_file\"\nglobtest_dir=$(mktemp -d) || pt_fail \"'mktemp -d' failed\"\npt_add_dir_to_remove \"$globtest_dir\"\nfor f in havoc alligator za all cucumber; do\n    pt_check touch \"$globtest_dir/$f\"\n    pt_add_file_to_remove \"$globtest_dir/$f\"\ndone\npt_write_widget_file <<__EOF__\nf = assert(io.open('$main_fifo_file', 'w'))\nf:setvbuf('line')\nf:write('init\\n')\n$preface\nwidget = {\n    plugin = '$PT_BUILD_DIR/plugins/fs/plugin-fs.so',\n    opts = {\n        globs = {'$globtest_dir/*?a*'},\n    },\n    cb = function(t)\n        _validate_t(t, {'$globtest_dir/havoc', '$globtest_dir/alligator', '$globtest_dir/za'})\n        f:write('cb glob seems to work...\\n')\n    end,\n}\n__EOF__\npt_spawn_luastatus\nexec {pfd}<\"$main_fifo_file\"\npt_expect_line 'init' <&$pfd\npt_expect_line 'cb glob seems to work...' <&$pfd\npt_close_fd \"$pfd\"\npt_testcase_end\n"
  },
  {
    "path": "tests/pt_tests/plugin-fs/04-glob-nomatch.lib.bash",
    "content": "pt_testcase_begin\npt_add_fifo \"$main_fifo_file\"\npt_write_widget_file <<__EOF__\nf = assert(io.open('$main_fifo_file', 'w'))\nf:setvbuf('line')\nf:write('init\\n')\n$preface\nwidget = {\n    plugin = '$PT_BUILD_DIR/plugins/fs/plugin-fs.so',\n    opts = {\n        globs = {'$globtest_dir/foo/bar/*', '$globtest_dir/*z*', '$globtest_dir/*'},\n    },\n    cb = function(t)\n        _validate_t(t, {})\n        f:write('fine\\n')\n    end,\n}\n__EOF__\npt_spawn_luastatus\nexec {pfd}<\"$main_fifo_file\"\npt_expect_line 'init' <&$pfd\npt_expect_line 'fine' <&$pfd\npt_close_fd \"$pfd\"\npt_testcase_end\n"
  },
  {
    "path": "tests/pt_tests/plugin-fs/05-bad-path.lib.bash",
    "content": "pt_testcase_begin\npt_add_fifo \"$main_fifo_file\"\npt_write_widget_file <<__EOF__\nf = assert(io.open('$main_fifo_file', 'w'))\nf:setvbuf('line')\nf:write('init\\n')\n$preface\nwidget = {\n    plugin = '$PT_BUILD_DIR/plugins/fs/plugin-fs.so',\n    opts = {\n        paths = {'$globtest_dir/foo/bar'},\n    },\n    cb = function(t)\n        _validate_t(t, {})\n        f:write('good\\n')\n    end,\n}\n__EOF__\npt_spawn_luastatus\nexec {pfd}<\"$main_fifo_file\"\npt_expect_line 'init' <&$pfd\npt_expect_line 'good' <&$pfd\npt_close_fd \"$pfd\"\npt_testcase_end\n"
  },
  {
    "path": "tests/pt_tests/plugin-fs/06-wakeup-fifo.lib.bash",
    "content": "pt_testcase_begin\nusing_measure\npt_add_fifo \"$main_fifo_file\"\npt_add_fifo \"$wakeup_fifo_file\"\npt_write_widget_file <<__EOF__\nf = assert(io.open('$main_fifo_file', 'w'))\nf:setvbuf('line')\nf:write('init\\n')\n$preface\nwidget = {\n    plugin = '$PT_BUILD_DIR/plugins/fs/plugin-fs.so',\n    opts = {\n        paths = {'/'},\n        period = 0.5,\n        fifo = '$wakeup_fifo_file',\n    },\n    cb = function(t)\n        _validate_t(t, {'/'})\n        f:write('cb called\\n')\n    end,\n}\n__EOF__\npt_spawn_luastatus\nexec {pfd}<\"$main_fifo_file\"\npt_expect_line 'init' <&$pfd\nmeasure_start\npt_expect_line 'cb called' <&$pfd\nmeasure_check_ms 0\npt_expect_line 'cb called' <&$pfd\nmeasure_check_ms 500\npt_expect_line 'cb called' <&$pfd\nmeasure_check_ms 500\ntouch \"$wakeup_fifo_file\"\npt_expect_line 'cb called' <&$pfd\nmeasure_check_ms 0\npt_expect_line 'cb called' <&$pfd\nmeasure_check_ms 500\npt_close_fd \"$pfd\"\npt_testcase_end\n"
  },
  {
    "path": "tests/pt_tests/plugin-fs/07-dyn-paths.lib.bash",
    "content": "pt_require_tools mktemp\n\npt_testcase_begin\npt_add_fifo \"$main_fifo_file\"\nglobtest_dir=$(mktemp -d) || pt_fail \"'mktemp -d' failed\"\npt_add_dir_to_remove \"$globtest_dir\"\nfor f in 1_one 2_two 3_three; do\n    pt_check touch \"$globtest_dir/$f\"\n    pt_add_file_to_remove \"$globtest_dir/$f\"\ndone\npt_write_widget_file <<__EOF__\nf = assert(io.open('$main_fifo_file', 'w'))\nf:setvbuf('line')\nf:write('init\\n')\n$preface\n\nlocal i = 1\nlocal function check_eq(s1, s2)\n    if s1 ~= s2 then\n        error(string.format('check_eq: \"%s\" ~= \"%s\"', s1, s2))\n    end\nend\nlocal function add(k, expected_result)\n    local path = '$globtest_dir/' .. k\n    local result = luastatus.plugin.add_dyn_path(path)\n    check_eq(result and 'true' or 'false', expected_result)\nend\nlocal function remove(k, expected_result)\n    local path = '$globtest_dir/' .. k\n    local result = luastatus.plugin.remove_dyn_path(path)\n    check_eq(result and 'true' or 'false', expected_result)\nend\nlocal function check_keys(keys)\n\n    if i == 1 then\n        check_eq(keys, '')\n        add('1_one', 'true')\n        add('1_one', 'false')\n        return true\n    end\n\n    if i == 2 then\n        check_eq(keys, '1_one')\n        add('2_two', 'true')\n        add('2_two', 'false')\n        return true\n    end\n    if i == 3 then\n        check_eq(keys, '1_one 2_two')\n        add('3_three', 'true')\n        add('3_three', 'false')\n        return true\n    end\n    if i == 4 then\n        check_eq(keys, '1_one 2_two 3_three')\n        remove('1_one', 'true')\n        remove('1_one', 'false')\n        return true\n    end\n    if i == 5 then\n        check_eq(keys, '2_two 3_three')\n        remove('2_two', 'true')\n        remove('2_two', 'false')\n        return true\n    end\n    if i == 6 then\n        check_eq(keys, '3_three')\n        remove('3_three', 'true')\n        remove('3_three', 'false')\n        return true\n    end\n    if i == 7 then\n        check_eq(keys, '')\n        return true\n    end\n    if i == 8 then\n        local max = luastatus.plugin.get_max_dyn_paths()\n        assert(max == 2147483647)\n        return true\n    end\n\n    return false\nend\nlocal function basename(s)\n    return assert(s:match('[^/]+$'))\nend\n\nwidget = {\n    plugin = '$PT_BUILD_DIR/plugins/fs/plugin-fs.so',\n    opts = {\n        enable_dyn_paths = true,\n        period = 0.1,\n    },\n    cb = function(t)\n        local keys_tbl = {}\n        for k, _ in pairs(t) do\n            table.insert(keys_tbl, basename(k))\n        end\n        table.sort(keys_tbl)\n        local keys = table.concat(keys_tbl, ' ')\n        if not check_keys(keys) then\n            return\n        end\n        f:write(string.format('ok %d\\n', i))\n        i = i + 1\n    end,\n}\n__EOF__\npt_spawn_luastatus\nexec {pfd}<\"$main_fifo_file\"\npt_expect_line 'init' <&$pfd\n\nfor (( i = 1; i <= 8; ++i )); do\n    pt_expect_line \"ok $i\" <&$pfd\ndone\n\npt_close_fd \"$pfd\"\npt_testcase_end\n"
  },
  {
    "path": "tests/pt_tests/plugin-imap/00-common.lib.bash",
    "content": "main_fifo_file=./tmp-fifo-main\n\npt_find_free_tcp_port\nport=$PT_FOUND_FREE_PORT\n\nfakeimap_spawn() {\n    pt_spawn_thing_pipe imap_parrot \"$PT_PARROT\" --reuseaddr --print-line-when-ready TCP-SERVER \"$port\"\n    pt_expect_line 'parrot: ready' <&${PT_SPAWNED_THINGS_FDS_0[imap_parrot]}\n}\n\nfakeimap_spawn_check_accept_time_ms() {\n    pt_spawn_thing_pipe imap_parrot \"$PT_PARROT\" --reuseaddr --print-line-when-ready --print-line-on-accept TCP-SERVER \"$port\"\n    pt_expect_line 'parrot: ready' <&${PT_SPAWNED_THINGS_FDS_0[imap_parrot]}\n    measure_start\n    pt_expect_line 'parrot: accepted' <&${PT_SPAWNED_THINGS_FDS_0[imap_parrot]}\n    measure_check_ms \"$1\"\n}\n\nfakeimap_read_line() {\n    pt_read_line <&${PT_SPAWNED_THINGS_FDS_0[imap_parrot]}\n    if [[ $PT_LINE != *$'\\r' ]]; then\n        pt_fail \"fakeimap_read_line: line does not end with CRLF.\" \"Line: '$PT_LINE'\"\n    fi\n    PT_LINE=${PT_LINE%$'\\r'}\n}\n\nfakeimap_expect() {\n    fakeimap_read_line\n    if [[ \"$PT_LINE\" != \"$1\" ]]; then\n        pt_fail \"fakeimap_expect: line does not match\" \"Expected: '$1'\" \"Found: '$PT_LINE'\"\n    fi\n}\n\nfakeimap_cmd_expect() {\n    fakeimap_read_line\n    if [[ \"$PT_LINE\" != *' '* ]]; then\n        pt_fail \"fakeimap_cmd_expect: expected command, found line without a space:\" \"'$PT_LINE'\"\n    fi\n    fakeimap_prefix=${PT_LINE%%' '*}\n    if [[ -z \"$fakeimap_prefix\" ]]; then\n        pt_fail \"fakeimap_cmd_expect: expected command, found line string with a space:\" \"'$PT_LINE'\"\n    fi\n    local rest=${PT_LINE#*' '}\n    if [[ \"$rest\" != \"$1\" ]]; then\n        pt_fail \"fakeimap_cmd_expect: line does not match\" \"Expected: '$1'\" \"Found: '$rest'\"\n    fi\n    echo >&2 \"[imap] Command prefix: '$fakeimap_prefix'.\"\n}\n\nfakeimap_cmd_done() {\n    fakeimap_say \"$fakeimap_prefix $1\"\n}\n\nfakeimap_say() {\n    printf '%s\\r\\n' \"$1\" >&${PT_SPAWNED_THINGS_FDS_1[imap_parrot]}\n}\n\nfakeimap_kill() {\n    pt_kill_thing imap_parrot\n}\n\nfakeimap_wait() {\n    pt_wait_thing imap_parrot || true\n}\n\nimap_interact_begin() {\n    fakeimap_say \"* OK IMAP4rev1 Service Ready\"\n\n    fakeimap_cmd_expect \"LOGIN mylogin mypassword\"\n    fakeimap_cmd_done \"OK LOGIN completed\"\n\n    fakeimap_cmd_expect \"SELECT Inbox\"\n    fakeimap_say \"* 18 EXISTS\"\n    fakeimap_say \"* OK [UNSEEN 17] Message 17 is the first unseen message\"\n    fakeimap_cmd_done \"OK [READ-WRITE] SELECT completed\"\n}\n\nimap_interact_middle_simple() {\n    local pfd=$1\n    local i\n    for (( i = 17; i < 20; ++i )); do\n        fakeimap_cmd_expect \"STATUS Inbox (UNSEEN)\"\n\n        # outlook.com sends the '* STATUS' line with trailing whitespace.\n        # See https://github.com/shdown/luastatus/issues/64\n        fakeimap_say \"* STATUS Inbox (UNSEEN $i) \"\n        fakeimap_cmd_done \"OK [READ-WRITE] SELECT completed\"\n\n        fakeimap_cmd_expect \"IDLE\"\n        pt_expect_line \"cb $i\" <&$pfd\n\n        fakeimap_say \"* SOMETHING\"\n        fakeimap_expect \"DONE\"\n        fakeimap_cmd_done \"OK IDLE completed\"\n    done\n}\n\nimap_interact_middle_idle() {\n    local pfd=$1\n\n    fakeimap_cmd_expect \"STATUS Inbox (UNSEEN)\"\n    fakeimap_say \"* STATUS Inbox (UNSEEN 135)\"\n    fakeimap_cmd_done \"OK [READ-WRITE] SELECT completed\"\n    pt_expect_line \"cb 135\" <&$pfd\n\n    fakeimap_cmd_expect \"IDLE\"\n}\n"
  },
  {
    "path": "tests/pt_tests/plugin-imap/01-simple.lib.bash",
    "content": "pt_testcase_begin\n\nfakeimap_spawn\n\npt_add_fifo \"$main_fifo_file\"\npt_write_widget_file <<__EOF__\nf = assert(io.open('$main_fifo_file', 'w'))\nf:setvbuf('line')\nf:write('init\\n')\nx = dofile('$PT_SOURCE_DIR/plugins/imap/imap.lua')\nwidget = x.widget{\n    host = 'localhost',\n    port = $port,\n    login = 'mylogin',\n    password = 'mypassword',\n    mailbox = 'Inbox',\n    error_sleep_period = 1,\n    cb = function(n)\n        if n == nil then\n            f:write('cb nil\\n')\n        else\n            f:write(string.format('cb %d\\n', n))\n        end\n    end,\n    event = my_event_func,\n}\nwidget.plugin = ('$PT_BUILD_DIR/plugins/{}/plugin-{}.so'):gsub('{}', widget.plugin)\nassert(widget.event == my_event_func)\n__EOF__\npt_spawn_luastatus\nexec {pfd}<\"$main_fifo_file\"\npt_expect_line 'init' <&$pfd\npt_expect_line 'cb nil' <&$pfd\n\nimap_interact_begin\nimap_interact_middle_simple \"$pfd\"\n\nfakeimap_kill\npt_expect_line 'cb nil' <&$pfd\n\npt_testcase_end\n"
  },
  {
    "path": "tests/pt_tests/plugin-imap/02-retry-timeout.lib.bash",
    "content": "pt_testcase_begin\nusing_measure\n\nfakeimap_spawn\n\npt_add_fifo \"$main_fifo_file\"\npt_write_widget_file <<__EOF__\nf = assert(io.open('$main_fifo_file', 'w'))\nf:setvbuf('line')\nf:write('init\\n')\nx = dofile('$PT_SOURCE_DIR/plugins/imap/imap.lua')\nwidget = x.widget{\n    host = 'localhost',\n    port = $port,\n    login = 'mylogin',\n    password = 'mypassword',\n    mailbox = 'Inbox',\n    error_sleep_period = 1,\n    cb = function(n)\n        if n == nil then\n            f:write('cb nil\\n')\n        else\n            f:write(string.format('cb %d\\n', n))\n        end\n    end,\n    event = my_event_func,\n}\nwidget.plugin = ('$PT_BUILD_DIR/plugins/{}/plugin-{}.so'):gsub('{}', widget.plugin)\nassert(widget.event == my_event_func)\n__EOF__\npt_spawn_luastatus\nexec {pfd}<\"$main_fifo_file\"\npt_expect_line 'init' <&$pfd\npt_expect_line 'cb nil' <&$pfd\n\nfor (( i = 0; i < 3; ++i )); do\n    imap_interact_begin\n    imap_interact_middle_simple \"$pfd\"\n\n    fakeimap_kill\n    pt_expect_line 'cb nil' <&$pfd\n    fakeimap_spawn_check_accept_time_ms 1000\ndone\n\nfakeimap_kill\npt_expect_line 'cb nil' <&$pfd\n\npt_testcase_end\n"
  },
  {
    "path": "tests/pt_tests/plugin-imap/03-idle-timeout.lib.bash",
    "content": "pt_testcase_begin\nusing_measure\n\nfakeimap_spawn\n\npt_add_fifo \"$main_fifo_file\"\npt_write_widget_file <<__EOF__\nf = assert(io.open('$main_fifo_file', 'w'))\nf:setvbuf('line')\nf:write('init\\n')\nx = dofile('$PT_SOURCE_DIR/plugins/imap/imap.lua')\nwidget = x.widget{\n    host = 'localhost',\n    port = $port,\n    login = 'mylogin',\n    password = 'mypassword',\n    mailbox = 'Inbox',\n    error_sleep_period = 1,\n    timeout = 1.5,\n    cb = function(n)\n        if n == nil then\n            f:write('cb nil\\n')\n        else\n            f:write(string.format('cb %d\\n', n))\n        end\n    end,\n    event = my_event_func,\n}\nwidget.plugin = ('$PT_BUILD_DIR/plugins/{}/plugin-{}.so'):gsub('{}', widget.plugin)\nassert(widget.event == my_event_func)\n__EOF__\npt_spawn_luastatus\nexec {pfd}<\"$main_fifo_file\"\npt_expect_line 'init' <&$pfd\npt_expect_line 'cb nil' <&$pfd\n\nfor (( i = 0; i < 3; ++i )); do\n    imap_interact_begin\n    imap_interact_middle_idle \"$pfd\"\n\n    measure_start\n    fakeimap_wait\n    measure_check_ms 1500\n\n    pt_expect_line 'cb nil' <&$pfd\n\n    fakeimap_spawn_check_accept_time_ms 1000\ndone\n\nfakeimap_kill\npt_expect_line 'cb nil' <&$pfd\n\npt_testcase_end\n"
  },
  {
    "path": "tests/pt_tests/plugin-inotify/00-common.lib.bash",
    "content": "main_fifo_file=./tmp-fifo-main\n\npreface='\nlocal function _fmt_mask(m)\n    local ks = {}\n    for k, _ in pairs(m) do\n        ks[#ks + 1] = k\n    end\n    table.sort(ks)\n    return table.concat(ks, \",\")\nend\n'\n"
  },
  {
    "path": "tests/pt_tests/plugin-inotify/01-simple.lib.bash",
    "content": "pt_require_tools mktemp\n\npt_testcase_begin\npt_add_fifo \"$main_fifo_file\"\nmyfile=$(mktemp) || pt_fail 'mktemp failed'\npt_add_file_to_remove \"$myfile\"\npt_write_widget_file <<__EOF__\nf = assert(io.open('$main_fifo_file', 'w'))\nf:setvbuf('line')\nf:write('init\\n')\n$preface\nwidget = {\n    plugin = '$PT_BUILD_DIR/plugins/inotify/plugin-inotify.so',\n    opts = {\n        watch = {['$myfile'] = {'close_write', 'delete_self'}},\n    },\n    cb = function(t)\n        assert(t.what == 'event', 'unexpected t.what')\n        f:write('cb event mask=' .. _fmt_mask(t.mask) .. '\\n')\n    end,\n}\n__EOF__\npt_spawn_luastatus\nexec {pfd}<\"$main_fifo_file\"\npt_expect_line 'init' <&$pfd\n\n# Try to avoid race condition: we may modify the file before the watch is set up.\nsleep 1\n\necho hello >> \"$myfile\" || pt_fail 'cannot append to myfile'\npt_expect_line 'cb event mask=close_write' <&$pfd\nrm -f \"$myfile\" || pt_fail 'cannot remove myfile'\npt_expect_line 'cb event mask=delete_self' <&$pfd\npt_expect_line 'cb event mask=ignored' <&$pfd\npt_close_fd \"$pfd\"\npt_testcase_end\n"
  },
  {
    "path": "tests/pt_tests/plugin-inotify/02-lfunc-get-initial-wds.lib.bash",
    "content": "pt_require_tools mktemp\n\npt_testcase_begin\npt_add_fifo \"$main_fifo_file\"\nmyfile1=$(mktemp) || pt_fail 'mktemp failed'\nmyfile2=$(mktemp) || pt_fail 'mktemp failed'\npt_add_file_to_remove \"$myfile1\"\npt_add_file_to_remove \"$myfile2\"\npt_write_widget_file <<__EOF__\nf = assert(io.open('$main_fifo_file', 'w'))\nf:setvbuf('line')\nf:write('init\\n')\n$preface\nwidget = {\n    plugin = '$PT_BUILD_DIR/plugins/inotify/plugin-inotify.so',\n    opts = {\n        watch = {\n            ['$myfile1'] = {'close_write', 'delete_self'},\n            ['$myfile2'] = {'delete_self'},\n        },\n        greet = true,\n    },\n    cb = function(t)\n        if t.what == 'hello' then\n            f:write('cb hello\\n')\n        else\n            assert(t.what == 'event', 'unexpected t.what')\n            assert(type(t.wd) == 'number', 't.wd is not a number')\n            local file_ids = {\n                ['$myfile1'] = '1',\n                ['$myfile2'] = '2',\n            }\n            local wds = luastatus.plugin.get_initial_wds()\n            local file_id\n            for k, v in pairs(wds) do\n                local id = file_ids[k]\n                assert(id ~= nil, string.format('unexpected key in initial wds: \"%s\"', k))\n                if v == t.wd then\n                    file_id = id\n                end\n            end\n            assert(file_id ~= nil, 't.wd is not present in initial wds')\n            f:write('cb event file' .. file_id .. ' mask=' .. _fmt_mask(t.mask) .. '\\n')\n        end\n    end,\n}\n__EOF__\npt_spawn_luastatus\nexec {pfd}<\"$main_fifo_file\"\npt_expect_line 'init' <&$pfd\npt_expect_line 'cb hello' <&$pfd\necho hello >> \"$myfile2\"\necho hello >> \"$myfile1\"\npt_expect_line 'cb event file1 mask=close_write' <&$pfd\nrm -f \"$myfile2\"\npt_expect_line 'cb event file2 mask=delete_self' <&$pfd\npt_expect_line 'cb event file2 mask=ignored' <&$pfd\nrm -f \"$myfile1\"\npt_expect_line 'cb event file1 mask=delete_self' <&$pfd\npt_expect_line 'cb event file1 mask=ignored' <&$pfd\npt_close_fd \"$pfd\"\npt_testcase_end\n"
  },
  {
    "path": "tests/pt_tests/plugin-inotify/03-lfunc-add-watch.lib.bash",
    "content": "pt_require_tools mktemp\n\npt_testcase_begin\npt_add_fifo \"$main_fifo_file\"\nmyfile=$(mktemp) || pt_fail 'mktemp failed'\npt_add_file_to_remove \"$myfile\"\npt_write_widget_file <<__EOF__\nf = assert(io.open('$main_fifo_file', 'w'))\nf:setvbuf('line')\nf:write('init\\n')\n$preface\nwidget = {\n    plugin = '$PT_BUILD_DIR/plugins/inotify/plugin-inotify.so',\n    opts = {\n        watch = {},\n        greet = true,\n    },\n    cb = function(t)\n        local line\n        if t.what == 'hello' then\n            line = 'cb hello'\n        else\n            assert(t.what == 'event')\n            line = 'cb event mask=' .. _fmt_mask(t.mask)\n        end\n        local wd = luastatus.plugin.add_watch('$myfile', {'close_write', 'delete_self', 'oneshot'})\n        if wd then\n            f:write(line .. ' (add_watch: ok)\\n')\n        else\n            f:write(line .. ' (add_watch: error)\\n')\n        end\n    end,\n}\n__EOF__\npt_spawn_luastatus\nexec {pfd}<\"$main_fifo_file\"\npt_expect_line 'init' <&$pfd\npt_expect_line 'cb hello (add_watch: ok)' <&$pfd\nfor (( i = 0; i < 5; ++i )); do\n    echo hello >> \"$myfile\"\n    pt_expect_line 'cb event mask=close_write (add_watch: ok)' <&$pfd\n    pt_expect_line 'cb event mask=ignored (add_watch: ok)' <&$pfd\ndone\nrm -f \"$myfile\"\npt_expect_line 'cb event mask=delete_self (add_watch: error)' <&$pfd\npt_expect_line 'cb event mask=ignored (add_watch: error)' <&$pfd\npt_close_fd \"$pfd\"\npt_testcase_end\n"
  },
  {
    "path": "tests/pt_tests/plugin-inotify/04-lfunuc-rm-watch.lib.bash",
    "content": "pt_require_tools mktemp\n\npt_testcase_begin\npt_add_fifo \"$main_fifo_file\"\nmyfile=$(mktemp) || pt_fail 'mktemp failed'\npt_add_file_to_remove \"$myfile\"\npt_write_widget_file <<__EOF__\nf = assert(io.open('$main_fifo_file', 'w'))\nf:setvbuf('line')\nf:write('init\\n')\n$preface\nwd = nil\nwidget = {\n    plugin = '$PT_BUILD_DIR/plugins/inotify/plugin-inotify.so',\n    opts = {\n        watch = {},\n        greet = true,\n        timeout = 0.25,\n    },\n    cb = function(t)\n        local initial_wds = luastatus.plugin.get_initial_wds()\n        assert(next(initial_wds) == nil, 'get_initial_wds() result is not empty')\n        if t.what == 'hello' then\n            wd = luastatus.plugin.add_watch('$myfile', {'close_write'})\n            assert(wd, 'add_watch() failed')\n            f:write('cb hello\\n')\n        elseif t.what == 'event' then\n            assert(wd ~= nil, 'global \"wd\" is nil')\n            local line = 'cb event mask=' .. _fmt_mask(t.mask)\n            local is_ok = luastatus.plugin.remove_watch(wd)\n            if is_ok then\n                f:write(line .. ' (remove_watch: ok)\\n')\n            else\n                f:write(line .. ' (remove_watch: error)\\n')\n            end\n        else\n            f:write('cb ' .. t.what .. '\\n')\n        end\n    end,\n}\n__EOF__\npt_spawn_luastatus\nexec {pfd}<\"$main_fifo_file\"\npt_expect_line 'init' <&$pfd\npt_expect_line 'cb hello' <&$pfd\npt_expect_line 'cb timeout' <&$pfd\necho hello >> \"$myfile\"\npt_expect_line 'cb event mask=close_write (remove_watch: ok)' <&$pfd\npt_expect_line 'cb event mask=ignored (remove_watch: error)' <&$pfd\necho hello >> \"$myfile\"\npt_expect_line 'cb timeout' <&$pfd\necho hello >> \"$myfile\"\npt_expect_line 'cb timeout' <&$pfd\npt_close_fd \"$pfd\"\npt_testcase_end\n"
  },
  {
    "path": "tests/pt_tests/plugin-inotify/05-timeout.lib.bash",
    "content": "pt_require_tools mktemp\n\npt_testcase_begin\nusing_measure\n\npt_add_fifo \"$main_fifo_file\"\nmyfile=$(mktemp) || pt_fail 'mktemp failed'\npt_add_file_to_remove \"$myfile\"\npt_write_widget_file <<__EOF__\nf = assert(io.open('$main_fifo_file', 'w'))\nf:setvbuf('line')\nf:write('init\\n')\n$preface\nwidget = {\n    plugin = '$PT_BUILD_DIR/plugins/inotify/plugin-inotify.so',\n    opts = {\n        watch = {['$myfile'] = {'close_write', 'delete_self'}},\n        timeout = 0.3,\n        greet = true,\n    },\n    cb = function(t)\n        if t.what == 'event' then\n            f:write('cb event mask=' .. _fmt_mask(t.mask) .. '\\n')\n        else\n            f:write('cb ' .. t.what .. '\\n')\n        end\n    end,\n}\n__EOF__\npt_spawn_luastatus\nexec {pfd}<\"$main_fifo_file\"\n\npt_expect_line 'init' <&$pfd\npt_expect_line 'cb hello' <&$pfd\n\nmeasure_start\npt_expect_line 'cb timeout' <&$pfd\nmeasure_check_ms 300\npt_expect_line 'cb timeout' <&$pfd\nmeasure_check_ms 300\n\necho hello >> \"$myfile\"\npt_expect_line 'cb event mask=close_write' <&$pfd\nmeasure_check_ms 0\n\npt_expect_line 'cb timeout' <&$pfd\nmeasure_check_ms 300\n\necho hello >> \"$myfile\"\npt_expect_line 'cb event mask=close_write' <&$pfd\nmeasure_check_ms 0\n\nrm -f \"$myfile\"\npt_expect_line 'cb event mask=delete_self' <&$pfd\npt_expect_line 'cb event mask=ignored' <&$pfd\nmeasure_check_ms 0\n\npt_close_fd \"$pfd\"\npt_testcase_end\n"
  },
  {
    "path": "tests/pt_tests/plugin-inotify/06-dir.lib.bash",
    "content": "pt_require_tools mktemp\n\nstage_dir=$(mktemp -d) || pt_fail \"'mktemp -d' failed\"\nstage_subdir=$stage_dir/subdir\nactor_file_1=$stage_dir/foo1\nactor_file_2=$stage_dir/foo2\n\npt_testcase_begin\npt_add_fifo \"$main_fifo_file\"\npt_add_file_to_remove \"$actor_file_1\"\npt_add_file_to_remove \"$actor_file_2\"\npt_write_widget_file <<__EOF__\nf = assert(io.open('$main_fifo_file', 'w'))\nf:setvbuf('line')\nf:write('init\\n')\n$preface\n_cookies = {}\n_cookies_cnt = 0\nfunction _fmt_cookie(c)\n    assert(type(c) == 'number', 'cookie is not a number')\n    if c == 0 then\n        return '(none)'\n    end\n    local r = _cookies[c]\n    if r == nil then\n        _cookies_cnt = _cookies_cnt + 1\n        r = string.format('(cookie #%d)', _cookies_cnt)\n        _cookies[c] = r\n    end\n    return r\nend\nwidget = {\n    plugin = '$PT_BUILD_DIR/plugins/inotify/plugin-inotify.so',\n    opts = {\n        watch = {['$stage_dir'] = {'close_write', 'delete', 'moved_from', 'moved_to'}},\n        greet = true,\n    },\n    cb = function(t)\n        if t.what == 'hello' then\n            f:write('cb hello\\n')\n        else\n            assert(t.what == 'event', 'unexpected t.what')\n            f:write(string.format('cb event mask=%s cookie=%s name=%s\\n', _fmt_mask(t.mask), _fmt_cookie(t.cookie), t.name or '(nil)'))\n        end\n    end,\n}\n__EOF__\npt_spawn_luastatus\nexec {pfd}<\"$main_fifo_file\"\n\npt_expect_line 'init' <&$pfd\npt_expect_line 'cb hello' <&$pfd\n\necho hello >> \"$actor_file_1\"\npt_expect_line 'cb event mask=close_write cookie=(none) name=foo1' <&$pfd\n\nmv -f \"$actor_file_1\" \"$actor_file_2\"\npt_expect_line 'cb event mask=moved_from cookie=(cookie #1) name=foo1' <&$pfd\npt_expect_line 'cb event mask=moved_to cookie=(cookie #1) name=foo2' <&$pfd\n\nrm -f \"$actor_file_2\"\npt_expect_line 'cb event mask=delete cookie=(none) name=foo2' <&$pfd\n\nmkdir \"$stage_subdir\"\nrmdir \"$stage_subdir\"\npt_expect_line 'cb event mask=delete,isdir cookie=(none) name=subdir' <&$pfd\n\nrmdir \"$stage_dir\"\npt_expect_line 'cb event mask=ignored cookie=(none) name=(nil)' <&$pfd\n\npt_close_fd \"$pfd\"\npt_testcase_end\n"
  },
  {
    "path": "tests/pt_tests/plugin-inotify/07-lfunc-push-timeout.lib.bash",
    "content": "pt_require_tools mktemp\n\npt_testcase_begin\nusing_measure\n\npt_add_fifo \"$main_fifo_file\"\nmyfile=$(mktemp) || pt_fail \"'mktemp -d' failed\"\npt_add_file_to_remove \"$myfile\"\npt_write_widget_file <<__EOF__\nf = assert(io.open('$main_fifo_file', 'w'))\nf:setvbuf('line')\nf:write('init\\n')\nn=0\nwidget = {\n    plugin = '$PT_BUILD_DIR/plugins/inotify/plugin-inotify.so',\n    opts = {\n        watch = {['$myfile'] = {'close_write'}},\n        greet = true,\n        timeout = 0.1,\n    },\n    cb = function(t)\n        if t.what == 'event' then\n            f:write('event\\n')\n        else\n            n=(n+1)%3\n            f:write(t.what .. ' n=' .. n .. '\\n')\n            if n == 0 then\n                luastatus.plugin.push_timeout(0.6)\n            end\n        end\n    end,\n}\n__EOF__\npt_spawn_luastatus\nexec {pfd}<\"$main_fifo_file\"\npt_expect_line 'init' <&$pfd\npt_expect_line 'hello n=1' <&$pfd\nmeasure_start\nfor (( i = 0; i < 4; ++i )); do\n    echo bye >> \"$myfile\"\n    pt_expect_line \"event\" <&$pfd\n    measure_check_ms 0\n    pt_expect_line 'timeout n=2' <&$pfd\n    measure_check_ms 100\n    pt_expect_line 'timeout n=0' <&$pfd\n    measure_check_ms 100\n    pt_expect_line 'timeout n=1' <&$pfd\n    measure_check_ms 600\ndone\npt_close_fd \"$pfd\"\npt_testcase_end\n"
  },
  {
    "path": "tests/pt_tests/plugin-inotify/08-lfunc-get-supported-events.lib.bash",
    "content": "pt_testcase_begin\npt_add_fifo \"$main_fifo_file\"\npt_write_widget_file <<__EOF__\nf = assert(io.open('$main_fifo_file', 'w'))\nf:setvbuf('line')\nf:write('init\\n')\nfunction check_events(e)\n    assert(type(e) == 'table')\n\n    -- These are supported by any glibc version.\n    assert(e.access == 'io')\n    assert(e.move == 'i')\n    assert(e.isdir == 'o')\n\n    -- Check sanity (all values are either \"i\", \"o\" or \"io\").\n    for k, v in pairs(e) do\n        print(string.format('Supported event: %s (%s)', k, v))\n        assert(v == 'i' or v == 'o' or v == 'io')\n    end\nend\nwidget = {\n    plugin = '$PT_BUILD_DIR/plugins/inotify/plugin-inotify.so',\n    opts = {\n        watch = {},\n        greet = true,\n    },\n    cb = function(t)\n        assert(t.what == 'hello')\n        check_events(luastatus.plugin.get_supported_events())\n        f:write('cb ok\\n')\n    end,\n}\n__EOF__\npt_spawn_luastatus\nexec {pfd}<\"$main_fifo_file\"\npt_expect_line 'init' <&$pfd\npt_expect_line 'cb ok' <&$pfd\npt_testcase_end\n"
  },
  {
    "path": "tests/pt_tests/plugin-inotify/09-access.lib.bash",
    "content": "pt_testcase_begin\nusing_measure\n\npt_add_fifo \"$main_fifo_file\"\n\npt_write_widget_file <<__EOF__\nf = assert(io.open('$main_fifo_file', 'w'))\nf:setvbuf('line')\nf:write('init\\n')\n\nfunction call_access(path)\n    local ok, err = luastatus.plugin.access(path)\n    if ok then\n        f:write('exists = true\\n')\n    else\n        if err then\n            f:write('error ' .. err .. '\\n')\n        else\n            f:write('exists = false\\n')\n        end\n    end\nend\n\nwidget = {\n    plugin = '$PT_BUILD_DIR/plugins/inotify/plugin-inotify.so',\n    opts = {\n        watch = {},\n        timeout = 1,\n        greet = true,\n    },\n    cb = function(t)\n        call_access('/')\n        call_access('/k4dsqflkr8ntrpwagmw7')\n    end,\n}\n__EOF__\npt_spawn_luastatus\nexec {pfd}<\"$main_fifo_file\"\n\npt_expect_line 'init' <&$pfd\npt_expect_line 'exists = true' <&$pfd\npt_expect_line 'exists = false' <&$pfd\n\npt_close_fd \"$pfd\"\npt_testcase_end\n"
  },
  {
    "path": "tests/pt_tests/plugin-is-program-running/00-common.lib.bash",
    "content": "pt_require_tools mktemp\n\nmain_fifo_file=./tmp-fifo-main\n\nis_program_running_testcase() {\n    local opts=${1?}; shift\n    local callback=${1?}; shift\n\n    pt_testcase_begin\n    pt_add_fifo \"$main_fifo_file\"\n\n    pt_write_widget_file <<__EOF__\nf = assert(io.open('$main_fifo_file', 'w'))\nf:setvbuf('line')\nf:write('init\\n')\nfunction my_event_func() end\nx = dofile('$PT_SOURCE_DIR/plugins/is-program-running/is-program-running.lua')\n\nfunction format_result_entry(x)\n    return string.format('%s=>%s', x[1], tostring(x[2]))\nend\n\nfunction format_result(t)\n    local chunks = {}\n    local arr = {}\n\n    if type(next(t)) == 'number' then\n        for i, v in ipairs(t) do\n            table.insert(arr, {i, v})\n        end\n        table.sort(arr, function(PQ_x, PQ_y)\n            return PQ_x[1] < PQ_y[1]\n        end)\n    else\n        for k, v in pairs(t) do\n            table.insert(arr, {k, v})\n        end\n    end\n\n    for _, x in ipairs(arr) do\n        table.insert(chunks, format_result_entry(x))\n    end\n    return table.concat(chunks, ' ')\nend\n\nwidget = x.widget{\n    timer_opts = {\n        period = 0.1,\n    },\n    cb = function(t)\n        if type(t) == 'table' then\n            f:write('cb ' .. format_result(t) .. '\\n')\n        else\n            f:write('cb ' .. tostring(t) .. '\\n')\n        end\n    end,\n    event = my_event_func,\n    $opts\n}\nwidget.plugin = ('$PT_BUILD_DIR/plugins/{}/plugin-{}.so'):gsub('{}', widget.plugin)\nassert(widget.event == my_event_func)\n__EOF__\n    pt_spawn_luastatus\n    exec {pfd}<\"$main_fifo_file\"\n    pt_expect_line 'init' <&$pfd\n\n    local i\n    for (( i = 0; i < $#; ++i )); do\n        pt_check $callback $i\n        local j=$(( i + 1 ))\n        pt_expect_line \"${!j}\" <&$pfd\n    done\n\n    pt_close_fd \"$pfd\"\n    pt_testcase_end\n}\n\nx_do_nothing() {\n    true\n}\n"
  },
  {
    "path": "tests/pt_tests/plugin-is-program-running/01-pidfile-devnull.lib.bash",
    "content": "is_program_running_testcase \\\n    'kind=\"pidfile\", path=\"/dev/null\"' \\\n    x_do_nothing \\\n    'cb false' \\\n    'cb false'\n"
  },
  {
    "path": "tests/pt_tests/plugin-is-program-running/02-pidfile.lib.bash",
    "content": "pidfile=$(mktemp) || pt_fail 'mktemp failed'\npt_add_file_to_remove \"$pidfile\"\n\nwrite_pidfile() {\n    printf '%s\\n' \"$1\" > \"$pidfile\" || pt_fail 'cannot write $pidfile'\n}\n\nx_my_callback() {\n    case \"$1\" in\n    0)\n        write_pidfile junk > \"$pidfile\"\n        ;;\n    1)\n        write_pidfile \"$$\" > \"$pidfile\"\n        ;;\n    2)\n        write_pidfile 0 > \"$pidfile\"\n        ;;\n    3)\n        write_pidfile 1 > \"$pidfile\"\n        ;;\n    4)\n        write_pidfile -1 > \"$pidfile\"\n        ;;\n    5)\n        true > \"$pidfile\" || pt_fail 'cannot write $pidfile'\n        ;;\n    esac\n}\n\nis_program_running_testcase \\\n    \"kind='pidfile', path='$pidfile'\" \\\n    x_my_callback \\\n    'cb false' \\\n    'cb true' \\\n    'cb false' \\\n    'cb true' \\\n    'cb false' \\\n    'cb false'\n"
  },
  {
    "path": "tests/pt_tests/plugin-is-program-running/03-pidfile-fileerr.lib.bash",
    "content": "is_program_running_testcase \\\n    \"kind='pidfile', path='/634Zb012x1g7f5ages2snA1stxdlpr'\" \\\n    x_do_nothing \\\n    'cb false' \\\n    'cb false'\n"
  },
  {
    "path": "tests/pt_tests/plugin-is-program-running/04-fileexists.lib.bash",
    "content": "is_program_running_testcase \\\n    'kind=\"file_exists\", path=\"/\"' \\\n    x_do_nothing \\\n    'cb true'\n\ntmpfile=$(mktemp) || pt_fail 'mktemp failed'\npt_add_file_to_remove \"$tmpfile\"\n\nx_my_callback_del_tmpfile() {\n    if (( $1 == 1 )); then\n        pt_check rm -- \"$tmpfile\"\n    fi\n}\n\nis_program_running_testcase \\\n    \"kind='file_exists', path='$tmpfile'\" \\\n    x_my_callback_del_tmpfile \\\n    'cb true' \\\n    'cb false' \\\n"
  },
  {
    "path": "tests/pt_tests/plugin-is-program-running/05-dirnonempty-root.lib.bash",
    "content": "is_program_running_testcase \\\n    'kind=\"dir_nonempty\", path=\"/\"' \\\n    x_do_nothing \\\n    'cb true'\n"
  },
  {
    "path": "tests/pt_tests/plugin-is-program-running/06-dirnonempty-normal-files.lib.bash",
    "content": "tmpdir=$(mktemp -d) || pt_fail 'mktemp -d failed'\npt_add_file_to_remove \"$tmpdir\"/.hidden\npt_add_file_to_remove \"$tmpdir\"/normal\npt_add_dir_to_remove \"$tmpdir\"\n\nx_my_callback_dirnonempty() {\n    case \"$1\" in\n    0)\n        ;;\n    1)\n        pt_check touch \"$tmpdir\"/.hidden\n        ;;\n    2)\n        pt_check touch \"$tmpdir\"/normal\n        ;;\n    3)\n        pt_check rm -- \"$tmpdir\"/normal\n        ;;\n    esac\n}\n\nis_program_running_testcase \\\n    \"kind='dir_nonempty', path='$tmpdir'\" \\\n    x_my_callback_dirnonempty \\\n    'cb false' \\\n    'cb false' \\\n    'cb true' \\\n    'cb false'\n"
  },
  {
    "path": "tests/pt_tests/plugin-is-program-running/07-dirnonempty-nonexisting.lib.bash",
    "content": "nonexisting_dir=/tzmicdb8kf38onA67olhZiyd79xml3\n\n# Without trailing '/'\nis_program_running_testcase \\\n    \"kind='dir_nonempty', path='${nonexisting_dir}'\" \\\n    x_do_nothing \\\n    'cb false'\n\n# With trailing '/'\nis_program_running_testcase \\\n    \"kind='dir_nonempty', path='${nonexisting_dir}/'\" \\\n    x_do_nothing \\\n    'cb false'\n"
  },
  {
    "path": "tests/pt_tests/plugin-is-program-running/08-dirnonempty-with-hidden.lib.bash",
    "content": "tmpdir=$(mktemp -d) || pt_fail 'mktemp -d failed'\npt_add_file_to_remove \"$tmpdir\"/.hidden\npt_add_file_to_remove \"$tmpdir\"/normal\npt_add_dir_to_remove \"$tmpdir\"\n\nx_my_callback_dirnonempty_with_hidden() {\n    case \"$1\" in\n    0)\n        ;;\n    1)\n        pt_check touch \"$tmpdir\"/.hidden\n        ;;\n    2)\n        pt_check touch \"$tmpdir\"/normal\n        ;;\n    3)\n        pt_check rm -- \"$tmpdir\"/normal\n        ;;\n    4)\n        pt_check rm -- \"$tmpdir\"/.hidden\n        ;;\n    esac\n    echo \"=== After $i: ===\" >&2\n    find \"$tmpdir\" >&2\n    echo \"=================\" >&2\n}\n\nis_program_running_testcase \\\n    \"kind='dir_nonempty_with_hidden', path='$tmpdir'\" \\\n    x_my_callback_dirnonempty_with_hidden \\\n    'cb false' \\\n    'cb true' \\\n    'cb true' \\\n    'cb true' \\\n    'cb false'\n"
  },
  {
    "path": "tests/pt_tests/plugin-mem-usage-linux/00-common.lib.bash",
    "content": "pt_require_tools mktemp\n\nmain_fifo_file=./tmp-fifo-main\n\nmem_usage_testcase() {\n    local expect_str=$1\n    local meminfo_content=$2\n\n    pt_testcase_begin\n    pt_add_fifo \"$main_fifo_file\"\n    local proc_dir; proc_dir=$(mktemp -d) || pt_fail \"'mktemp -d' failed\"\n    pt_add_dir_to_remove \"$proc_dir\"\n    local meminfo_file=$proc_dir/meminfo\n    printf '%s' \"$meminfo_content\" > \"$meminfo_file\" || pt_fail 'cannot write meminfo_file'\n    pt_add_file_to_remove \"$meminfo_file\"\n    pt_write_widget_file <<__EOF__\nf = assert(io.open('$main_fifo_file', 'w'))\nf:setvbuf('line')\nf:write('init\\n')\nfunction my_event_func() end\nx = dofile('$PT_SOURCE_DIR/plugins/mem-usage-linux/mem-usage-linux.lua')\nwidget = x.widget{\n    _procpath = '$proc_dir',\n    cb = function(t)\n        f:write(string.format('cb avail=%s(%s) total=%s(%s)\\n', t.avail.value, t.avail.unit, t.total.value, t.total.unit))\n    end,\n    event = my_event_func,\n}\nwidget.plugin = ('$PT_BUILD_DIR/plugins/{}/plugin-{}.so'):gsub('{}', widget.plugin)\nassert(widget.event == my_event_func)\n__EOF__\n    pt_spawn_luastatus\n    exec {pfd}<\"$main_fifo_file\"\n    pt_expect_line 'init' <&$pfd\n    pt_expect_line \"$expect_str\" <&$pfd\n    pt_close_fd \"$pfd\"\n    pt_testcase_end\n}\n"
  },
  {
    "path": "tests/pt_tests/plugin-mem-usage-linux/01-simple.lib.bash",
    "content": "mem_usage_testcase 'cb avail=1527556(kB) total=3877576(kB)' \"\\\nMemTotal:        3877576 kB\nMemFree:          774760 kB\nMemAvailable:    1527556 kB\nBuffers:          178504 kB\nCached:          1263372 kB\nSwapCached:            0 kB\nActive:           458264 kB\nInactive:        1938980 kB\nActive(anon):       5360 kB\nInactive(anon):  1489156 kB\nActive(file):     452904 kB\nInactive(file):   449824 kB\nUnevictable:      394212 kB\nMlocked:             144 kB\nSwapTotal:             0 kB\nSwapFree:              0 kB\nDirty:               108 kB\nWriteback:             0 kB\nAnonPages:       1259188 kB\nMapped:           336640 kB\nShmem:            539148 kB\nKReclaimable:     115248 kB\nSlab:             212356 kB\nSReclaimable:     115248 kB\nSUnreclaim:        97108 kB\nKernelStack:        6976 kB\nPageTables:        26212 kB\nNFS_Unstable:          0 kB\nBounce:                0 kB\nWritebackTmp:          0 kB\nCommitLimit:     1938788 kB\nCommitted_AS:    6041840 kB\nVmallocTotal:   34359738367 kB\nVmallocUsed:       27600 kB\nVmallocChunk:          0 kB\nPercpu:             2208 kB\nHardwareCorrupted:     0 kB\nAnonHugePages:    444416 kB\nShmemHugePages:        0 kB\nShmemPmdMapped:        0 kB\nFileHugePages:         0 kB\nFilePmdMapped:         0 kB\nHugePages_Total:       0\nHugePages_Free:        0\nHugePages_Rsvd:        0\nHugePages_Surp:        0\nHugepagesize:       2048 kB\nHugetlb:               0 kB\nDirectMap4k:      836220 kB\nDirectMap2M:     3203072 kB\nDirectMap1G:           0 kB\n\"\n"
  },
  {
    "path": "tests/pt_tests/plugin-mpd/00-common.lib.bash",
    "content": "main_fifo_file=./tmp-fifo-main\nretry_fifo_file=./tmp-fifo-mpd-retry\n\npt_find_free_tcp_port\nport=$PT_FOUND_FREE_PORT\n\npreface='\nlocal function _fmt_kv(m)\n    local ks = {}\n    for k, v in pairs(m) do\n        ks[#ks + 1] = k\n    end\n    table.sort(ks)\n    local s = {}\n    for _, k in ipairs(ks) do\n        s[#s + 1] = string.format(\"%s=>%s\", k, m[k])\n    end\n    return string.format(\"{%s}\", table.concat(s, \",\"))\nend\n'\n\nfakempd_spawn() {\n    pt_spawn_thing_pipe mpd_parrot \"$PT_PARROT\" --reuseaddr --print-line-when-ready TCP-SERVER \"$port\"\n    pt_expect_line 'parrot: ready' <&${PT_SPAWNED_THINGS_FDS_0[mpd_parrot]}\n}\n\nfakempd_spawn_unixsocket() {\n    pt_spawn_thing_pipe mpd_parrot \"$PT_PARROT\" --reuseaddr --print-line-when-ready UNIX-SERVER \"$1\"\n    pt_expect_line 'parrot: ready' <&${PT_SPAWNED_THINGS_FDS_0[mpd_parrot]}\n}\n\nfakempd_expect() {\n    pt_expect_line \"$1\" <&${PT_SPAWNED_THINGS_FDS_0[mpd_parrot]}\n}\n\nfakempd_say() {\n    printf '%s\\n' \"$1\" >&${PT_SPAWNED_THINGS_FDS_1[mpd_parrot]}\n}\n\nfakempd_kill() {\n    pt_kill_thing mpd_parrot\n}\n\nfakempd_wait() {\n    pt_wait_thing mpd_parrot || true\n}\n"
  },
  {
    "path": "tests/pt_tests/plugin-mpd/01-bad-answer.lib.bash",
    "content": "pt_testcase_begin\nfakempd_spawn\npt_write_widget_file <<__EOF__\nwidget = {\n    plugin = '$PT_BUILD_DIR/plugins/mpd/plugin-mpd.so',\n    opts = {\n        port = $port,\n        retry_in = -1,\n    },\n    cb = function(t) end,\n}\n__EOF__\npt_spawn_luastatus -e\nfakempd_say \"I'm not music player daemon, huh.\"\npt_wait_luastatus || pt_fail \"luastatus exited with non-zero exit code $?\"\n\nfakempd_wait\n\npt_testcase_end\n"
  },
  {
    "path": "tests/pt_tests/plugin-mpd/02-simple-template.lib.bash",
    "content": "x_mpd_simple_template() {\n    local bind_params=$1\n    local expect_error_in_beginning=$2\n    local enable_tcp_keepalive=${3:-0}\n\n    local extra_opts=\n    if [[ -n \"$bind_params\" ]]; then\n        local bind_ipver=${bind_params%%/*}\n        local bind_addr=${bind_params#*/}\n        extra_opts=\"bind = {addr='$bind_addr', ipver='$bind_ipver'},\"\n    fi\n\n    if (( enable_tcp_keepalive )); then\n        extra_opts+='enable_tcp_keepalive = true,'\n    fi\n\n    fakempd_spawn\n\n    pt_add_fifo \"$main_fifo_file\"\n\n    pt_write_widget_file <<__EOF__\nf = assert(io.open('$main_fifo_file', 'w'))\nf:setvbuf('line')\nf:write('init\\n')\n$preface\nwidget = {\n    plugin = '$PT_BUILD_DIR/plugins/mpd/plugin-mpd.so',\n    opts = {\n        port = $port,\n        $extra_opts\n    },\n    cb = function(t)\n        if t.what == 'update' then\n            f:write(string.format('cb update song=%s status=%s\\n', _fmt_kv(t.song), _fmt_kv(t.status)))\n        else\n            f:write('cb ' .. t.what .. '\\n')\n        end\n    end,\n}\n__EOF__\n\n    pt_spawn_luastatus\n\n    exec {pfd}<\"$main_fifo_file\"\n\n    pt_expect_line 'init' <&$pfd\n    pt_expect_line 'cb connecting' <&$pfd\n\n    if (( ! $expect_error_in_beginning )); then\n\n        fakempd_say \"OK MPD I-am-actually-a-shell-script\"\n\n        for (( i = 0; i < 3; ++i )); do\n            fakempd_expect 'currentsong'\n            fakempd_say 'Song_Foo: bar'\n            fakempd_say 'Song_Baz: quiz'\n            fakempd_say 'OK'\n\n            fakempd_expect 'status'\n            fakempd_say 'Status_One: ein'\n            fakempd_say 'Status_Two: zwei'\n            fakempd_say 'Status_Three: drei'\n            fakempd_say \"Z: $i\"\n            fakempd_say 'OK'\n\n            fakempd_expect 'idle mixer player'\n            pt_expect_line \"cb update song={Song_Baz=>quiz,Song_Foo=>bar} status={Status_One=>ein,Status_Three=>drei,Status_Two=>zwei,Z=>$i}\" <&$pfd\n\n            fakempd_say 'OK'\n        done\n\n        # First kill fake mpd, then expect an \"error\" line\n        fakempd_kill\n        pt_expect_line 'cb error' <&$pfd\n\n    else\n\n        # First expect an \"error\" line, then kill fake mpd.\n        pt_expect_line 'cb error' <&$pfd\n        fakempd_kill\n    fi\n}\n"
  },
  {
    "path": "tests/pt_tests/plugin-mpd/03-simple.lib.bash",
    "content": "pt_testcase_begin\n\nx_mpd_simple_template '' 0\n\npt_testcase_end\n"
  },
  {
    "path": "tests/pt_tests/plugin-mpd/04-simple-bind.lib.bash",
    "content": "pt_testcase_begin\n\nx_mpd_simple_template ipv4/'127.0.0.1' 0\n\npt_testcase_end\n"
  },
  {
    "path": "tests/pt_tests/plugin-mpd/05-simple-bind-invalid.lib.bash",
    "content": "pt_testcase_begin\n\n# RFC 5737: \"The blocks 192.0.2.0/24 (TEST-NET-1), 198.51.100.0/24 (TEST-NET-2),\n# and 203.0.113.0/24 (TEST-NET-3) are provided for use in documentation.\"\n#\n# So 192.0.2.0 is a \"sort of invalid\" IPv4 address.\n\nx_mpd_simple_template ipv4/'192.0.2.0' 1\n\npt_testcase_end\n"
  },
  {
    "path": "tests/pt_tests/plugin-mpd/06-simple-bind-invalid-ipv6.lib.bash",
    "content": "pt_testcase_begin\n\n# This is a malformed IPv4-mapped IPv6 address.\n# It should fail regardless of whether or not the system we are running on supports IPv6.\nx_mpd_simple_template ipv6/'::ffff:256.255.255.255' 1\n\npt_testcase_end\n"
  },
  {
    "path": "tests/pt_tests/plugin-mpd/07-keepalive.lib.bash",
    "content": "pt_testcase_begin\n\nx_mpd_simple_template '' 0 1\n\npt_testcase_end\n"
  },
  {
    "path": "tests/pt_tests/plugin-mpd/08-retry-in.lib.bash",
    "content": "pt_testcase_begin\nusing_measure\n\nfakempd_spawn\n\npt_add_fifo \"$main_fifo_file\"\npt_write_widget_file <<__EOF__\nf = assert(io.open('$main_fifo_file', 'w'))\nf:setvbuf('line')\nf:write('init\\n')\n$preface\nwidget = {\n    plugin = '$PT_BUILD_DIR/plugins/mpd/plugin-mpd.so',\n    opts = {\n        port = $port,\n        retry_in = 1.25,\n    },\n    cb = function(t)\n        if t.what == 'update' then\n            f:write(string.format('cb update song=%s status=%s\\n', _fmt_kv(t.song), _fmt_kv(t.status)))\n        else\n            f:write('cb ' .. t.what .. '\\n')\n        end\n    end,\n}\n__EOF__\npt_spawn_luastatus\nexec {pfd}<\"$main_fifo_file\"\npt_expect_line 'init' <&$pfd\n\npt_expect_line 'cb connecting' <&$pfd\n\nfor (( i = 0; i < 3; ++i )); do\n    fakempd_say \"OK MPD I-am-actually-a-shell-script\"\n\n    fakempd_expect 'currentsong'\n    fakempd_say \"Song_Foo: bar\"\n    fakempd_say \"Song_Baz: quiz\"\n    fakempd_say \"OK\"\n\n    fakempd_expect 'status'\n    fakempd_say \"Status_One: ein\"\n    fakempd_say \"Status_Two: zwei\"\n    fakempd_say \"Status_Three: drei\"\n    fakempd_say \"Z: $i\"\n    fakempd_say \"OK\"\n\n    fakempd_expect 'idle mixer player'\n    pt_expect_line \"cb update song={Song_Baz=>quiz,Song_Foo=>bar} status={Status_One=>ein,Status_Three=>drei,Status_Two=>zwei,Z=>$i}\" <&$pfd\n\n    fakempd_kill\n\n    pt_expect_line 'cb error' <&$pfd\n\n    fakempd_spawn\n\n    measure_start\n    pt_expect_line 'cb connecting' <&$pfd\n    measure_check_ms 1250\ndone\n\nfakempd_kill\npt_expect_line 'cb error' <&$pfd\n\npt_testcase_end\n"
  },
  {
    "path": "tests/pt_tests/plugin-mpd/09-retry-fifo.lib.bash",
    "content": "pt_testcase_begin\nusing_measure\n\nfakempd_spawn\n\npt_add_fifo \"$main_fifo_file\"\npt_add_fifo \"$retry_fifo_file\"\npt_write_widget_file <<__EOF__\nf = assert(io.open('$main_fifo_file', 'w'))\nf:setvbuf('line')\nf:write('init\\n')\n$preface\nwidget = {\n    plugin = '$PT_BUILD_DIR/plugins/mpd/plugin-mpd.so',\n    opts = {\n        port = $port,\n        hostname = '127.0.0.1',\n        retry_in = 1,\n        retry_fifo = '$retry_fifo_file',\n    },\n    cb = function(t)\n        if t.what == 'update' then\n            f:write(string.format('cb update song=%s status=%s\\n', _fmt_kv(t.song), _fmt_kv(t.status)))\n        else\n            f:write('cb ' .. t.what .. '\\n')\n        end\n    end,\n}\n__EOF__\npt_spawn_luastatus\nexec {pfd}<\"$main_fifo_file\"\npt_expect_line 'init' <&$pfd\n\npt_expect_line 'cb connecting' <&$pfd\n\nfor (( i = 0; i < 6; ++i )); do\n    fakempd_say \"OK MPD I-am-actually-a-shell-script\"\n\n    fakempd_expect 'currentsong'\n    fakempd_say \"Song_Foo: bar\"\n    fakempd_say \"Song_Baz: quiz\"\n    fakempd_say \"OK\"\n\n    fakempd_expect 'status'\n    fakempd_say \"Status_One: ein\"\n    fakempd_say \"Status_Two: zwei\"\n    fakempd_say \"Status_Three: drei\"\n    fakempd_say \"Z: $i\"\n    fakempd_say \"OK\"\n\n    fakempd_expect 'idle mixer player'\n    pt_expect_line \"cb update song={Song_Baz=>quiz,Song_Foo=>bar} status={Status_One=>ein,Status_Three=>drei,Status_Two=>zwei,Z=>$i}\" <&$pfd\n\n    fakempd_kill\n\n    pt_expect_line 'cb error' <&$pfd\n\n    fakempd_spawn\n\n    if (( i % 2 == 0 )); then\n        measure_start\n        pt_expect_line 'cb connecting' <&$pfd\n        measure_check_ms 1000\n    else\n        touch \"$retry_fifo_file\"\n        measure_start\n        pt_expect_line 'cb connecting' <&$pfd\n        measure_check_ms 0\n    fi\ndone\n\nfakempd_kill\npt_expect_line 'cb error' <&$pfd\n\npt_testcase_end\n"
  },
  {
    "path": "tests/pt_tests/plugin-mpd/10-timeout.lib.bash",
    "content": "pt_testcase_begin\nusing_measure\n\nfakempd_spawn\n\npt_add_fifo \"$main_fifo_file\"\npt_write_widget_file <<__EOF__\nf = assert(io.open('$main_fifo_file', 'w'))\nf:setvbuf('line')\nf:write('init\\n')\n$preface\nwidget = {\n    plugin = '$PT_BUILD_DIR/plugins/mpd/plugin-mpd.so',\n    opts = {\n        port = $port,\n        timeout = 0.25,\n    },\n    cb = function(t)\n        if t.what == 'update' then\n            f:write(string.format('cb update song=%s status=%s\\n', _fmt_kv(t.song), _fmt_kv(t.status)))\n        else\n            f:write('cb ' .. t.what .. '\\n')\n        end\n    end,\n}\n__EOF__\npt_spawn_luastatus\nexec {pfd}<\"$main_fifo_file\"\npt_expect_line 'init' <&$pfd\npt_expect_line 'cb connecting' <&$pfd\n\nfakempd_say \"OK MPD I-am-actually-a-shell-script\"\n\nfor (( i = 0; i < 3; ++i )); do\n    fakempd_expect 'currentsong'\n    fakempd_say \"Song_Foo: bar\"\n    fakempd_say \"Song_Baz: quiz\"\n    fakempd_say \"OK\"\n\n    fakempd_expect 'status'\n    fakempd_say \"Status_One: ein\"\n    fakempd_say \"Status_Two: zwei\"\n    fakempd_say \"Status_Three: drei\"\n    fakempd_say \"Z: $i\"\n    fakempd_say \"OK\"\n\n    fakempd_expect 'idle mixer player'\n\n    measure_start\n    pt_expect_line \"cb update song={Song_Baz=>quiz,Song_Foo=>bar} status={Status_One=>ein,Status_Three=>drei,Status_Two=>zwei,Z=>$i}\" <&$pfd\n    measure_check_ms 0\n\n    pt_expect_line \"cb timeout\" <&$pfd\n    measure_check_ms 250\n\n    pt_expect_line \"cb timeout\" <&$pfd\n    measure_check_ms 250\n\n    fakempd_say \"OK\"\ndone\n\nfakempd_kill\n\npt_expect_line 'cb error' <&$pfd\n\npt_testcase_end\n"
  },
  {
    "path": "tests/pt_tests/plugin-mpd/11-password-and-events.lib.bash",
    "content": "pt_testcase_begin\n\nfakempd_spawn\n\npt_add_fifo \"$main_fifo_file\"\npt_write_widget_file <<__EOF__\nf = assert(io.open('$main_fifo_file', 'w'))\nf:setvbuf('line')\nf:write('init\\n')\n$preface\nwidget = {\n    plugin = '$PT_BUILD_DIR/plugins/mpd/plugin-mpd.so',\n    opts = {\n        port = $port,\n        hostname = 'localhost',\n        password = 'qwerty123456',\n        events = {'my', 'custom', 'events', 'string'},\n    },\n    cb = function(t)\n        if t.what == 'update' then\n            f:write(string.format('cb update song=%s status=%s\\n', _fmt_kv(t.song), _fmt_kv(t.status)))\n        else\n            f:write('cb ' .. t.what .. '\\n')\n        end\n    end,\n}\n__EOF__\npt_spawn_luastatus\nexec {pfd}<\"$main_fifo_file\"\npt_expect_line 'init' <&$pfd\npt_expect_line 'cb connecting' <&$pfd\n\nfakempd_say \"OK MPD I-am-actually-a-shell-script\"\nfakempd_expect 'password \"qwerty123456\"'\nfakempd_say \"OK\"\n\nfor (( i = 0; i < 3; ++i )); do\n    fakempd_expect \"currentsong\"\n    fakempd_say \"Song_Foo: bar\"\n    fakempd_say \"Song_Baz: quiz\"\n    fakempd_say \"OK\"\n\n    fakempd_expect \"status\"\n    fakempd_say \"Status_One: ein\"\n    fakempd_say \"Status_Two: zwei\"\n    fakempd_say \"Status_Three: drei\"\n    fakempd_say \"Z: $i\"\n    fakempd_say \"OK\"\n\n    fakempd_expect \"idle my custom events string\"\n    pt_expect_line \"cb update song={Song_Baz=>quiz,Song_Foo=>bar} status={Status_One=>ein,Status_Three=>drei,Status_Two=>zwei,Z=>$i}\" <&$pfd\n\n    fakempd_say \"OK\"\ndone\n\nfakempd_kill\n\npt_expect_line 'cb error' <&$pfd\n\npt_testcase_end\n"
  },
  {
    "path": "tests/pt_tests/plugin-mpd/12-connect-via-unix-socket.lib.bash",
    "content": "pt_testcase_begin\n\nsocket_file=$PWD/tmp-fake-mpd-socket\nrm -f \"$socket_file\"\npt_add_file_to_remove \"$socket_file\"\n\nfakempd_spawn_unixsocket \"$socket_file\"\n\npt_add_fifo \"$main_fifo_file\"\npt_write_widget_file <<__EOF__\nf = assert(io.open('$main_fifo_file', 'w'))\nf:setvbuf('line')\nf:write('init\\n')\n$preface\nwidget = {\n    plugin = '$PT_BUILD_DIR/plugins/mpd/plugin-mpd.so',\n    opts = {\n        hostname = '$socket_file',\n    },\n    cb = function(t)\n        if t.what == 'update' then\n            f:write(string.format('cb update song=%s status=%s\\n', _fmt_kv(t.song), _fmt_kv(t.status)))\n        else\n            f:write('cb ' .. t.what .. '\\n')\n        end\n    end,\n}\n__EOF__\npt_spawn_luastatus\nexec {pfd}<\"$main_fifo_file\"\npt_expect_line 'init' <&$pfd\npt_expect_line 'cb connecting' <&$pfd\n\nfakempd_say \"OK MPD I-am-actually-a-shell-script\"\n\nfor (( i = 0; i < 3; ++i )); do\n    fakempd_expect 'currentsong'\n    fakempd_say \"Song_Foo: bar\"\n    fakempd_say \"Song_Baz: quiz\"\n    fakempd_say \"OK\"\n\n    fakempd_expect 'status'\n    fakempd_say \"Status_One: ein\"\n    fakempd_say \"Status_Two: zwei\"\n    fakempd_say \"Status_Three: drei\"\n    fakempd_say \"Z: $i\"\n    fakempd_say \"OK\"\n\n    fakempd_expect 'idle mixer player'\n    pt_expect_line \"cb update song={Song_Baz=>quiz,Song_Foo=>bar} status={Status_One=>ein,Status_Three=>drei,Status_Two=>zwei,Z=>$i}\" <&$pfd\n\n    fakempd_say \"OK\"\ndone\n\nfakempd_kill\n\npt_expect_line 'cb error' <&$pfd\n\npt_testcase_end\n"
  },
  {
    "path": "tests/pt_tests/plugin-mpd/13-bad-password.lib.bash",
    "content": "pt_testcase_begin\n\nfakempd_spawn\n\npt_add_fifo \"$main_fifo_file\"\npt_write_widget_file <<__EOF__\nf = assert(io.open('$main_fifo_file', 'w'))\nf:setvbuf('line')\nf:write('init\\n')\n$preface\nwidget = {\n    plugin = '$PT_BUILD_DIR/plugins/mpd/plugin-mpd.so',\n    opts = {\n        port = $port,\n        password = 'this is password with \"QUOTE MARKS\" ha-ha',\n    },\n    cb = function(t)\n        f:write('cb ' .. t.what .. '\\n')\n    end,\n}\n__EOF__\npt_spawn_luastatus\nexec {pfd}<\"$main_fifo_file\"\npt_expect_line 'init' <&$pfd\npt_expect_line 'cb connecting' <&$pfd\n\nfakempd_say \"OK MPD I-am-actually-a-shell-script\"\n\nfakempd_expect 'password \"this is password with \\\"QUOTE MARKS\\\" ha-ha\"'\nfakempd_say 'ACK wrong password (even though it is with \"QUOTE MARKS\" he-he)'\n\npt_expect_line 'cb error' <&$pfd\n\nfakempd_wait\n\npt_testcase_end\n"
  },
  {
    "path": "tests/pt_tests/plugin-mpd/14-bad-hostname.lib.bash",
    "content": "pt_testcase_begin\nfakempd_spawn\npt_write_widget_file <<__EOF__\nwidget = {\n    plugin = '$PT_BUILD_DIR/plugins/mpd/plugin-mpd.so',\n    opts = {\n        port = $port,\n        hostname = '255.255.255.255',\n        retry_in = -1,\n    },\n    cb = function(t) end,\n}\n__EOF__\npt_spawn_luastatus -e\npt_wait_luastatus || pt_fail \"luastatus exited with non-zero code $?\"\n\nfakempd_kill\n\npt_testcase_end\n"
  },
  {
    "path": "tests/pt_tests/plugin-multiplex/00-common.lib.bash",
    "content": "main_fifo_file=./tmp-fifo-main\nwakeup_fifo_file=./tmp-fifo-wakeup\n"
  },
  {
    "path": "tests/pt_tests/plugin-multiplex/01-simple.lib.bash",
    "content": "pt_testcase_begin\nusing_measure\n\npt_add_fifo \"$main_fifo_file\"\npt_write_widget_file <<__EOF__\nf = assert(io.open('$main_fifo_file', 'w'))\nf:setvbuf('line')\nf:write('init\\n')\nwidget = {\n    plugin = '$PT_BUILD_DIR/plugins/multiplex/plugin-multiplex.so',\n    opts = {\n        data_sources = {\n            src1 = [[\n                i = 0\n                widget = {\n                    plugin = '$PT_BUILD_DIR/plugins/timer/plugin-timer.so',\n                    cb = function(t)\n                        i = i + 1\n                        return string.format(\"%s %d\", t, i)\n                    end,\n                }\n            ]],\n        },\n    },\n    cb = function(t)\n        assert(t.what == 'update')\n        local text = t.updates.src1\n        f:write('cb ' .. text .. '\\n')\n    end,\n}\n__EOF__\npt_spawn_luastatus\nexec {pfd}<\"$main_fifo_file\"\npt_expect_line 'init' <&$pfd\nmeasure_start\npt_expect_line 'cb hello 1' <&$pfd\nmeasure_check_ms 0\npt_expect_line 'cb timeout 2' <&$pfd\nmeasure_check_ms 1000\npt_expect_line 'cb timeout 3' <&$pfd\nmeasure_check_ms 1000\npt_close_fd \"$pfd\"\npt_testcase_end\n"
  },
  {
    "path": "tests/pt_tests/plugin-multiplex/02-two-data-sources.lib.bash",
    "content": "pt_testcase_begin\n\npt_add_fifo \"$main_fifo_file\"\npt_write_widget_file <<__EOF__\nf = assert(io.open('$main_fifo_file', 'w'))\nf:setvbuf('line')\nf:write('init\\n')\n\nfunction make_data_source(num, period)\n    local prefix1 = string.format(\"MY_NUM = %d\\n\", num)\n    local prefix2 = string.format(\"MY_PERIOD = %.3f\\n\", period)\n    return prefix1 .. prefix2 .. [[\n        i = 0\n        widget = {\n            plugin = '$PT_BUILD_DIR/plugins/timer/plugin-timer.so',\n            opts = {\n                period = MY_PERIOD,\n            },\n            cb = function(t)\n                if t == 'hello' then\n                    return nil\n                end\n                i = i + 1\n                return string.format(\"[src%d] %s %d\", MY_NUM, t, i)\n            end,\n        }\n    ]]\nend\n\nwidget = {\n    plugin = '$PT_BUILD_DIR/plugins/multiplex/plugin-multiplex.so',\n    opts = {\n        data_sources = {\n            src1 = make_data_source(1, 3.0),\n            src2 = make_data_source(2, 7.5),\n        },\n    },\n    cb = function(t)\n        assert(t.what == 'update')\n        local text = t.updates.src1 or t.updates.src2\n        if type(text) == 'string' then\n            f:write('cb ' .. text .. '\\n')\n        end\n    end,\n}\n__EOF__\npt_spawn_luastatus\nexec {pfd}<\"$main_fifo_file\"\npt_expect_line 'init' <&$pfd\n\npt_expect_line \"cb [src1] timeout 1\" <&$pfd\npt_expect_line \"cb [src1] timeout 2\" <&$pfd\npt_expect_line \"cb [src2] timeout 1\" <&$pfd\npt_expect_line \"cb [src1] timeout 3\" <&$pfd\n\npt_close_fd \"$pfd\"\npt_testcase_end\n"
  },
  {
    "path": "tests/pt_tests/plugin-multiplex/03-call-event.lib.bash",
    "content": "pt_testcase_begin\n\npt_add_fifo \"$main_fifo_file\"\npt_write_widget_file <<__EOF__\nf = assert(io.open('$main_fifo_file', 'w'))\nf:setvbuf('line')\nf:write('init\\n')\nwidget = {\n    plugin = '$PT_BUILD_DIR/plugins/multiplex/plugin-multiplex.so',\n    opts = {\n        data_sources = {\n            src1 = [[\n                alt_text = ''\n                i = 0\n                widget = {\n                    plugin = '$PT_BUILD_DIR/plugins/timer/plugin-timer.so',\n                    cb = function(t)\n                        if #alt_text ~= 0 then\n                            return alt_text\n                        end\n                        i = i + 1\n                        return string.format(\"%s %d\", t, i)\n                    end,\n                    event = function(s)\n                        alt_text = string.format('event %s', s)\n                    end,\n                }\n            ]],\n        },\n    },\n    cb = function(t)\n        assert(t.what == 'update')\n        local text = t.updates.src1\n        f:write('cb ' .. text .. '\\n')\n        if text == 'timeout 3' then\n            assert(luastatus.plugin.call_event('src1', '[some data]'))\n        end\n    end,\n}\n__EOF__\npt_spawn_luastatus\nexec {pfd}<\"$main_fifo_file\"\npt_expect_line 'init' <&$pfd\npt_expect_line 'cb hello 1' <&$pfd\npt_expect_line 'cb timeout 2' <&$pfd\npt_expect_line 'cb timeout 3' <&$pfd\npt_expect_line 'cb event [some data]' <&$pfd\npt_close_fd \"$pfd\"\npt_testcase_end\n"
  },
  {
    "path": "tests/pt_tests/plugin-network-linux/00-common.lib.bash",
    "content": "main_fifo_file=./tmp-fifo-main\n"
  },
  {
    "path": "tests/pt_tests/plugin-network-linux/01-simple.lib.bash",
    "content": "pt_testcase_begin\npt_add_fifo \"$main_fifo_file\"\npt_write_widget_file <<__EOF__\nf = assert(io.open('$main_fifo_file', 'w'))\nf:setvbuf('line')\nf:write('init\\n')\nlocal function _fmt_t(t)\n    local s = {}\n    for k, v in pairs(t) do\n        for _, x in ipairs(v.ipv4 or {}) do\n            s[#s + 1] = string.format('%s ipv4 %s', k, x)\n        end\n        for _, x in ipairs(v.ipv6 or {}) do\n            s[#s + 1] = string.format('%s ipv6 %s', k, x)\n        end\n    end\n    table.sort(s)\n    return table.concat(s, ';') .. ';'\nend\nwidget = {\n    plugin = '$PT_BUILD_DIR/plugins/network-linux/plugin-network-linux.so',\n    opts = {new_ip_fmt = true},\n    cb = function(t)\n        if t == nil then\n            f:write('cb nil\\n')\n        else\n            f:write('cb table ' .. _fmt_t(t) .. '\\n')\n        end\n    end,\n}\n__EOF__\npt_spawn_luastatus\nexec {pfd}<\"$main_fifo_file\"\npt_expect_line 'init' <&$pfd\n\nlistnets_binary=$PT_BUILD_DIR/tests/listnets\nc=0; nets=$(\"$listnets_binary\") || c=$?\ncase \"$c\" in\n0)\n    nets=$(printf '%s\\n' \"$nets\" | LC_ALL=C sort | tr '\\n' ';')\n    expect_str=\"cb table $nets\"\n    ;;\n1)\n    expect_str=\"cb nil\"\n    ;;\n*)\n    pt_fail \"listnets binary ('$listnets_binary') failed with code $c.\"\n    ;;\nesac\n\npt_expect_line \"$expect_str\" <&$pfd\n\npt_close_fd \"$pfd\"\npt_testcase_end\n"
  },
  {
    "path": "tests/pt_tests/plugin-network-linux/02-simple-newfmt.lib.bash",
    "content": "pt_testcase_begin\npt_add_fifo \"$main_fifo_file\"\npt_write_widget_file <<__EOF__\nf = assert(io.open('$main_fifo_file', 'w'))\nf:setvbuf('line')\nf:write('init\\n')\nlocal function _fmt_t(t)\n    local s = {}\n    for k, v in pairs(t) do\n        for _, x in ipairs(v.ipv4 or {}) do\n            s[#s + 1] = string.format('%s ipv4 %s', k, x)\n        end\n        for _, x in ipairs(v.ipv6 or {}) do\n            s[#s + 1] = string.format('%s ipv6 %s', k, x)\n        end\n    end\n    table.sort(s)\n    return table.concat(s, ';') .. ';'\nend\nwidget = {\n    plugin = '$PT_BUILD_DIR/plugins/network-linux/plugin-network-linux.so',\n    opts = {\n        new_ip_fmt = true,\n        new_overall_fmt = true,\n    },\n    cb = function(t)\n        assert(type(t) == 'table', 't is not a table')\n        assert(t.what, 't.what is not present')\n\n        if t.what == 'error' then\n            f:write('cb error\\n')\n        else\n            f:write('cb ok ' .. _fmt_t(t.data) .. '\\n')\n        end\n    end,\n}\n__EOF__\npt_spawn_luastatus\nexec {pfd}<\"$main_fifo_file\"\npt_expect_line 'init' <&$pfd\n\nlistnets_binary=$PT_BUILD_DIR/tests/listnets\nc=0; nets=$(\"$listnets_binary\") || c=$?\ncase \"$c\" in\n0)\n    nets=$(printf '%s\\n' \"$nets\" | LC_ALL=C sort | tr '\\n' ';')\n    expect_str=\"cb ok $nets\"\n    ;;\n1)\n    expect_str=\"cb error\"\n    ;;\n*)\n    pt_fail \"listnets binary ('$listnets_binary') failed with code $c.\"\n    ;;\nesac\n\npt_expect_line \"$expect_str\" <&$pfd\n\npt_close_fd \"$pfd\"\npt_testcase_end\n"
  },
  {
    "path": "tests/pt_tests/plugin-network-rate-linux/00-common.lib.bash",
    "content": "pt_require_tools mktemp\n\nmain_fifo_file=./tmp-fifo-main\n\nnetdev_content_1=\"\\\nInter-|   Receive                                                |  Transmit\n face |bytes    packets errs drop fifo frame compressed multicast|bytes    packets errs drop fifo colls carrier compressed\n    lo: 1563691862  518094    0    0    0     0          0         0 1563691862  518094    0    0    0     0       0          0\nenp3s0:       0       0    0    0    0     0          0         0        0       0    0    0    0     0       0          0\n wlan0: 14599861312 19393562    0  706    0     0          0         0 14123982636 19982115    0   23    0     0       0          0\n  tun0:    1482      19    0    0    0     0          0         0   251702    1316    0    0    0     0       0          0\n\"\n\nnetdev_content_2=\"\\\nInter-|   Receive                                                |  Transmit\n face |bytes    packets errs drop fifo frame compressed multicast|bytes    packets errs drop fifo colls carrier compressed\n    lo: 1563691862  518094    0    0    0     0          0         0 1563691862  518094    0    0    0     0       0          0\nenp3s0:       0       0    0    0    0     0          0         0        0       0    0    0    0     0       0          0\n wlan0: 14601405091 19395437    0  706    0     0          0         0 14125578402 19984370    0   23    0     0       0          0\n  tun0:    1482      19    0    0    0     0          0         0   251702    1316    0    0    0     0       0          0\n\"\n\nres_wlan0_div1='R:1543779.0,S:1595766.0'\nres_wlan0_div2='R:771889.5,S:797883.0'\n\nnetwork_rate_linux_testcase() {\n    local extra_lua_opts=$1\n    local expect_str=$2\n\n    pt_testcase_begin\n    pt_add_fifo \"$main_fifo_file\"\n\n    local proc_dir; proc_dir=$(mktemp -d) || pt_fail \"'mktemp -d' failed\"\n    pt_check mkdir \"$proc_dir\"/net\n    pt_add_dirs_to_remove_inorder \"$proc_dir\"/net \"$proc_dir\"\n\n    local netdev_file=$proc_dir/net/dev\n\n    printf '%s' \"$netdev_content_1\" > \"$netdev_file\" || pt_fail 'cannot write netdev_file'\n    pt_add_file_to_remove \"$netdev_file\"\n    pt_write_widget_file <<__EOF__\nf = assert(io.open('$main_fifo_file', 'w'))\nf:setvbuf('line')\nf:write('init\\n')\nfunction my_event_func() end\nx = dofile('$PT_SOURCE_DIR/plugins/network-rate-linux/network-rate-linux.lua')\n\nfunction stringify_results(tbl)\n    if type(next(tbl)) ~= \"number\" then\n        local new_tbl = {}\n        for P, Q in pairs(tbl) do\n            table.insert(new_tbl, {P, Q})\n        end\n        table.sort(new_tbl, function(PQ_a, PQ_b) return PQ_a[1] < PQ_b[1] end)\n        tbl = new_tbl\n    end\n    local chunks = {}\n    for _, PQ in ipairs(tbl) do\n        table.insert(chunks, string.format(\"%s=>{R:%.1f,S:%.1f}\", PQ[1], PQ[2].R, PQ[2].S))\n    end\n    return string.format(\"[%s]\", table.concat(chunks, \",\"))\nend\n\nwidget = x.widget{\n    _procpath = '$proc_dir',\n    cb = function(t)\n        f:write('cb ' .. stringify_results(t) .. '\\n')\n    end,\n    event = my_event_func,\n    $extra_lua_opts\n}\nwidget.plugin = ('$PT_BUILD_DIR/plugins/{}/plugin-{}.so'):gsub('{}', widget.plugin)\nassert(widget.event == my_event_func)\n__EOF__\n    pt_spawn_luastatus\n    exec {pfd}<\"$main_fifo_file\"\n    pt_expect_line 'init' <&$pfd\n    pt_expect_line 'cb []' <&$pfd\n    printf '%s' \"$netdev_content_2\" > \"$netdev_file\"\n    pt_expect_line \"$expect_str\" <&$pfd\n    pt_close_fd \"$pfd\"\n    pt_testcase_end\n}\n"
  },
  {
    "path": "tests/pt_tests/plugin-network-rate-linux/01-simple.lib.bash",
    "content": "network_rate_linux_testcase '' \"cb [lo=>{R:0.0,S:0.0},tun0=>{R:0.0,S:0.0},wlan0=>{$res_wlan0_div1}]\"\n\nnetwork_rate_linux_testcase 'period=2' \"cb [lo=>{R:0.0,S:0.0},tun0=>{R:0.0,S:0.0},wlan0=>{$res_wlan0_div2}]\"\n"
  },
  {
    "path": "tests/pt_tests/plugin-network-rate-linux/02-in-array-form.lib.bash",
    "content": "network_rate_linux_testcase 'in_array_form=true' \"cb [lo=>{R:0.0,S:0.0},wlan0=>{$res_wlan0_div1},tun0=>{R:0.0,S:0.0}]\"\n\nnetwork_rate_linux_testcase 'in_array_form=true,period=2' \"cb [lo=>{R:0.0,S:0.0},wlan0=>{$res_wlan0_div2},tun0=>{R:0.0,S:0.0}]\"\n"
  },
  {
    "path": "tests/pt_tests/plugin-network-rate-linux/03-iface-filter.lib.bash",
    "content": "network_rate_linux_testcase_iface_filter() {\n    local lua_opts=$1\n    shift\n\n    local expected_out=\n\n    local letter\n    for letter in \"$@\"; do\n        case \"$letter\" in\n            L) expected_out+='lo=>{R:0.0,S:0.0},' ;;\n            T) expected_out+='tun0=>{R:0.0,S:0.0},' ;;\n            W) expected_out+=\"wlan0=>{$res_wlan0_div1},\" ;;\n            *) pt_fail \"unexpected letter '$letter'\" ;;\n        esac\n    done\n\n    expected_out=${expected_out%,}\n\n    network_rate_linux_testcase \"$lua_opts\" \"cb [$expected_out]\"\n}\n\nnetwork_rate_linux_testcase_iface_filter 'iface_only=\"wlan0\"' W\nnetwork_rate_linux_testcase_iface_filter 'iface_except=\"lo\"' T W\n\nnetwork_rate_linux_testcase_iface_filter 'iface_only={\"tun0\",\"wlan0\"}' T W\nnetwork_rate_linux_testcase_iface_filter 'iface_except={\"lo\",\"tun0\"}' W\n\nnetwork_rate_linux_testcase_iface_filter 'iface_only={tun0=true,wlan0=true}' T W\nnetwork_rate_linux_testcase_iface_filter 'iface_except={lo=true,tun0=true}' W\n\nnetwork_rate_linux_testcase_iface_filter 'iface_filter=function(s) return string.find(s, \"w\") == nil end' L T\n"
  },
  {
    "path": "tests/pt_tests/plugin-pipe/00-common.lib.bash",
    "content": "main_fifo_file=./tmp-fifo-main\n"
  },
  {
    "path": "tests/pt_tests/plugin-pipe/01-simple.lib.bash",
    "content": "pt_testcase_begin\npt_add_fifo \"$main_fifo_file\"\npt_write_widget_file <<__EOF__\nf = assert(io.open('$main_fifo_file', 'w'))\nf:setvbuf('line')\nf:write('init\\n')\nfunction my_event_func() end\nx = dofile('$PT_SOURCE_DIR/plugins/pipe/pipe.lua')\nwidget = x.widget{\n    command = 'echo one; echo two; echo three',\n    cb = function(line)\n        f:write('cb ' .. line .. '\\n')\n    end,\n    on_eof = function()\n        f:write('eof\\n')\n        while true do\n        end\n    end,\n    event = my_event_func,\n}\nwidget.plugin = ('$PT_BUILD_DIR/plugins/{}/plugin-{}.so'):gsub('{}', widget.plugin)\nassert(widget.event == my_event_func)\n__EOF__\npt_spawn_luastatus\nexec {pfd}<\"$main_fifo_file\"\npt_expect_line 'init' <&$pfd\npt_expect_line 'cb one' <&$pfd\npt_expect_line 'cb two' <&$pfd\npt_expect_line 'cb three' <&$pfd\npt_expect_line 'eof' <&$pfd\npt_close_fd \"$pfd\"\npt_testcase_end\n"
  },
  {
    "path": "tests/pt_tests/plugin-pipe/02-func-shell-escape.lib.bash",
    "content": "pt_testcase_begin\npt_add_fifo \"$main_fifo_file\"\npt_write_widget_file <<__EOF__\nf = assert(io.open('$main_fifo_file', 'w'))\nf:setvbuf('line')\nf:write('init\\n')\nx = dofile('$PT_SOURCE_DIR/plugins/pipe/pipe.lua')\nlocal function dump(id, arg)\n    local s = x.shell_escape(arg)\n    assert(type(s) == 'string', 'shell_escape() return value is not string')\n    f:write(string.format('%s => %s\\n', id, s:gsub('\\n', ';')))\nend\ndump('1', 'hello')\ndump('2', 'hello world')\ndump('3', 'ichi\\t  nichi')\ndump('4', 'one\\ntwo')\ndump('5', \"it's fine\")\ndump('6', '')\ndump('7', {})\ndump('8', {'one'})\ndump('9', {\"one's own\"})\ndump('10', {'one', 'two', 'three'})\ndump('11', {'one two', 'three'})\ndump('12', {'one', 'two\\n\\tthree'})\n__EOF__\npt_spawn_luastatus\nexec {pfd}<\"$main_fifo_file\"\ntab=$'\\t'\npt_expect_line 'init' <&$pfd\npt_expect_line \"1 => 'hello'\" <&$pfd\npt_expect_line \"2 => 'hello world'\" <&$pfd\npt_expect_line \"3 => 'ichi${tab}  nichi'\" <&$pfd\npt_expect_line \"4 => 'one;two'\" <&$pfd\npt_expect_line \"5 => 'it'\\\\''s fine'\" <&$pfd\npt_expect_line \"6 => ''\" <&$pfd\npt_expect_line \"7 => \" <&$pfd\npt_expect_line \"8 => 'one'\" <&$pfd\npt_expect_line \"9 => 'one'\\\\''s own'\" <&$pfd\npt_expect_line \"10 => 'one' 'two' 'three'\" <&$pfd\npt_expect_line \"11 => 'one two' 'three'\" <&$pfd\npt_expect_line \"12 => 'one' 'two;${tab}three'\" <&$pfd\npt_close_fd \"$pfd\"\npt_testcase_end\n"
  },
  {
    "path": "tests/pt_tests/plugin-pipev2/00-common.lib.bash",
    "content": "main_fifo_file=./tmp-fifo-main\n"
  },
  {
    "path": "tests/pt_tests/plugin-pipev2/01-simple.lib.bash",
    "content": "pt_testcase_begin\npt_add_fifo \"$main_fifo_file\"\npt_write_widget_file <<__EOF__\nf = assert(io.open('$main_fifo_file', 'w'))\nf:setvbuf('line')\nf:write('init\\n')\n\nwidget = {\n    plugin = '$PT_BUILD_DIR/plugins/pipev2/plugin-pipev2.so',\n    opts = {\n        argv = {'/bin/sh', '-c', 'echo one; echo two; echo three'},\n    },\n    cb = function(t)\n        if t.what == 'line' then\n            f:write('cb line ' .. t.line .. '\\n')\n        else\n            f:write('cb ' .. t.what .. '\\n')\n        end\n    end,\n}\n__EOF__\npt_spawn_luastatus\nexec {pfd}<\"$main_fifo_file\"\npt_expect_line 'init' <&$pfd\npt_expect_line 'cb line one' <&$pfd\npt_expect_line 'cb line two' <&$pfd\npt_expect_line 'cb line three' <&$pfd\npt_close_fd \"$pfd\"\npt_testcase_end\n"
  },
  {
    "path": "tests/pt_tests/plugin-pipev2/02-greet.lib.bash",
    "content": "pt_testcase_begin\npt_add_fifo \"$main_fifo_file\"\npt_write_widget_file <<__EOF__\nf = assert(io.open('$main_fifo_file', 'w'))\nf:setvbuf('line')\nf:write('init\\n')\n\nwidget = {\n    plugin = '$PT_BUILD_DIR/plugins/pipev2/plugin-pipev2.so',\n    opts = {\n        argv = {'/bin/sh', '-c', 'echo one; echo two; echo three'},\n        greet = true,\n    },\n    cb = function(t)\n        if t.what == 'line' then\n            f:write('cb line ' .. t.line .. '\\n')\n        else\n            f:write('cb ' .. t.what .. '\\n')\n        end\n    end,\n}\n__EOF__\npt_spawn_luastatus\nexec {pfd}<\"$main_fifo_file\"\npt_expect_line 'init' <&$pfd\npt_expect_line 'cb hello' <&$pfd\npt_expect_line 'cb line one' <&$pfd\npt_expect_line 'cb line two' <&$pfd\npt_expect_line 'cb line three' <&$pfd\npt_close_fd \"$pfd\"\npt_testcase_end\n"
  },
  {
    "path": "tests/pt_tests/plugin-pipev2/03-bye.lib.bash",
    "content": "pt_testcase_begin\npt_add_fifo \"$main_fifo_file\"\npt_write_widget_file <<__EOF__\nf = assert(io.open('$main_fifo_file', 'w'))\nf:setvbuf('line')\nf:write('init\\n')\n\nwidget = {\n    plugin = '$PT_BUILD_DIR/plugins/pipev2/plugin-pipev2.so',\n    opts = {\n        argv = {'/bin/sh', '-c', 'echo one; echo two; echo three'},\n        bye = true,\n    },\n    cb = function(t)\n        if t.what == 'line' then\n            f:write('cb line ' .. t.line .. '\\n')\n        else\n            f:write('cb ' .. t.what .. '\\n')\n        end\n    end,\n}\n__EOF__\npt_spawn_luastatus\nexec {pfd}<\"$main_fifo_file\"\npt_expect_line 'init' <&$pfd\npt_expect_line 'cb line one' <&$pfd\npt_expect_line 'cb line two' <&$pfd\npt_expect_line 'cb line three' <&$pfd\npt_expect_line 'cb bye' <&$pfd\npt_close_fd \"$pfd\"\npt_testcase_end\n"
  },
  {
    "path": "tests/pt_tests/plugin-pipev2/04-write-to-stdin.lib.bash",
    "content": "pt_testcase_begin\npt_add_fifo \"$main_fifo_file\"\npt_write_widget_file <<__EOF__\nf = assert(io.open('$main_fifo_file', 'w'))\nf:setvbuf('line')\nf:write('init\\n')\n\ni = 0\n\nwidget = {\n    plugin = '$PT_BUILD_DIR/plugins/pipev2/plugin-pipev2.so',\n    opts = {\n        argv = {'/bin/sh', '-c', 'while read x; do echo \"you said <\\$x>\"; done'},\n        greet = true,\n        pipe_stdin = true,\n    },\n    cb = function(t)\n        assert(luastatus.plugin.write_to_stdin(string.format('%d\\n', i)))\n        i = i + 1\n\n        if t.what == 'line' then\n            f:write('cb line ' .. t.line .. '\\n')\n        else\n            f:write('cb ' .. t.what .. '\\n')\n        end\n    end,\n}\n__EOF__\npt_spawn_luastatus\nexec {pfd}<\"$main_fifo_file\"\npt_expect_line 'init' <&$pfd\npt_expect_line 'cb hello' <&$pfd\npt_expect_line 'cb line you said <0>' <&$pfd\npt_expect_line 'cb line you said <1>' <&$pfd\npt_expect_line 'cb line you said <2>' <&$pfd\n\npt_close_fd \"$pfd\"\npt_testcase_end\n"
  },
  {
    "path": "tests/pt_tests/plugin-pipev2/05-kill-default.lib.bash",
    "content": "pt_testcase_begin\npt_add_fifo \"$main_fifo_file\"\npt_write_widget_file <<__EOF__\nf = assert(io.open('$main_fifo_file', 'w'))\nf:setvbuf('line')\nf:write('init\\n')\n\nwidget = {\n    plugin = '$PT_BUILD_DIR/plugins/pipev2/plugin-pipev2.so',\n    opts = {\n        argv = {'/usr/bin/env', 'bash', '-c', 'for (( i=0; ; ++i)); do echo \\$i; sleep 1; done'},\n        greet = true,\n        bye = true,\n    },\n    cb = function(t)\n        if t.what == 'line' then\n            f:write('cb line ' .. t.line .. '\\n')\n            if t.line == '3' then\n                luastatus.plugin.kill()\n            end\n        else\n            f:write('cb ' .. t.what .. '\\n')\n        end\n    end,\n}\n__EOF__\npt_spawn_luastatus\nexec {pfd}<\"$main_fifo_file\"\npt_expect_line 'init' <&$pfd\npt_expect_line 'cb hello' <&$pfd\npt_expect_line 'cb line 0' <&$pfd\npt_expect_line 'cb line 1' <&$pfd\npt_expect_line 'cb line 2' <&$pfd\npt_expect_line 'cb line 3' <&$pfd\npt_expect_line 'cb bye' <&$pfd\n\npt_close_fd \"$pfd\"\npt_testcase_end\n"
  },
  {
    "path": "tests/pt_tests/plugin-pipev2/06-kill-by-spelling.lib.bash",
    "content": "pt_testcase_begin\npt_add_fifo \"$main_fifo_file\"\npt_write_widget_file <<__EOF__\nf = assert(io.open('$main_fifo_file', 'w'))\nf:setvbuf('line')\nf:write('init\\n')\n\nwidget = {\n    plugin = '$PT_BUILD_DIR/plugins/pipev2/plugin-pipev2.so',\n    opts = {\n        argv = {'/usr/bin/env', 'bash', '-c', 'for (( i=0; ; ++i)); do echo \\$i; sleep 1; done'},\n        greet = true,\n        bye = true,\n    },\n    cb = function(t)\n        if t.what == 'line' then\n            f:write('cb line ' .. t.line .. '\\n')\n            if t.line == '3' then\n                luastatus.plugin.kill('SIGKILL')\n            end\n        else\n            f:write('cb ' .. t.what .. '\\n')\n        end\n    end,\n}\n__EOF__\npt_spawn_luastatus\nexec {pfd}<\"$main_fifo_file\"\npt_expect_line 'init' <&$pfd\npt_expect_line 'cb hello' <&$pfd\npt_expect_line 'cb line 0' <&$pfd\npt_expect_line 'cb line 1' <&$pfd\npt_expect_line 'cb line 2' <&$pfd\npt_expect_line 'cb line 3' <&$pfd\npt_expect_line 'cb bye' <&$pfd\n\npt_close_fd \"$pfd\"\npt_testcase_end\n"
  },
  {
    "path": "tests/pt_tests/plugin-pipev2/07-kill-by-signo.lib.bash",
    "content": "pt_testcase_begin\npt_add_fifo \"$main_fifo_file\"\npt_write_widget_file <<__EOF__\nf = assert(io.open('$main_fifo_file', 'w'))\nf:setvbuf('line')\nf:write('init\\n')\n\nwidget = {\n    plugin = '$PT_BUILD_DIR/plugins/pipev2/plugin-pipev2.so',\n    opts = {\n        argv = {'/usr/bin/env', 'bash', '-c', 'for (( i=0; ; ++i)); do echo \\$i; sleep 1; done'},\n        greet = true,\n        bye = true,\n    },\n    cb = function(t)\n        if t.what == 'line' then\n            f:write('cb line ' .. t.line .. '\\n')\n            if t.line == '3' then\n                luastatus.plugin.kill(9)\n            end\n        else\n            f:write('cb ' .. t.what .. '\\n')\n        end\n    end,\n}\n__EOF__\npt_spawn_luastatus\nexec {pfd}<\"$main_fifo_file\"\npt_expect_line 'init' <&$pfd\npt_expect_line 'cb hello' <&$pfd\npt_expect_line 'cb line 0' <&$pfd\npt_expect_line 'cb line 1' <&$pfd\npt_expect_line 'cb line 2' <&$pfd\npt_expect_line 'cb line 3' <&$pfd\npt_expect_line 'cb bye' <&$pfd\n\npt_close_fd \"$pfd\"\npt_testcase_end\n"
  },
  {
    "path": "tests/pt_tests/plugin-pipev2/08-sigrt-bounds.lib.bash",
    "content": "pt_testcase_begin\npt_add_fifo \"$main_fifo_file\"\npt_write_widget_file <<__EOF__\nf = assert(io.open('$main_fifo_file', 'w'))\nf:setvbuf('line')\nf:write('init\\n')\n\nwidget = {\n    plugin = '$PT_BUILD_DIR/plugins/pipev2/plugin-pipev2.so',\n    opts = {\n        argv = {'/bin/sh', '-c', 'exit 0'},\n        greet = true,\n    },\n    cb = function(t)\n        if t.what == 'hello' then\n            min, max = luastatus.plugin.get_sigrt_bounds()\n            assert(min < max)\n            print(string.format('sigrt bounds: min=%d, max=%d', min, max))\n            f:write('ok\\n')\n        end\n    end,\n}\n__EOF__\npt_spawn_luastatus\nexec {pfd}<\"$main_fifo_file\"\npt_expect_line 'init' <&$pfd\npt_expect_line 'ok' <&$pfd\n\npt_close_fd \"$pfd\"\npt_testcase_end\n"
  },
  {
    "path": "tests/pt_tests/plugin-pulse/00-common.lib.bash",
    "content": "main_fifo_file=./tmp-fifo-main\nsink_name='PX7NE64ItQVFBw2'\n\nx_pulse_begin() {\n    pt_dbus_daemon_spawn --session\n    pt_pulseaudio_daemon_spawn\n}\n\nx_pulse_end() {\n    pt_pulseaudio_daemon_kill\n    pt_dbus_daemon_kill\n}\n"
  },
  {
    "path": "tests/pt_tests/plugin-pulse/01-simple.lib.bash",
    "content": "pt_require_tools pacmd\n\nx_pulse_begin\n\npt_testcase_begin\n\npt_spawn_thing_pipe pulsetalker \"$PT_SOURCE_DIR\"/tests/pulsetalker.sh \"$sink_name\"\npt_expect_line 'ready' <&${PT_SPAWNED_THINGS_FDS_0[pulsetalker]}\n\npt_check pacmd set-sink-mute \"$sink_name\" false\npt_check pacmd set-sink-volume \"$sink_name\" 65536\n\npt_add_fifo \"$main_fifo_file\"\npt_write_widget_file <<__EOF__\nf = assert(io.open('$main_fifo_file', 'w'))\nf:setvbuf('line')\nf:write('init\\n')\nlocal last_line = nil\nwidget = {\n    plugin = '$PT_BUILD_DIR/plugins/pulse/plugin-pulse.so',\n    opts = {\n        sink = '$sink_name',\n    },\n    cb = function(t)\n        assert(t)\n        local line = string.format('[%s] cb %.0f%%', t.name, t.cur / t.norm * 100)\n        if t.mute then\n            line = line .. ' (mute)'\n        end\n        if line ~= last_line then\n            f:write(line .. '\\n')\n        end\n        last_line = line\n    end,\n}\n__EOF__\npt_spawn_luastatus\nexec {pfd}<\"$main_fifo_file\"\npt_expect_line 'init' <&$pfd\npt_expect_line \"[$sink_name] cb 100%\" <&$pfd\n\npacmd set-sink-volume \"$sink_name\" 32768\npt_expect_line \"[$sink_name] cb 50%\" <&$pfd\n\npacmd set-sink-mute \"$sink_name\" true\npt_expect_line \"[$sink_name] cb 50% (mute)\" <&$pfd\n\npacmd set-sink-volume \"$sink_name\" 2048\npt_expect_line \"[$sink_name] cb 3% (mute)\" <&$pfd\n\npt_close_fd \"$pfd\"\npt_testcase_end\n\nx_pulse_end\n\n"
  },
  {
    "path": "tests/pt_tests/plugin-pulse/02-lfuncs.lib.bash",
    "content": "pt_require_tools pacmd\n\nx_pulse_begin\n\npt_testcase_begin\n\npt_spawn_thing_pipe pulsetalker \"$PT_SOURCE_DIR\"/tests/pulsetalker.sh \"$sink_name\"\npt_expect_line 'ready' <&${PT_SPAWNED_THINGS_FDS_0[pulsetalker]}\n\npt_check pacmd set-sink-mute \"$sink_name\" false || exit $?\npt_check pacmd set-sink-volume \"$sink_name\" 65536 || exit $?\n\npt_add_fifo \"$main_fifo_file\"\npt_write_widget_file <<__EOF__\nf = assert(io.open('$main_fifo_file', 'w'))\nf:setvbuf('line')\nf:write('init\\n')\nlocal last_line = nil\nwidget = {\n    plugin = '$PT_BUILD_DIR/plugins/pulse/plugin-pulse.so',\n    opts = {\n        sink = '$sink_name',\n        make_self_pipe = true,\n    },\n    cb = function(t)\n        if t == nil then\n            f:write('cb nil\\n')\n            return\n        end\n        local line = string.format('cb %.0f%%', t.cur / t.norm * 100)\n        if t.mute then\n            line = line .. ' (mute)'\n        end\n        if line ~= last_line then\n            f:write(line .. '\\n')\n        end\n        last_line = line\n        luastatus.plugin.wake_up()\n    end,\n}\n__EOF__\npt_spawn_luastatus\nexec {pfd}<\"$main_fifo_file\"\npt_expect_line 'init' <&$pfd\n\npt_expect_line 'cb 100%' <&$pfd\npt_expect_line 'cb nil' <&$pfd\n\npacmd set-sink-volume \"$sink_name\" 32768\npt_expect_line 'cb 50%' <&$pfd\npt_expect_line 'cb nil' <&$pfd\n\npacmd set-sink-mute \"$sink_name\" true\npt_expect_line 'cb 50% (mute)' <&$pfd\npt_expect_line 'cb nil' <&$pfd\n\npacmd set-sink-volume \"$sink_name\" 2048\npt_expect_line 'cb 3% (mute)' <&$pfd\npt_expect_line 'cb nil' <&$pfd\n\npt_close_fd \"$pfd\"\npt_testcase_end\n\nx_pulse_end\n"
  },
  {
    "path": "tests/pt_tests/plugin-temperature-linux/00-common.lib.bash",
    "content": "pt_require_tools mktemp\n\nmain_fifo_file=./tmp-fifo-main\n\ntemperature_linux_add_dir() {\n    local path=$1\n\n    mkdir -- \"$path\" || pt_fail \"cannot create directory '$path'\"\n    pt_add_dir_to_remove \"$path\"\n}\n\ntemperature_linux_add_file() {\n    local path=$1\n    local content=$2\n\n    printf '%s\\n' \"$content\" > \"$path\" || pt_fail \"cannot write file '$path'\"\n    pt_add_file_to_remove \"$path\"\n}\n\ntemperature_linux_create_carcass() {\n    local tmp_root=$1\n    local carcass=$2\n\n    mkdir \"$tmp_root\"/hwmon || pt_fail \"cannot create hwmon dir ($tmp_root/hwmon)\"\n    mkdir \"$tmp_root\"/thermal || pt_fail \"cannot create thermal dir ($tmp_root/thermal)\"\n\n    local ftype\n    local path\n    local content\n    local dont_care_1\n    local dont_care_2\n    while read ftype path content dont_care_1 dont_care_2; do\n        if [[ $ftype == D ]]; then\n            temperature_linux_add_dir \"$tmp_root\"/\"$path\"\n        elif [[ $ftype == F ]]; then\n            temperature_linux_add_file \"$tmp_root\"/\"$path\" \"$content\"\n        else\n            pt_fail \"Unexpected ftype '$ftype'\"\n        fi\n    done < <(printf '%s\\n' \"$carcass\" | sed '/^$/d')\n\n    pt_add_dir_to_remove \"$tmp_root\"/hwmon\n    pt_add_dir_to_remove \"$tmp_root\"/thermal\n}\n\ntemperature_linux_make_awk_program() {\n    local filter_regex=$1\n    cat <<EOF\n{\n    ftype = \\$1\n    path = \\$2\n    content = \\$3\n    kind = \\$4\n    name = \\$5\n    if (ftype != \"F\") { next }\n    if (kind == \"_\") { next }\n\n    ident = kind \":\" name\n    if (!(ident ~ /$filter_regex/)) { next }\n\n    print path, content, kind, name\n}\nEOF\n}\n\ntemperature_linux_make_expect_str() {\n    local carcass=$1\n    local filter_regex=$2\n\n    local awk_program=$(temperature_linux_make_awk_program \"$filter_regex\")\n\n    local res='cb'\n\n    local path\n    local content\n    local kind\n    local name\n    while read path content kind name; do\n        res+=\" (kind=>$kind, name=>$name, value=>$content)\"\n    done < <(\n        printf '%s' \"$carcass\" \\\n            | awk \"$awk_program\" \\\n            | LC_ALL=C sort -k1,1\n    )\n\n    printf '%s\\n' \"$res\"\n}\n\ntemperature_linux_testcase() {\n    local carcass=$1\n    local filter_func=${2:-nil}\n    local filter_regex=${3:-'^.*$'}\n\n    local expect_str=$(temperature_linux_make_expect_str \"$carcass\" \"$filter_regex\") \\\n        || pt_fail 'temperature_linux_make_expect_str failed'\n\n    pt_testcase_begin\n    pt_add_fifo \"$main_fifo_file\"\n    local tmp_root; tmp_root=$(mktemp -d) || pt_fail \"'mktemp -d' failed\"\n\n    temperature_linux_create_carcass \"$tmp_root\" \"$carcass\" \\\n        || pt_fail 'temperature_linux_create_carcass failed'\n\n    pt_add_dir_to_remove \"$tmp_root\"\n\n    pt_write_widget_file <<__EOF__\nf = assert(io.open('$main_fifo_file', 'w'))\nf:setvbuf('line')\nf:write('init\\n')\nfunction my_event_func() end\nx = dofile('$PT_SOURCE_DIR/plugins/temperature-linux/temperature-linux.lua')\n_data = {\n    _thermal_path = '$tmp_root/thermal',\n    _hwmon_path = '$tmp_root/hwmon',\n    filter_func = $filter_func,\n}\n_opts = {\n    cb = function(t)\n        if not t then\n            f:write('cb nil\\n')\n        end\n\n        f:write('cb')\n\n        table.sort(t, function(a, b)\n            return a.path < b.path\n        end)\n\n        for _, x in ipairs(t) do\n            f:write(string.format(\n                \" (kind=>%s, name=>%s, value=>%.0f)\",\n                x.kind,\n                x.name,\n                x.value * 1000\n            ))\n        end\n\n        f:write('\\n')\n    end,\n    event = my_event_func,\n}\nwidget = x.widget(_opts, _data)\nwidget.plugin = ('$PT_BUILD_DIR/plugins/{}/plugin-{}.so'):gsub('{}', widget.plugin)\nassert(widget.event == my_event_func)\n__EOF__\n\n    pt_spawn_luastatus\n    exec {pfd}<\"$main_fifo_file\"\n    pt_expect_line 'init' <&$pfd\n\n    pt_expect_line \"$expect_str\" <&$pfd\n\n    pt_close_fd \"$pfd\"\n    pt_testcase_end\n}\n"
  },
  {
    "path": "tests/pt_tests/plugin-temperature-linux/01-common-carcass.lib.bash",
    "content": "### --- Generated with: ---\n###########################\n# (\n#     set -e\n#\n#     echo_D() {\n#         local path=$1\n#         if [ -e \"$path\" ]; then\n#             echo \"D $path _ _ _\"\n#         fi\n#     }\n#\n#     echo_F() {\n#         local path=$1\n#         local kind=$2\n#         local name=$3\n#         if [ -e \"$path\" ]; then\n#             echo \"F $path $(cat < \"$path\") $kind $name\"\n#         fi\n#     }\n#\n#     cd /sys/class\n#\n#     for d in thermal/thermal_zone*; do\n#         echo_D \"$d\"\n#         echo_F \"$d\"/temp thermal \"${d##*/}\"\n#     done\n#\n#     for d in hwmon/*; do\n#         echo_D \"$d\"\n#         echo_F \"$d\"/name _ _\n#         IFS= read -r name < \"$d\"/name || continue\n#         for f in \"$d\"/temp*_input; do\n#             echo_F \"$f\" hwmon \"$name\"\n#         done\n#     done\n# )\n\ncommon_carcass=\"\\\nD thermal/thermal_zone0 _ _ _\nF thermal/thermal_zone0/temp 43000 thermal thermal_zone0\nD thermal/thermal_zone1 _ _ _\nF thermal/thermal_zone1/temp 20000 thermal thermal_zone1\nD thermal/thermal_zone2 _ _ _\nF thermal/thermal_zone2/temp 37050 thermal thermal_zone2\nD thermal/thermal_zone3 _ _ _\nF thermal/thermal_zone3/temp 42050 thermal thermal_zone3\nD thermal/thermal_zone4 _ _ _\nF thermal/thermal_zone4/temp 40050 thermal thermal_zone4\nD thermal/thermal_zone5 _ _ _\nF thermal/thermal_zone5/temp 50 thermal thermal_zone5\nD thermal/thermal_zone6 _ _ _\nF thermal/thermal_zone6/temp 50 thermal thermal_zone6\nD thermal/thermal_zone7 _ _ _\nF thermal/thermal_zone7/temp 32650 thermal thermal_zone7\nD thermal/thermal_zone8 _ _ _\nF thermal/thermal_zone8/temp 43050 thermal thermal_zone8\nD thermal/thermal_zone9 _ _ _\nF thermal/thermal_zone9/temp 43000 thermal thermal_zone9\nD hwmon/hwmon0 _ _ _\nF hwmon/hwmon0/name acpitz _ _\nF hwmon/hwmon0/temp1_input 43000 hwmon acpitz\nD hwmon/hwmon1 _ _ _\nF hwmon/hwmon1/name BAT0 _ _\nD hwmon/hwmon2 _ _ _\nF hwmon/hwmon2/name nvme _ _\nF hwmon/hwmon2/temp1_input 36850 hwmon nvme\nD hwmon/hwmon3 _ _ _\nF hwmon/hwmon3/name ADP1 _ _\nD hwmon/hwmon4 _ _ _\nF hwmon/hwmon4/name coretemp _ _\nF hwmon/hwmon4/temp1_input 42000 hwmon coretemp\nF hwmon/hwmon4/temp2_input 39000 hwmon coretemp\nF hwmon/hwmon4/temp3_input 40000 hwmon coretemp\nF hwmon/hwmon4/temp4_input 39000 hwmon coretemp\nF hwmon/hwmon4/temp5_input 39000 hwmon coretemp\n\"\n"
  },
  {
    "path": "tests/pt_tests/plugin-temperature-linux/02-simple.lib.bash",
    "content": "temperature_linux_testcase \"$common_carcass\"\n"
  },
  {
    "path": "tests/pt_tests/plugin-temperature-linux/03-filter-by-kind.lib.bash",
    "content": "temperature_linux_testcase \"$common_carcass\" \\\n    'function(kind, name) return kind == \"thermal\" end' \\\n    '^thermal:.*$'\n"
  },
  {
    "path": "tests/pt_tests/plugin-temperature-linux/04-filter-by-name.lib.bash",
    "content": "temperature_linux_testcase \"$common_carcass\" \\\n    'function(kind, name) return (name:find(\"t\")) ~= nil end' \\\n    '^.*:.*t.*$'\n"
  },
  {
    "path": "tests/pt_tests/plugin-timer/00-common.lib.bash",
    "content": "main_fifo_file=./tmp-fifo-main\nwakeup_fifo_file=./tmp-fifo-wakeup\n"
  },
  {
    "path": "tests/pt_tests/plugin-timer/01-default.lib.bash",
    "content": "pt_testcase_begin\nusing_measure\n\npt_add_fifo \"$main_fifo_file\"\npt_write_widget_file <<__EOF__\nf = assert(io.open('$main_fifo_file', 'w'))\nf:setvbuf('line')\nf:write('init\\n')\nwidget = {\n    plugin = '$PT_BUILD_DIR/plugins/timer/plugin-timer.so',\n    cb = function(t)\n        f:write('cb ' .. t .. '\\n')\n    end,\n}\n__EOF__\npt_spawn_luastatus\nexec {pfd}<\"$main_fifo_file\"\npt_expect_line 'init' <&$pfd\nmeasure_start\npt_expect_line 'cb hello' <&$pfd\nmeasure_check_ms 0\npt_expect_line 'cb timeout' <&$pfd\nmeasure_check_ms 1000\npt_expect_line 'cb timeout' <&$pfd\nmeasure_check_ms 1000\npt_close_fd \"$pfd\"\npt_testcase_end\n"
  },
  {
    "path": "tests/pt_tests/plugin-timer/02-period.lib.bash",
    "content": "pt_testcase_begin\nusing_measure\n\npt_add_fifo \"$main_fifo_file\"\npt_write_widget_file <<__EOF__\nf = assert(io.open('$main_fifo_file', 'w'))\nf:setvbuf('line')\nf:write('init\\n')\nwidget = {\n    plugin = '$PT_BUILD_DIR/plugins/timer/plugin-timer.so',\n    opts = {\n        period = 0.1,\n    },\n    cb = function(t)\n        f:write('cb ' .. t .. '\\n')\n    end,\n}\n__EOF__\npt_spawn_luastatus\nexec {pfd}<\"$main_fifo_file\"\npt_expect_line 'init' <&$pfd\npt_expect_line 'cb hello' <&$pfd\nmeasure_start\npt_expect_line 'cb timeout' <&$pfd\nmeasure_check_ms 100\npt_expect_line 'cb timeout' <&$pfd\nmeasure_check_ms 100\npt_close_fd \"$pfd\"\npt_testcase_end\n"
  },
  {
    "path": "tests/pt_tests/plugin-timer/03-wakeup-fifo.lib.bash",
    "content": "pt_testcase_begin\nusing_measure\n\npt_add_fifo \"$main_fifo_file\"\npt_add_fifo \"$wakeup_fifo_file\"\npt_write_widget_file <<__EOF__\nf = assert(io.open('$main_fifo_file', 'w'))\nf:setvbuf('line')\nf:write('init\\n')\nwidget = {\n    plugin = '$PT_BUILD_DIR/plugins/timer/plugin-timer.so',\n    opts = {\n        fifo = '$wakeup_fifo_file',\n    },\n    cb = function(t)\n        f:write('cb ' .. t .. '\\n')\n    end,\n}\n__EOF__\npt_spawn_luastatus\nexec {pfd}<\"$main_fifo_file\"\npt_expect_line 'init' <&$pfd\nmeasure_start\npt_expect_line 'cb hello' <&$pfd\nmeasure_check_ms 0\npt_expect_line 'cb timeout' <&$pfd\nmeasure_check_ms 1000\ntouch \"$wakeup_fifo_file\"\npt_expect_line 'cb fifo' <&$pfd\nmeasure_check_ms 0\npt_expect_line 'cb timeout' <&$pfd\nmeasure_check_ms 1000\npt_expect_line 'cb timeout' <&$pfd\nmeasure_check_ms 1000\npt_close_fd \"$pfd\"\npt_testcase_end\n"
  },
  {
    "path": "tests/pt_tests/plugin-timer/04-lfuncs.lib.bash",
    "content": "pt_testcase_begin\nusing_measure\n\npt_add_fifo \"$main_fifo_file\"\npt_write_widget_file <<__EOF__\nf = assert(io.open('$main_fifo_file', 'w'))\nf:setvbuf('line')\nf:write('init\\n')\nn=0\nwidget = {\n    plugin = '$PT_BUILD_DIR/plugins/timer/plugin-timer.so',\n    opts = {\n        period = 0.1,\n    },\n    cb = function(t)\n        if t == 'timeout' then\n            n = (n+1)%3\n            f:write('cb timeout n=' .. n .. '\\n')\n            if n == 0 then\n                luastatus.plugin.push_period(0.6)\n            end\n        else\n            f:write('cb ' .. t .. '\\n')\n        end\n    end,\n}\n__EOF__\npt_spawn_luastatus\nexec {pfd}<\"$main_fifo_file\"\npt_expect_line 'init' <&$pfd\nmeasure_start\npt_expect_line 'cb hello' <&$pfd\nmeasure_check_ms 0\npt_expect_line 'cb timeout n=1' <&$pfd\nmeasure_check_ms 100\nfor (( i = 0; i < 3; ++i )); do\n    pt_expect_line 'cb timeout n=2' <&$pfd\n    measure_check_ms 100\n    pt_expect_line 'cb timeout n=0' <&$pfd\n    measure_check_ms 100\n    pt_expect_line 'cb timeout n=1' <&$pfd\n    measure_check_ms 600\ndone\npt_close_fd \"$pfd\"\npt_testcase_end\n"
  },
  {
    "path": "tests/pt_tests/plugin-timer/05-procalive-lfuncs.lib.bash",
    "content": "pt_testcase_begin\nusing_measure\n\npt_add_fifo \"$main_fifo_file\"\npt_write_widget_file <<__EOF__\nf = assert(io.open('$main_fifo_file', 'w'))\nf:setvbuf('line')\nf:write('init\\n')\n\nfunction check_eq(a, b)\n    if a ~= b then\n        print(a, '!=', b)\n        error('check_eq() failed')\n    end\nend\n\nfunction array_contains(t, v)\n    for _, x in ipairs(t) do\n        if x == v then\n            return true\n        end\n    end\n    return false\nend\n\n-- this function converts return list with multiple values into a _S_ingle value\nfunction S(x)\n    return x\nend\n\nwidget = {\n    plugin = '$PT_BUILD_DIR/plugins/timer/plugin-timer.so',\n    opts = {\n        period = 1.0,\n    },\n    cb = function(t)\n        if t ~= 'hello' then\n            return\n        end\n\n        check_eq(S(luastatus.plugin.access('.')), true)\n        check_eq(S(luastatus.plugin.access('./gbdc06iiw1kZ4yccnnx9')), false)\n\n        check_eq(S(luastatus.plugin.stat('.')), 'dir')\n        check_eq(S(luastatus.plugin.stat('/proc/uptime')), 'regular')\n        check_eq(S(luastatus.plugin.stat('./gbdc06iiw1kZ4yccnnx9')), nil)\n\n        assert(array_contains(S(luastatus.plugin.glob('./*')), './pt.sh'))\n\n        assert(luastatus.plugin.is_process_alive($$))\n        assert(luastatus.plugin.is_process_alive(\"$$\"))\n\n        assert(luastatus.plugin.is_process_alive(1))\n        assert(luastatus.plugin.is_process_alive(\"1\"))\n\n        while true do\n            local random_pid = math.random(2, 1073741823)\n            print('trying random_pid=' .. random_pid)\n            if not luastatus.plugin.is_process_alive(random_pid) then\n                break\n            end\n            print('it is alive, retrying')\n        end\n\n        f:write('ok\\n')\n    end,\n}\n__EOF__\npt_spawn_luastatus\nexec {pfd}<\"$main_fifo_file\"\npt_expect_line 'init' <&$pfd\n\npt_expect_line 'ok' <&$pfd\n\npt_close_fd \"$pfd\"\npt_testcase_end\n"
  },
  {
    "path": "tests/pt_tests/plugin-timer/06-self-pipe.lib.bash",
    "content": "pt_testcase_begin\n\npt_add_fifo \"$main_fifo_file\"\npt_write_widget_file <<__EOF__\nf = assert(io.open('$main_fifo_file', 'w'))\nf:setvbuf('line')\nf:write('init\\n')\nwidget = {\n    plugin = '$PT_BUILD_DIR/plugins/timer/plugin-timer.so',\n    opts = {\n        period = 0.1,\n        make_self_pipe = true,\n    },\n    cb = function(t)\n        f:write('cb ' .. t .. '\\n')\n        if t == 'timeout' then\n            luastatus.plugin.wake_up()\n        end\n    end,\n}\n__EOF__\n\npt_spawn_luastatus\nexec {pfd}<\"$main_fifo_file\"\n\npt_expect_line 'init' <&$pfd\npt_expect_line 'cb hello' <&$pfd\npt_expect_line 'cb timeout' <&$pfd\npt_expect_line 'cb self_pipe' <&$pfd\npt_expect_line 'cb timeout' <&$pfd\npt_expect_line 'cb self_pipe' <&$pfd\npt_close_fd \"$pfd\"\n\npt_testcase_end\n"
  },
  {
    "path": "tests/pt_tests/plugin-unixsock/00-common.lib.bash",
    "content": "main_fifo_file=./tmp-fifo-main\nsocket_file=./pt-test-socket\n\nunixsock_wait_socket() {\n    echo >&2 \"Waiting for socket $socket_file to appear...\"\n    while ! [[ -S $socket_file ]]; do\n        true\n    done\n}\n\nunixsock_send_verbatim() {\n    printf '%s' \"$1\" | \"$PT_PARROT\" UNIX-CLIENT \"$socket_file\"\n}\n"
  },
  {
    "path": "tests/pt_tests/plugin-unixsock/01-simple.lib.bash",
    "content": "pt_testcase_begin\nrm -f \"$socket_file\"\npt_add_fifo \"$main_fifo_file\"\npt_add_file_to_remove \"$socket_file\"\npt_write_widget_file <<__EOF__\nf = assert(io.open('$main_fifo_file', 'w'))\nf:setvbuf('line')\nf:write('init\\n')\nwidget = {\n    plugin = '$PT_BUILD_DIR/plugins/unixsock/plugin-unixsock.so',\n    opts = {\n        path = '$socket_file',\n    },\n    cb = function(t)\n        assert(t.what == 'line')\n        f:write('line ' .. t.line .. '\\n')\n    end,\n}\n__EOF__\npt_spawn_luastatus\nexec {pfd}<\"$main_fifo_file\"\npt_expect_line 'init' <&$pfd\nunixsock_wait_socket\nunixsock_send_verbatim $'one\\n'\npt_expect_line 'line one' <&$pfd\nunixsock_send_verbatim $'two\\n'\npt_expect_line 'line two' <&$pfd\nunixsock_send_verbatim 'without newline'\nunixsock_send_verbatim $'with newline\\n'\npt_expect_line 'line with newline' <&$pfd\nunixsock_send_verbatim $'aypibmmwxrdxdknh\\ngmstvxwxamouhlmw\\ncybymucjtfxrauwn'\npt_expect_line 'line aypibmmwxrdxdknh' <&$pfd\npt_close_fd \"$pfd\"\npt_testcase_end\n"
  },
  {
    "path": "tests/pt_tests/plugin-unixsock/02-greet.lib.bash",
    "content": "pt_testcase_begin\nrm -f \"$socket_file\"\npt_add_fifo \"$main_fifo_file\"\npt_add_file_to_remove \"$socket_file\"\npt_write_widget_file <<__EOF__\nf = assert(io.open('$main_fifo_file', 'w'))\nf:setvbuf('line')\nf:write('init\\n')\nwidget = {\n    plugin = '$PT_BUILD_DIR/plugins/unixsock/plugin-unixsock.so',\n    opts = {\n        path = '$socket_file',\n        greet = true,\n    },\n    cb = function(t)\n        if t.what == 'line' then\n            f:write('line ' .. t.line .. '\\n')\n        else\n            f:write(t.what .. '\\n')\n        end\n    end,\n}\n__EOF__\npt_spawn_luastatus\nexec {pfd}<\"$main_fifo_file\"\npt_expect_line 'init' <&$pfd\nunixsock_wait_socket\npt_expect_line 'hello' <&$pfd\nunixsock_send_verbatim $'one\\n'\npt_expect_line 'line one' <&$pfd\nunixsock_send_verbatim $'two\\n'\npt_expect_line 'line two' <&$pfd\nunixsock_send_verbatim $'three\\n'\npt_expect_line 'line three' <&$pfd\npt_close_fd \"$pfd\"\npt_testcase_end\n"
  },
  {
    "path": "tests/pt_tests/plugin-unixsock/03-do-not-try-unlink-failure.lib.bash",
    "content": "pt_testcase_begin\nrm -f \"$socket_file\"\ntrue > \"$socket_file\" || pt_fail \"Cannot create regular file $socket_file.\"\npt_add_file_to_remove \"$socket_file\"\npt_write_widget_file <<__EOF__\nwidget = {\n    plugin = '$PT_BUILD_DIR/plugins/unixsock/plugin-unixsock.so',\n    opts = {\n        path = '$socket_file',\n        try_unlink = false,\n    },\n    cb = function(t) end,\n}\n__EOF__\npt_spawn_luastatus -e\npt_wait_luastatus || pt_fail \"luastatus exited with non-zero code $?\"\npt_testcase_end\n"
  },
  {
    "path": "tests/pt_tests/plugin-unixsock/04-do-try-unlink-success.lib.bash",
    "content": "pt_testcase_begin\nrm -f \"$socket_file\"\npt_add_fifo \"$main_fifo_file\"\ntrue > \"$socket_file\" || pt_fail \"Cannot create regular file $socket_file.\"\npt_add_file_to_remove \"$socket_file\"\npt_write_widget_file <<__EOF__\nf = assert(io.open('$main_fifo_file', 'w'))\nf:setvbuf('line')\nf:write('init\\n')\nwidget = {\n    plugin = '$PT_BUILD_DIR/plugins/unixsock/plugin-unixsock.so',\n    opts = {\n        path = '$socket_file',\n    },\n    cb = function(t)\n        assert(t.what == 'line')\n        f:write('line ' .. t.line .. '\\n')\n    end,\n}\n__EOF__\npt_spawn_luastatus\nexec {pfd}<\"$main_fifo_file\"\npt_expect_line 'init' <&$pfd\nunixsock_wait_socket\nunixsock_send_verbatim $'aloha\\n'\npt_expect_line 'line aloha' <&$pfd\npt_close_fd \"$pfd\"\npt_testcase_end\n"
  },
  {
    "path": "tests/pt_tests/plugin-unixsock/05-timeout.lib.bash",
    "content": "pt_testcase_begin\nrm -f \"$socket_file\"\nusing_measure\npt_add_fifo \"$main_fifo_file\"\npt_add_file_to_remove \"$socket_file\"\npt_write_widget_file <<__EOF__\nf = assert(io.open('$main_fifo_file', 'w'))\nf:setvbuf('line')\nf:write('init\\n')\nwidget = {\n    plugin = '$PT_BUILD_DIR/plugins/unixsock/plugin-unixsock.so',\n    opts = {\n        path = '$socket_file',\n        greet = true,\n        timeout = 0.25,\n    },\n    cb = function(t)\n        if t.what == 'line' then\n            f:write('line ' .. t.line .. '\\n')\n        else\n            f:write(t.what .. '\\n')\n        end\n    end,\n}\n__EOF__\npt_spawn_luastatus\nexec {pfd}<\"$main_fifo_file\"\npt_expect_line 'init' <&$pfd\nunixsock_wait_socket\npt_expect_line 'hello' <&$pfd\nmeasure_start\npt_expect_line 'timeout' <&$pfd\nmeasure_check_ms 250\npt_expect_line 'timeout' <&$pfd\nmeasure_check_ms 250\nunixsock_send_verbatim $'boo\\n'\npt_expect_line 'line boo' <&$pfd\nmeasure_check_ms 0\npt_expect_line 'timeout' <&$pfd\nmeasure_check_ms 250\npt_close_fd \"$pfd\"\npt_testcase_end\n"
  },
  {
    "path": "tests/pt_tests/plugin-unixsock/06-lfuncs.lib.bash",
    "content": "pt_testcase_begin\nrm -f \"$socket_file\"\nusing_measure\npt_add_fifo \"$main_fifo_file\"\npt_add_file_to_remove \"$socket_file\"\npt_write_widget_file <<__EOF__\nf = assert(io.open('$main_fifo_file', 'w'))\nf:setvbuf('line')\nf:write('init\\n')\nn=0\nwidget = {\n    plugin = '$PT_BUILD_DIR/plugins/unixsock/plugin-unixsock.so',\n    opts = {\n        path = '$socket_file',\n        greet = true,\n        timeout = 0.1,\n    },\n    cb = function(t)\n        if t.what == 'line' then\n            f:write('line ' .. t.line .. '\\n')\n        else\n            n=(n+1)%3\n            f:write(t.what .. ' n=' .. n .. '\\n')\n            if n == 0 then\n                luastatus.plugin.push_timeout(0.6)\n            end\n        end\n    end,\n}\n__EOF__\npt_spawn_luastatus\nexec {pfd}<\"$main_fifo_file\"\npt_expect_line 'init' <&$pfd\nunixsock_wait_socket\npt_expect_line 'hello n=1' <&$pfd\nmeasure_start\nfor (( i = 0; i < 4; ++i )); do\n    unixsock_send_verbatim \"foobar$i\"$'\\n'\n    pt_expect_line \"line foobar$i\" <&$pfd\n    measure_check_ms 0\n    pt_expect_line 'timeout n=2' <&$pfd\n    measure_check_ms 100\n    pt_expect_line 'timeout n=0' <&$pfd\n    measure_check_ms 100\n    pt_expect_line 'timeout n=1' <&$pfd\n    measure_check_ms 600\ndone\npt_close_fd \"$pfd\"\npt_testcase_end\n"
  },
  {
    "path": "tests/pt_tests/plugin-web/00-common.lib.bash",
    "content": "main_fifo_file=./tmp-fifo-main\n\npt_find_free_tcp_port\nport=$PT_FOUND_FREE_PORT\n\nhttpserv_spawn() {\n    pt_spawn_thing_pipe httpserv \"$PT_HTTPSERV\" --port=\"$port\" \"$@\"\n    pt_expect_line 'ready' <&${PT_SPAWNED_THINGS_FDS_0[httpserv]}\n}\n\nhttpserv_expect() {\n    pt_expect_line \"$1\" <&${PT_SPAWNED_THINGS_FDS_0[httpserv]}\n}\n\nhttpserv_say() {\n    printf '%s\\n' \"$1\" >&${PT_SPAWNED_THINGS_FDS_1[httpserv]}\n}\n\nhttpserv_kill() {\n    pt_kill_thing httpserv\n}\n\nhttpserv_wait() {\n    pt_wait_thing httpserv || true\n}\n"
  },
  {
    "path": "tests/pt_tests/plugin-web/01-simple.lib.bash",
    "content": "pt_testcase_begin\n\nhttpserv_spawn GET /\n\npt_add_fifo \"$main_fifo_file\"\npt_write_widget_file <<__EOF__\nf = assert(io.open('$main_fifo_file', 'w'))\nf:setvbuf('line')\nf:write('init\\n')\nwidget = {\n    plugin = '$PT_BUILD_DIR/plugins/web/plugin-web.so',\n    opts = {\n        planner = function()\n            while true do\n                coroutine.yield({\n                    action = 'request',\n                    params = {\n                        url = 'http://127.0.0.1:$port/',\n                    },\n                })\n                coroutine.yield({action = 'sleep', period = 1.0})\n            end\n        end,\n    },\n    cb = function(t)\n        assert(t.what == 'response')\n        if t.error then\n            error('got error: ' .. t.error)\n        end\n        f:write('resp ' .. t.body .. '\\n')\n    end,\n}\n__EOF__\npt_spawn_luastatus\nexec {pfd}<\"$main_fifo_file\"\npt_expect_line 'init' <&$pfd\n\nhttpserv_expect '>'\nhttpserv_say 'RESPONSE'\npt_expect_line 'resp RESPONSE' <&$pfd\n\npt_close_fd \"$pfd\"\npt_testcase_end\n"
  },
  {
    "path": "tests/pt_tests/plugin-web/02-post.lib.bash",
    "content": "pt_testcase_begin\n\nhttpserv_spawn POST /\n\npt_add_fifo \"$main_fifo_file\"\npt_write_widget_file <<__EOF__\nf = assert(io.open('$main_fifo_file', 'w'))\nf:setvbuf('line')\nf:write('init\\n')\nwidget = {\n    plugin = '$PT_BUILD_DIR/plugins/web/plugin-web.so',\n    opts = {\n        planner = function()\n            while true do\n                coroutine.yield({\n                    action = 'request',\n                    params = {\n                        url = 'http://127.0.0.1:$port/',\n                        post_fields = 'test=TEST',\n                    },\n                })\n                coroutine.yield({action = 'sleep', period = 1.0})\n            end\n        end,\n    },\n    cb = function(t)\n        assert(t.what == 'response')\n        f:write('resp ' .. t.body .. '\\n')\n    end,\n}\n__EOF__\npt_spawn_luastatus\nexec {pfd}<\"$main_fifo_file\"\npt_expect_line 'init' <&$pfd\n\nhttpserv_expect '>test=TEST'\nhttpserv_say 'RESPONSE'\npt_expect_line 'resp RESPONSE' <&$pfd\n\npt_close_fd \"$pfd\"\npt_testcase_end\n"
  },
  {
    "path": "tests/pt_tests/plugin-web/03-call-cb.lib.bash",
    "content": "pt_testcase_begin\npt_add_fifo \"$main_fifo_file\"\npt_write_widget_file <<__EOF__\nf = assert(io.open('$main_fifo_file', 'w'))\nf:setvbuf('line')\nf:write('init\\n')\nwidget = {\n    plugin = '$PT_BUILD_DIR/plugins/web/plugin-web.so',\n    opts = {\n        planner = function()\n            while true do\n                coroutine.yield({action = 'call_cb', what = 'hi there'})\n                coroutine.yield({action = 'sleep', period = 0.1})\n            end\n        end,\n    },\n    cb = function(t)\n        assert(t.what == 'hi there')\n        f:write(t.what .. '\\n')\n    end,\n}\n__EOF__\npt_spawn_luastatus\nexec {pfd}<\"$main_fifo_file\"\npt_expect_line 'init' <&$pfd\n\nfor (( i = 0; i < 5; ++i )); do\n    pt_expect_line 'hi there' <&$pfd\ndone\n\npt_close_fd \"$pfd\"\npt_testcase_end\n"
  },
  {
    "path": "tests/pt_tests/plugin-web/04-with-headers.lib.bash",
    "content": "pt_testcase_begin\n\nhttpserv_spawn GET /\n\npt_add_fifo \"$main_fifo_file\"\npt_write_widget_file <<__EOF__\nf = assert(io.open('$main_fifo_file', 'w'))\nf:setvbuf('line')\nf:write('init\\n')\n\nlocal function strip(header)\n    local res, _ = header:gsub('[\\r\\n]', '')\n    return res\nend\n\nwidget = {\n    plugin = '$PT_BUILD_DIR/plugins/web/plugin-web.so',\n    opts = {\n        planner = function()\n            while true do\n                coroutine.yield({\n                    action = 'request',\n                    params = {\n                        url = 'http://127.0.0.1:$port/',\n                        with_headers = true,\n                    },\n                })\n                coroutine.yield({action = 'sleep', period = 1.0})\n            end\n        end,\n    },\n    cb = function(t)\n        assert(t.what == 'response')\n        print('Total # of headers:', #t.headers)\n        for _, header in ipairs(t.headers) do\n            print('Header:', strip(header))\n            if header:lower() == 'content-type: text/plain\\r\\n' then\n                f:write('ok\\n')\n                return\n            end\n        end\n        f:write('fail\\n')\n    end,\n}\n__EOF__\npt_spawn_luastatus\nexec {pfd}<\"$main_fifo_file\"\npt_expect_line 'init' <&$pfd\n\nhttpserv_expect '>'\nhttpserv_say 'RESPONSE'\npt_expect_line 'ok' <&$pfd\n\npt_close_fd \"$pfd\"\npt_testcase_end\n"
  },
  {
    "path": "tests/pt_tests/plugin-web/05-json-encode.lib.bash",
    "content": "pt_testcase_begin\npt_add_fifo \"$main_fifo_file\"\npt_write_widget_file <<__EOF__\nf = assert(io.open('$main_fifo_file', 'w'))\nf:setvbuf('line')\nf:write('init\\n')\nwidget = {\n    plugin = '$PT_BUILD_DIR/plugins/web/plugin-web.so',\n    opts = {\n        planner = function()\n            while true do\n                coroutine.yield({action = 'call_cb', what = 'test'})\n                coroutine.yield({action = 'sleep', period = 1.0})\n            end\n        end,\n    },\n    cb = function(t)\n        assert(t.what == 'test')\n\n        local r\n\n        r = luastatus.plugin.json_encode_str([[xyz\"\\\\]] .. '\\n')\n        assert(r == [[xyz\\u0022\\u005C\\u000A]])\n\n        r = luastatus.plugin.json_encode_num(0.5)\n        assert(tonumber(r) == 0.5)\n\n        f:write('ok\\n')\n    end,\n}\n__EOF__\npt_spawn_luastatus\nexec {pfd}<\"$main_fifo_file\"\npt_expect_line 'init' <&$pfd\n\npt_expect_line 'ok' <&$pfd\n\npt_close_fd \"$pfd\"\npt_testcase_end\n"
  },
  {
    "path": "tests/pt_tests/plugin-web/06-urlencode.lib.bash",
    "content": "pt_testcase_begin\npt_add_fifo \"$main_fifo_file\"\npt_write_widget_file <<__EOF__\nf = assert(io.open('$main_fifo_file', 'w'))\nf:setvbuf('line')\nf:write('init\\n')\n\nlocal function check(src, plus_encoding, expected)\n    local r = luastatus.plugin.urlencode(src, plus_encoding)\n    if r ~= expected then\n        print('Expected:', expected)\n        print('Found:', r)\n        print('plus_encoding:', plus_encoding)\n        error('check() failed')\n    end\nend\n\nwidget = {\n    plugin = '$PT_BUILD_DIR/plugins/web/plugin-web.so',\n    opts = {\n        planner = function()\n            while true do\n                coroutine.yield({action = 'call_cb', what = 'test'})\n                coroutine.yield({action = 'sleep', period = 1.0})\n            end\n        end,\n    },\n    cb = function(t)\n        assert(t.what == 'test')\n\n        check('test!foo?bar', true,  'test%21foo%3Fbar')\n        check('test!foo?bar', false, 'test%21foo%3Fbar')\n\n        check('check it', true, 'check+it')\n        check('check it', false, 'check%20it')\n\n        f:write('ok\\n')\n    end,\n}\n__EOF__\npt_spawn_luastatus\nexec {pfd}<\"$main_fifo_file\"\npt_expect_line 'init' <&$pfd\n\npt_expect_line 'ok' <&$pfd\n\npt_close_fd \"$pfd\"\npt_testcase_end\n"
  },
  {
    "path": "tests/pt_tests/plugin-web/07-urldecode.lib.bash",
    "content": "pt_testcase_begin\npt_add_fifo \"$main_fifo_file\"\npt_write_widget_file <<__EOF__\nf = assert(io.open('$main_fifo_file', 'w'))\nf:setvbuf('line')\nf:write('init\\n')\n\nlocal function check(src, expected)\n    local r = luastatus.plugin.urldecode(src)\n    if r ~= expected then\n        print('Expected:', expected)\n        print('Found:', r)\n        error('check() failed')\n    end\nend\n\nwidget = {\n    plugin = '$PT_BUILD_DIR/plugins/web/plugin-web.so',\n    opts = {\n        planner = function()\n            while true do\n                coroutine.yield({action = 'call_cb', what = 'test'})\n                coroutine.yield({action = 'sleep', period = 1.0})\n            end\n        end,\n    },\n    cb = function(t)\n        assert(t.what == 'test')\n\n        check('test', 'test')\n        check('foo+bar', 'foo bar')\n\n        check('test%20case', 'test case')\n        check('what%3F', 'what?')\n        check('what%3f', 'what?')\n\n        check('xx%xx', nil)\n        check('foo%', nil)\n        check('foo%1', nil)\n        check('foo%1x', nil)\n\n        f:write('ok\\n')\n    end,\n}\n__EOF__\npt_spawn_luastatus\nexec {pfd}<\"$main_fifo_file\"\npt_expect_line 'init' <&$pfd\n\npt_expect_line 'ok' <&$pfd\n\npt_close_fd \"$pfd\"\npt_testcase_end\n"
  },
  {
    "path": "tests/pt_tests/plugin-web/08-json-decode.lib.bash",
    "content": "pt_testcase_begin\npt_add_fifo \"$main_fifo_file\"\npt_write_widget_file <<__EOF__\nf = assert(io.open('$main_fifo_file', 'w'))\nf:setvbuf('line')\nf:write('init\\n')\n\nlocal function check_dict_impl(json_str, expected, is_marked)\n    local res, err = luastatus.plugin.json_decode(json_str, is_marked)\n    assert(type(res) == 'table')\n\n    if expected.foo ~= nil then\n        local res_key = next(res)\n        assert(res_key == 'foo')\n        assert(next(res, res_key) == nil)\n        assert(res.foo == expected.foo)\n    else\n        assert(next(res) == nil)\n    end\n\n    local mt = getmetatable(res)\n    if is_marked then\n        assert(mt ~= nil)\n        assert(mt.is_dict)\n        assert(not mt.is_array)\n    else\n        assert(mt == nil)\n    end\nend\n\nlocal function check_dict(json_str, expected)\n    check_dict_impl(json_str, expected, false)\n    check_dict_impl(json_str, expected, true)\nend\n\nlocal function check_array_impl(json_str, expected, is_marked)\n    local res, err = luastatus.plugin.json_decode(json_str, is_marked)\n    assert(type(res) == 'table')\n\n    assert(#res == #expected)\n    for i = 1, #res do\n        assert(res[i] == expected[i])\n    end\n\n    local mt = getmetatable(res)\n    if is_marked then\n        assert(mt ~= nil)\n        assert(mt.is_array)\n        assert(not mt.is_dict)\n    else\n        assert(mt == nil)\n    end\nend\n\nlocal function check_array(json_str, expected)\n    check_array_impl(json_str, expected, false)\n    check_array_impl(json_str, expected, true)\nend\n\nwidget = {\n    plugin = '$PT_BUILD_DIR/plugins/web/plugin-web.so',\n    opts = {\n        planner = function()\n            while true do\n                coroutine.yield({action = 'call_cb', what = 'test'})\n                coroutine.yield({action = 'sleep', period = 1.0})\n            end\n        end,\n    },\n    cb = function(t)\n        assert(t.what == 'test')\n\n        check_dict('{\"foo\":\"bar\"}', {foo = \"bar\"})\n        check_dict('{\"foo\":42}', {foo = 42})\n        check_dict('{\"foo\":true}', {foo = true})\n        check_dict('{\"foo\":false}', {foo = false})\n        check_dict('{\"foo\":null}', {})\n\n        check_array('[]', {})\n        check_array('[\"foo\"]', {\"foo\"})\n        check_array('[\"foo\", \"bar\"]', {\"foo\", \"bar\"})\n\n        f:write('ok\\n')\n    end,\n}\n__EOF__\npt_spawn_luastatus\nexec {pfd}<\"$main_fifo_file\"\npt_expect_line 'init' <&$pfd\n\npt_expect_line 'ok' <&$pfd\n\npt_close_fd \"$pfd\"\npt_testcase_end\n"
  },
  {
    "path": "tests/pt_tests/plugin-web/09-json-decode-null.lib.bash",
    "content": "pt_testcase_begin\npt_add_fifo \"$main_fifo_file\"\npt_write_widget_file <<__EOF__\nf = assert(io.open('$main_fifo_file', 'w'))\nf:setvbuf('line')\nf:write('init\\n')\n\nwidget = {\n    plugin = '$PT_BUILD_DIR/plugins/web/plugin-web.so',\n    opts = {\n        planner = function()\n            while true do\n                coroutine.yield({action = 'call_cb', what = 'test'})\n                coroutine.yield({action = 'sleep', period = 1.0})\n            end\n        end,\n    },\n    cb = function(t)\n        assert(t.what == 'test')\n\n        local json_str = '{\"foo\":null}'\n        local res = assert(luastatus.plugin.json_decode(json_str, false, true))\n        assert(type(res) == 'table')\n        assert(type(res.foo) == 'userdata')\n\n        f:write('ok\\n')\n    end,\n}\n__EOF__\npt_spawn_luastatus\nexec {pfd}<\"$main_fifo_file\"\npt_expect_line 'init' <&$pfd\n\npt_expect_line 'ok' <&$pfd\n\npt_close_fd \"$pfd\"\npt_testcase_end\n"
  },
  {
    "path": "tests/pt_tests/plugin-web/10-error.lib.bash",
    "content": "pt_testcase_begin\npt_add_fifo \"$main_fifo_file\"\npt_write_widget_file <<__EOF__\nf = assert(io.open('$main_fifo_file', 'w'))\nf:setvbuf('line')\nf:write('init\\n')\nwidget = {\n    plugin = '$PT_BUILD_DIR/plugins/web/plugin-web.so',\n    opts = {\n        planner = function()\n            while true do\n                coroutine.yield({\n                    action = 'request',\n                    params = {\n                        url = 'foobar:////',\n                    },\n                })\n                coroutine.yield({action = 'sleep', period = 1.0})\n            end\n        end,\n    },\n    cb = function(t)\n        assert(t.what == 'response')\n        assert(t.error)\n        local i, _ = t.error:find('libcurl error')\n        assert(i)\n        f:write('ok\\n')\n    end,\n}\n__EOF__\npt_spawn_luastatus\nexec {pfd}<\"$main_fifo_file\"\npt_expect_line 'init' <&$pfd\n\npt_expect_line 'ok' <&$pfd\n\npt_close_fd \"$pfd\"\npt_testcase_end\n"
  },
  {
    "path": "tests/pt_tests/plugin-web/11-timeout.lib.bash",
    "content": "pt_testcase_begin\n\nhttpserv_spawn --max-requests=1 --freeze-for=10000 GET /\n\npt_add_fifo \"$main_fifo_file\"\npt_write_widget_file <<__EOF__\nf = assert(io.open('$main_fifo_file', 'w'))\nf:setvbuf('line')\nf:write('init\\n')\nwidget = {\n    plugin = '$PT_BUILD_DIR/plugins/web/plugin-web.so',\n    opts = {\n        planner = function()\n            while true do\n                coroutine.yield({\n                    action = 'request',\n                    params = {\n                        url = 'http://127.0.0.1:$port/',\n                        timeout = 2.0,\n                    },\n                })\n                coroutine.yield({action = 'sleep', period = 0.1})\n            end\n        end,\n    },\n    cb = function(t)\n        assert(t.what == 'response')\n        if t.error then\n            f:write('error\\n')\n        else\n            f:write('resp ' .. t.body .. '\\n')\n        end\n    end,\n}\n__EOF__\npt_spawn_luastatus\nexec {pfd}<\"$main_fifo_file\"\npt_expect_line 'init' <&$pfd\n\nhttpserv_expect '>'\nhttpserv_say 'RESPONSE'\npt_expect_line 'resp RESPONSE' <&$pfd\n\npt_expect_line 'error' <&$pfd\n\npt_close_fd \"$pfd\"\npt_testcase_end\n"
  },
  {
    "path": "tests/pt_tests/plugin-web/12-not-avail.lib.bash",
    "content": "pt_testcase_begin\n\nhttpserv_spawn --max-requests=1 GET /\n\npt_add_fifo \"$main_fifo_file\"\npt_write_widget_file <<__EOF__\nf = assert(io.open('$main_fifo_file', 'w'))\nf:setvbuf('line')\nf:write('init\\n')\nwidget = {\n    plugin = '$PT_BUILD_DIR/plugins/web/plugin-web.so',\n    opts = {\n        planner = function()\n            while true do\n                coroutine.yield({\n                    action = 'request',\n                    params = {\n                        url = 'http://127.0.0.1:$port/',\n                        timeout = 2.0,\n                    },\n                })\n                coroutine.yield({action = 'sleep', period = 0.1})\n            end\n        end,\n    },\n    cb = function(t)\n        assert(t.what == 'response')\n        if t.error then\n            f:write('error\\n')\n        else\n            f:write('resp ' .. t.body .. '\\n')\n        end\n    end,\n}\n__EOF__\npt_spawn_luastatus\nexec {pfd}<\"$main_fifo_file\"\npt_expect_line 'init' <&$pfd\n\nhttpserv_expect '>'\nhttpserv_say 'RESPONSE'\npt_expect_line 'resp RESPONSE' <&$pfd\n\npt_expect_line 'error' <&$pfd\n\npt_close_fd \"$pfd\"\npt_testcase_end\n"
  },
  {
    "path": "tests/pt_tests/plugin-web/13-time-now.lib.bash",
    "content": "pt_testcase_begin\npt_add_fifo \"$main_fifo_file\"\npt_write_widget_file <<__EOF__\nf = assert(io.open('$main_fifo_file', 'w'))\nf:setvbuf('line')\nf:write('init\\n')\n\nwidget = {\n    plugin = '$PT_BUILD_DIR/plugins/web/plugin-web.so',\n    opts = {\n        planner = function()\n            while true do\n                coroutine.yield({action = 'call_cb', what = 'test'})\n                coroutine.yield({action = 'sleep', period = 1.0})\n            end\n        end,\n    },\n    cb = function(t)\n        assert(t.what == 'test')\n\n        local t0 = assert(luastatus.plugin.time_now())\n\n        os.execute('sleep 1')\n\n        local t1 = assert(luastatus.plugin.time_now())\n\n        assert(t1 > t0)\n\n        f:write('ok\\n')\n    end,\n}\n__EOF__\npt_spawn_luastatus\nexec {pfd}<\"$main_fifo_file\"\npt_expect_line 'init' <&$pfd\n\npt_expect_line 'ok' <&$pfd\n\npt_close_fd \"$pfd\"\npt_testcase_end\n"
  },
  {
    "path": "tests/pt_tests/plugin-web/14-get-supported-opts.lib.bash",
    "content": "pt_testcase_begin\npt_add_fifo \"$main_fifo_file\"\npt_write_widget_file <<__EOF__\nf = assert(io.open('$main_fifo_file', 'w'))\nf:setvbuf('line')\nf:write('init\\n')\n\nwidget = {\n    plugin = '$PT_BUILD_DIR/plugins/web/plugin-web.so',\n    opts = {\n        planner = function()\n            while true do\n                coroutine.yield({action = 'call_cb', what = 'test'})\n                coroutine.yield({action = 'sleep', period = 1.0})\n            end\n        end,\n    },\n    cb = function(t)\n        assert(t.what == 'test')\n\n        local t = assert(luastatus.plugin.get_supported_opts())\n        assert(t.url)\n        for k, v in pairs(t) do\n            assert(type(k) == 'string')\n            assert(v == true)\n        end\n\n        f:write('ok\\n')\n    end,\n}\n__EOF__\npt_spawn_luastatus\nexec {pfd}<\"$main_fifo_file\"\npt_expect_line 'init' <&$pfd\n\npt_expect_line 'ok' <&$pfd\n\npt_close_fd \"$pfd\"\npt_testcase_end\n"
  },
  {
    "path": "tests/pulsetalker.sh",
    "content": "#!/usr/bin/env bash\n\nset -e\n\nif (( $# == 1 )); then\n    sink_name=$1\n    pactl_opts=()\nelif (( $# == 2 )); then\n    sink_name=$1\n    server_name=$2\n    pactl_opts=( --server=\"$server_name\" )\nelse\n    echo >&2 \"USAGE: $0 SINK_NAME [SERVER_NAME]\"\n    exit 2\nfi\n\nif ! command -v pactl >/dev/null; then\n    echo >&2 \"'pactl' tool was not found.\"\n    exit 1\nfi\n\nunset mod_idx\n\ntrap '\n    if [[ -n \"$mod_idx\" ]]; then\n        echo >&2 \"[pulsetalker] Unloading module with index $mod_idx...\"\n        pactl \"${pactl_opts[@]}\" unload-module \"$mod_idx\"\n    fi\n' EXIT\n\necho >&2 \"[pulsetalker] Creating a null sink with name '$sink_name'...\"\n\nmod_idx=$(pactl \"${pactl_opts[@]}\" load-module module-null-sink sink_name=\"$sink_name\")\necho ready\n\necho >&2 \"[pulsetalker] Done, module index is $mod_idx.\"\necho >&2 \"[pulsetalker] Now waiting for signal...\"\n\nwhile true; do\n    sleep 10\ndone\n"
  },
  {
    "path": "tests/stopwatch.c",
    "content": "/*\n * Copyright (C) 2021-2025  luastatus developers\n *\n * This file is part of luastatus.\n *\n * luastatus is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * luastatus is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with luastatus.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n#include <time.h>\n#include <stdio.h>\n#include <stdint.h>\n#include <stdlib.h>\n#include <string.h>\n#include <errno.h>\n#include <inttypes.h>\n#include <sys/time.h>\n\nstatic inline struct timespec now_ts(void)\n{\n    struct timespec ts;\n    int rc;\n\n#if (!defined(_POSIX_MONOTONIC_CLOCK)) || (_POSIX_MONOTONIC_CLOCK < 0)\n    // CLOCK_MONOTONIC is not supported at compile-time.\n    rc = clock_gettime(CLOCK_REALTIME, &ts);\n\n#elif _POSIX_MONOTONIC_CLOCK > 0\n    // CLOCK_MONOTONIC is supported both at compile-time and at run-time.\n    rc = clock_gettime(CLOCK_MONOTONIC, &ts);\n\n#else\n    // CLOCK_MONOTONIC is supported at compile-time, but might or might not\n    // be supported at run-time.\n    rc = clock_gettime(CLOCK_MONOTONIC, &ts);\n    if (rc < 0) {\n        rc = clock_gettime(CLOCK_REALTIME, &ts);\n    }\n#endif\n\n    if (rc < 0) {\n        fprintf(stderr, \"stopwatch: clock_gettime() failed.\\n\");\n        abort();\n    }\n    return ts;\n}\n\nstatic inline uint64_t now_ms(void)\n{\n    struct timespec ts = now_ts();\n    return ((uint64_t) ts.tv_sec) * 1000 + (ts.tv_nsec / 1000000);\n}\n\nstatic uint64_t parse_uint_cstr_or_die(const char *s)\n{\n    errno = 0;\n    char *endptr;\n    uint64_t r = strtoull(s, &endptr, 10);\n    if (errno || endptr == s || endptr[0] != '\\0') {\n        fprintf(stderr, \"stopwatch: cannot parse as unsigned integer: '%s'.\\n\", s);\n        abort();\n    }\n    return r;\n}\n\nstatic void trim_newline(char *s)\n{\n    size_t ns = strlen(s);\n    if (ns && s[ns - 1] == '\\n') {\n        s[ns - 1] = '\\0';\n    }\n}\n\nint main(int argc, char **argv)\n{\n    if (argc != 2) {\n        fprintf(stderr, \"USAGE: stopwatch <slackness in milliseconds>\\n\");\n        return 2;\n    }\n    uint64_t slackness = parse_uint_cstr_or_die(argv[1]);\n\n    uint64_t start = 0;\n\n    char *buf = NULL;\n    size_t nbuf = 0;\n    while (getline(&buf, &nbuf, stdin) > 0) {\n        switch (buf[0]) {\n        case 's':\n            start = now_ms();\n            break;\n        case 'q':\n            printf(\"%\" PRIu64 \"\\n\", now_ms() - start);\n            fflush(stdout);\n            break;\n        case 'r':\n            {\n                uint64_t cur = now_ms();\n                printf(\"%\" PRIu64 \"\\n\", cur - start);\n                fflush(stdout);\n                start = cur;\n            }\n            break;\n        case 'c':\n            {\n                if (buf[1] != ' ') {\n                    fprintf(stderr, \"stopwatch: expected space after 'c', found symbol '%c'.\\n\", buf[1]);\n                    abort();\n                }\n                trim_newline(buf);\n                uint64_t cur = now_ms();\n                uint64_t delta = cur - start;\n                uint64_t expected = parse_uint_cstr_or_die(buf + 2);\n                if (delta > expected + slackness) {\n                    printf(\"0 delta-is-too-big %\" PRIu64 \"\\n\", delta);\n                } else if (expected > slackness && delta < (expected - slackness)) {\n                    printf(\"0 delta-is-too-small %\" PRIu64 \"\\n\", delta);\n                } else {\n                    printf(\"1\\n\");\n                }\n                fflush(stdout);\n                start = cur;\n            }\n            break;\n        default:\n            fprintf(stderr, \"stopwatch: got line with unexpected first symbol '%c'.\\n\", buf[0]);\n            abort();\n        }\n    }\n    if (!feof(stdin)) {\n        perror(\"stopwatch: getline\");\n        abort();\n    }\n    free(buf);\n    return 0;\n}\n"
  },
  {
    "path": "tests/torture.sh",
    "content": "#!/usr/bin/env bash\n\nset -e\n\nopwd=$PWD\ncd -- \"$(dirname \"$(readlink \"$0\" || printf '%s\\n' \"$0\")\")\"\n\nsource ./utils.lib.bash\n\nif (( $# != 1 )); then\n    echo >&2 \"USAGE: $0 <build root>\"\n    exit 2\nfi\nBUILD_DIR=$(resolve_relative \"$1\" \"$opwd\")\n\nLUASTATUS=(\n    \"$BUILD_DIR\"/luastatus/luastatus ${DEBUG:+-l trace}\n)\n\nVALGRIND=(\n    valgrind --error-exitcode=42\n)\n\nrun1() {\n    local n=$1 m=$2 sep_st=$3 event_beg event_end\n    if (( sep_st )); then\n        event_beg='function()'\n        event_end='end'\n    else\n        event_beg='[['\n        event_end=']]'\n    fi\n    shift 3\n    \"${VALGRIND[@]}\" \"$@\" \"${LUASTATUS[@]}\" -e -b \"$BUILD_DIR\"/tests/barlib-mock.so -B gen_events=\"$m\" <(cat <<__EOF__\nn = 0\nwidget = {\n    plugin = '$BUILD_DIR/tests/plugin-mock.so',\n    opts = {\n        make_calls = $n,\n    },\n    cb = function()\n        n = n + 1\n        if n % 10000 == 0 then\n            --print('--- n = ' .. n .. ' ---')\n        end\n    end,\n    event = $event_beg\n        m = (m or 0) + 1\n        if m % 10000 == 0 then\n            --print('--- m = ' .. m .. ' ---')\n        end\n    $event_end,\n}\n__EOF__\n)\n}\n\nrun2() {\n    run1 \"$1\" \"$2\" 0 \"${@:3}\"\n    run1 \"$1\" \"$2\" 1 \"${@:3}\"\n}\n\nrun2 10000 10000 \\\n    --suppressions=dlopen.supp \\\n    --leak-check=full \\\n    --show-leak-kinds=all \\\n    --errors-for-leak-kinds=all \\\n    --track-fds=yes\n# Interesting options:\n#     --fair-sched=yes\n\nrun2 100000 100000 \\\n    --tool=helgrind\n\necho >&2 \"=== PASSED ===\"\n"
  },
  {
    "path": "tests/utils.lib.bash",
    "content": "resolve_relative() {\n    if [[ $1 == /* ]]; then\n        printf '%s\\n' \"$1\"\n    else\n        printf '%s\\n' \"$2/$1\"\n    fi\n}\n"
  }
]