diff --git a/userspace/programs/CMakeLists.txt b/userspace/programs/CMakeLists.txt index 4af346be..6870e8ca 100644 --- a/userspace/programs/CMakeLists.txt +++ b/userspace/programs/CMakeLists.txt @@ -1,5 +1,6 @@ set(USERSPACE_PROGRAMS audio + audioctl AudioServer bananfetch basename diff --git a/userspace/programs/audioctl/CMakeLists.txt b/userspace/programs/audioctl/CMakeLists.txt new file mode 100644 index 00000000..6cb40d29 --- /dev/null +++ b/userspace/programs/audioctl/CMakeLists.txt @@ -0,0 +1,10 @@ +set(SOURCES + main.cpp +) + +add_executable(audioctl ${SOURCES}) +banan_link_library(audioctl ban) +banan_link_library(audioctl libc) +banan_link_library(audioctl libaudio) + +install(TARGETS audioctl OPTIONAL) diff --git a/userspace/programs/audioctl/main.cpp b/userspace/programs/audioctl/main.cpp new file mode 100644 index 00000000..5bf38aac --- /dev/null +++ b/userspace/programs/audioctl/main.cpp @@ -0,0 +1,165 @@ +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +static uint32_t parse_u32_or_exit(const char* string) +{ + errno = 0; + char* endptr; + const uint32_t result = strtoul(string, &endptr, 0); + if (errno || *endptr != '\0') + { + fprintf(stderr, "invalid integer %s\n", string); + exit(1); + } + return result; +} + +static int get_server_fd() +{ + int fd = socket(AF_UNIX, SOCK_STREAM, 0); + if (fd == -1) + { + perror("Failed to create a socket"); + return -1; + } + + sockaddr_un addr; + addr.sun_family = AF_UNIX; + strcpy(addr.sun_path, LibAudio::s_audio_server_socket.data()); + if (connect(fd, reinterpret_cast(&addr), sizeof(addr)) == -1) + { + perror("Failed to connect to audio server"); + return -1; + } + + return fd; +} + +static uint32_t send_request(int fd, LibAudio::Packet packet, bool wait_response) +{ + if (ssize_t ret = send(fd, &packet, sizeof(packet), 0); ret != sizeof(packet)) + { + fprintf(stderr, "Failed to send request to server"); + if (ret < 0) + fprintf(stderr, ": %s", strerror(errno)); + fprintf(stderr, "\n"); + exit(1); + } + + if (!wait_response) + return 0; + + uint32_t response; + if (ssize_t ret = recv(fd, &response, sizeof(response), 0) != sizeof(response)) + { + fprintf(stderr, "Failed to receive response from server"); + if (ret < 0) + fprintf(stderr, ": %s", strerror(errno)); + fprintf(stderr, "\n"); + exit(1); + } + + return response; +} + +static void list_devices(int fd) +{ + const uint32_t current_device = send_request(fd, { .type = LibAudio::Packet::GetDevice, .parameter = 0 }, true); + const uint32_t current_pin = send_request(fd, { .type = LibAudio::Packet::GetPin, .parameter = 0 }, true); + + const uint32_t total_devices = send_request(fd, { .type = LibAudio::Packet::QueryDevices, .parameter = 0 }, true); + for (uint32_t dev = 0; dev < total_devices; dev++) + { + const uint32_t total_pins = send_request(fd, { .type = LibAudio::Packet::QueryPins, .parameter = dev }, true); + + printf("Device %" PRIu32 "", dev); + if (dev == current_device) + printf(" (current)"); + printf("\n"); + + for (uint32_t pin = 0; pin < total_pins; pin++) + { + printf(" Pin %" PRIu32 "", pin); + if (dev == current_device && pin == current_pin) + printf(" (current)"); + printf("\n"); + } + } +} + +int main(int argc, char** argv) +{ + bool list { false }; + BAN::Optional device; + BAN::Optional pin; + + for (;;) + { + static option long_options[] { + { "list", no_argument, nullptr, 'l' }, + { "device", required_argument, nullptr, 'd' }, + { "pin", required_argument, nullptr, 'p' }, + { "help", no_argument, nullptr, 'h' }, + }; + + int ch = getopt_long(argc, argv, "ld:p:h", long_options, nullptr); + if (ch == -1) + break; + + switch (ch) + { + case 'h': + fprintf(stderr, "usage: %s [OPTIONS]...\n", argv[0]); + fprintf(stderr, " control the audio server\n"); + fprintf(stderr, "OPTIONS:\n"); + fprintf(stderr, " -l, --list list devices and their pins\n"); + fprintf(stderr, " -d, --device N set device index N as the current one\n"); + fprintf(stderr, " -p, --pin N set pin N as the current one\n"); + fprintf(stderr, " -h, --help show this message and exit\n"); + return 0; + case 'l': + list = true; + break; + case 'd': + device = parse_u32_or_exit(optarg); + break; + case 'p': + pin = parse_u32_or_exit(optarg); + break; + case '?': + fprintf(stderr, "invalid option %c\n", optopt); + fprintf(stderr, "see '%s --help' for usage\n", argv[0]); + return 1; + } + } + + if (!device.has_value() && !pin.has_value()) + list = true; + + const int fd = get_server_fd(); + if (fd == -1) + return 1; + + if (device.has_value()) + send_request(fd, { .type = LibAudio::Packet::SetDevice, .parameter = device.value() }, false); + + if (pin.has_value()) + send_request(fd, { .type = LibAudio::Packet::SetPin, .parameter = pin.value() }, false); + + if (list) + list_devices(fd); + + close(fd); + + return 0; +}