diff --git a/.gitignore b/.gitignore index 02f9372..50d749b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ compile_commands.json build .cache +src/config.hpp diff --git a/README.md b/README.md index b2dd51d..73ec38f 100644 --- a/README.md +++ b/README.md @@ -1,33 +1,58 @@ # somebar - dwl-like bar for dwm -This is extremely work in progress. +![Screenshot](screenshot.png) + +This project is rather new. Beware of bugs. ## Dependencies * c++ compiler, meson, and ninja -* Qt 5 GUI -* libwayland * wayland-scanner +* libwayland-client +* libwayland-cursor +* libcairo +* libpango +* libpangocairo - sudo apt install build-essential meson ninja qtbase5-dev libqt5core5a libqt5gui5 \ - libwayland-bin libwayland-client0 libwayland-cursor0 libwayland-dev +``` +sudo apt install build-essential meson ninja \ + libwayland-bin libwayland-client0 libwayland-cursor0 libwayland-dev \ + libcairo2 libcairo2-dev \ + libpango-1.0-0 libpango1.0-dev libpangocairo-1.0-0 +``` -dwl must have the [wayland-ipc patch](https://gitlab.com/raphaelr/dwl/-/raw/master/patches/wayland-ipc.patch) applied, +**dwl must have the [wayland-ipc patch](https://gitlab.com/raphaelr/dwl/-/raw/master/patches/wayland-ipc.patch) applied**, since that's how the bar communicates with dwl. ## Configuration -src/config.hpp +Copy `src/config.def.hpp` to `src/config.hpp`, and adjust if needed. ## Building meson setup build ninja -C build + ./build/somebar ## Usage somebar doesn't use dwl's status feature. You can start somebar using dwl's `-s` option, -but if you do, close stdout. +but if you do, close stdin, as somebar will not read from it. + +Somebar can be controlled by writing to `$XDG_RUNTIME_DIR/somebar-0`. The following +commands are supported: + +* `status TEXT`: Updates the status bar +* `hide MONITOR` Hides somebar on the specified monitor +* `shows MONITOR` Shows somebar on the specified monitor +* `toggle MONITOR` Toggles somebar on the specified monitor + +MONITOR is an zxdg_output_v1 name, which can be determined e.g. using `weston-info`. +Additionally, MONITOR can be `all` (all monitors) or `selected` (the monitor with focus). + +Commands can be sent either by writing to the file name above, or equivalently by calling +somebar with the `-c` argument. For example: `somebar -c toggle all`. This is recommended +for shell scripts, as there is no race-free way to write to a file only if it exists. ## License diff --git a/meson.build b/meson.build index 4043100..3adaa19 100644 --- a/meson.build +++ b/meson.build @@ -1,5 +1,9 @@ project('somebar', ['c', 'cpp'], - default_options: ['cpp_std=c++17', 'cpp_args=-Wno-parentheses']) + version: '0.1.0', + default_options: [ + 'cpp_std=c++17', + 'cpp_args=-Wno-parentheses', + ]) wayland_dep = dependency('wayland-client') wayland_cursor_dep = dependency('wayland-cursor') @@ -20,4 +24,5 @@ executable('somebar', cairo_dep, pango_dep, pangocairo_dep, - ]) + ], + cpp_args: '-DSOMEBAR_VERSION="@0@"'.format(meson.project_version())) diff --git a/screenshot.png b/screenshot.png new file mode 100644 index 0000000..7a800fb Binary files /dev/null and b/screenshot.png differ diff --git a/src/config.def.hpp b/src/config.def.hpp index 38f4909..2af8c14 100644 --- a/src/config.def.hpp +++ b/src/config.def.hpp @@ -22,5 +22,5 @@ constexpr Button buttons[] = { { ClkTagBar, BTN_MIDDLE, toggletag, {0} }, { ClkLayoutSymbol, BTN_LEFT, setlayout, {.ui = 0} }, { ClkLayoutSymbol, BTN_RIGHT, setlayout, {.ui = 2} }, - { ClkWinTitle, BTN_RIGHT, spawn, {.v = termcmd} }, + { ClkStatusText, BTN_RIGHT, spawn, {.v = termcmd} }, }; diff --git a/src/main.cpp b/src/main.cpp index e7f6294..9b4126d 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -24,6 +24,7 @@ #include "net-tapesoftware-dwl-wm-unstable-v1-client-protocol.h" #include "common.hpp" #include "bar.hpp" +#include "line_buffer.hpp" struct Monitor { uint32_t registryName; @@ -101,7 +102,7 @@ void toggletag(Monitor &m, const Arg &arg) } void spawn(Monitor&, const Arg &arg) { - if (fork()) { + if (fork() == 0) { auto argv = static_cast(arg.v); setsid(); execvp(argv[0], argv); @@ -334,29 +335,31 @@ static void updateVisibility(const std::string &name, T updater) } } +static LineBuffer<512> _statusBuffer; static void onStatus() { - // this doesn't handle cases where there's multiple or partial lines in the buffer - char buffer[512]; - auto n = read(statusFifoFd, buffer, sizeof(buffer)); - auto str = std::string {buffer, (unsigned long) n}; - auto trailer = str.rfind('\n'); - if (trailer != std::string::npos) str.erase(trailer); - if (str.rfind(prefixStatus, 0) == 0) { - lastStatus = str.substr(prefixStatus.size()); - for (auto &monitor : monitors) { - if (monitor.bar) { - monitor.bar->setStatus(lastStatus); - monitor.bar->invalidate(); + _statusBuffer.readLines( + [](void *p, size_t size) { + return read(statusFifoFd, p, size); + }, + [](const char *buffer, size_t n) { + auto str = std::string {buffer, n}; + if (str.rfind(prefixStatus, 0) == 0) { + lastStatus = str.substr(prefixStatus.size()); + for (auto &monitor : monitors) { + if (monitor.bar) { + monitor.bar->setStatus(lastStatus); + monitor.bar->invalidate(); + } + } + } else if (str.rfind(prefixShow, 0) == 0) { + updateVisibility(str.substr(prefixShow.size()), [](bool) { return true; }); + } else if (str.rfind(prefixHide, 0) == 0) { + updateVisibility(str.substr(prefixHide.size()), [](bool) { return false; }); + } else if (str.rfind(prefixToggle, 0) == 0) { + updateVisibility(str.substr(prefixToggle.size()), [](bool vis) { return !vis; }); } - } - } else if (str.rfind(prefixShow, 0) == 0) { - updateVisibility(str.substr(prefixShow.size()), [](bool) { return true; }); - } else if (str.rfind(prefixHide, 0) == 0) { - updateVisibility(str.substr(prefixHide.size()), [](bool) { return false; }); - } else if (str.rfind(prefixToggle, 0) == 0) { - updateVisibility(str.substr(prefixToggle.size()), [](bool vis) { return !vis; }); - } + }); } struct HandleGlobalHelper { @@ -411,6 +414,41 @@ static const struct wl_registry_listener registry_listener = { int main(int argc, char **argv) { + int opt; + while ((opt = getopt(argc, argv, "chv")) != -1) { + switch (opt) { + case 'h': + printf("Usage: %s [-h] [-v] [-c command]\n", argv[0]); + printf(" -h: Show this help\n"); + printf(" -v: Show somebar version\n"); + printf(" -c: Sends a command to sombar. See README for details.\n"); + printf("If any of these are specified, somebar exits after the action.\n"); + printf("Otherwise, somebar will display itself.\n"); + exit(0); + case 'v': + printf("somebar " SOMEBAR_VERSION "\n"); + exit(0); + case 'c': + if (optind >= argc) { + die("Expected command"); + } + auto path = std::string {getenv("XDG_RUNTIME_DIR")} + "/somebar-0"; + int fd = open(path.c_str(), O_WRONLY | O_CLOEXEC); + if (fd < 0) { + fprintf(stderr, "could not open %s: ", path.c_str()); + perror(""); + exit(1); + } + auto str = std::string {}; + for (auto i = optind; i optind) str += " "; + str += argv[i]; + } + str += "\n"; + write(fd, str.c_str(), str.size()); + exit(0); + } + } static sigset_t blockedsigs; sigemptyset(&blockedsigs); sigaddset(&blockedsigs, SIGINT); @@ -506,6 +544,7 @@ void waylandFlush() } void die(const char *why) { + fprintf(stderr, "%s\n", why); cleanup(); exit(1); }