diff --git a/include/bar.hpp b/include/bar.hpp new file mode 100644 index 00000000..e94d9aad --- /dev/null +++ b/include/bar.hpp @@ -0,0 +1,30 @@ +#pragma once + +#include +#include "wlr-layer-shell-unstable-v1-client-protocol.h" + +namespace waybar { + + struct Client; + + struct Bar { + Bar(Client& client, std::unique_ptr&& output); + Bar(const Bar&) = delete; + Client& client; + Gtk::Window window; + struct wl_surface *surface; + struct zwlr_layer_surface_v1 *layer_surface; + std::unique_ptr output; + bool visible = true; + auto set_width(int) -> void; + auto toggle() -> void; + private: + auto setup_widgets() -> void; + auto setup_css() -> void; + + int width = 10; + Glib::RefPtr style_context; + Glib::RefPtr css_provider; + }; + +} diff --git a/include/client.hpp b/include/client.hpp new file mode 100644 index 00000000..34dcba52 --- /dev/null +++ b/include/client.hpp @@ -0,0 +1,43 @@ +#pragma once + +#include + +#include + +#include +#include +#include + +#include "wlr-layer-shell-unstable-v1-client-protocol.h" + +#include "util/ptr_vec.hpp" + +#include + +#include "bar.hpp" + +namespace waybar { + + struct Client { + uint32_t height = 30; + std::string css_file = "./resources/style.css"; + + Gtk::Main gtk_main; + + Glib::RefPtr gdk_display; + struct wl_display *wlDisplay; + struct wl_registry *registry; + struct zwlr_layer_shell_v1 *layer_shell; + util::ptr_vec bars; + + struct { + sigc::signal workspace_state; + sigc::signal focused_window_name; + } signals; + + Client(int argc, char* argv[]); + void bind_interfaces(); + auto setup_css(); + int main(int argc, char* argv[]); + }; +} diff --git a/include/ipc/client.hpp b/include/ipc/client.hpp new file mode 100644 index 00000000..c6ac230f --- /dev/null +++ b/include/ipc/client.hpp @@ -0,0 +1,32 @@ +#pragma once + +#include +#include "ipc.hpp" + +/** + * IPC response including type of IPC response, size of payload and the json + * encoded payload string. + */ +struct ipc_response { + uint32_t size; + uint32_t type; + std::string payload; +}; + +/** + * Gets the path to the IPC socket from sway. + */ +std::string get_socketpath(void); +/** + * Opens the sway socket. + */ +int ipc_open_socket(std::string socket_path); +/** + * Issues a single IPC command and returns the buffer. len will be updated with + * the length of the buffer returned from sway. + */ +std::string ipc_single_command(int socketfd, uint32_t type, const char *payload, uint32_t *len); +/** + * Receives a single IPC response and returns an ipc_response. + */ +struct ipc_response ipc_recv_response(int socketfd); diff --git a/include/ipc/ipc.hpp b/include/ipc/ipc.hpp new file mode 100644 index 00000000..45bacbb5 --- /dev/null +++ b/include/ipc/ipc.hpp @@ -0,0 +1,32 @@ +#pragma once + +#define event_mask(ev) (1 << (ev & 0x7F)) + +enum ipc_command_type { + // i3 command types - see i3's I3_REPLY_TYPE constants + IPC_COMMAND = 0, + IPC_GET_WORKSPACES = 1, + IPC_SUBSCRIBE = 2, + IPC_GET_OUTPUTS = 3, + IPC_GET_TREE = 4, + IPC_GET_MARKS = 5, + IPC_GET_BAR_CONFIG = 6, + IPC_GET_VERSION = 7, + IPC_GET_BINDING_MODES = 8, + IPC_GET_CONFIG = 9, + IPC_SEND_TICK = 10, + + // sway-specific command types + IPC_GET_INPUTS = 100, + IPC_GET_SEATS = 101, + + // Events sent from sway to clients. Events have the highest bits set. + IPC_EVENT_WORKSPACE = ((1<<31) | 0), + IPC_EVENT_OUTPUT = ((1<<31) | 1), + IPC_EVENT_MODE = ((1<<31) | 2), + IPC_EVENT_WINDOW = ((1<<31) | 3), + IPC_EVENT_BARCONFIG_UPDATE = ((1<<31) | 4), + IPC_EVENT_BINDING = ((1<<31) | 5), + IPC_EVENT_SHUTDOWN = ((1<<31) | 6), + IPC_EVENT_TICK = ((1<<31) | 7), +}; diff --git a/include/modules/battery.hpp b/include/modules/battery.hpp new file mode 100644 index 00000000..6047b7e0 --- /dev/null +++ b/include/modules/battery.hpp @@ -0,0 +1,26 @@ +#pragma once + +#include +#include +#include +#include +#include +#include "util/chrono.hpp" + +namespace waybar::modules { + + namespace fs = std::filesystem; + + class Battery { + public: + Battery(); + auto update() -> void; + operator Gtk::Widget&(); + private: + static inline const fs::path _data_dir = "/sys/class/power_supply/"; + std::vector _batteries; + util::SleeperThread _thread; + Gtk::Label _label; + }; + +} diff --git a/include/modules/clock.hpp b/include/modules/clock.hpp new file mode 100644 index 00000000..59bc1ab1 --- /dev/null +++ b/include/modules/clock.hpp @@ -0,0 +1,19 @@ +#pragma once + +#include +#include +#include +#include "util/chrono.hpp" + +namespace waybar::modules { + + class Clock { + public: + Clock(); + operator Gtk::Widget &(); + private: + Gtk::Label _label; + waybar::util::SleeperThread _thread; + }; + +} diff --git a/include/modules/workspaces.hpp b/include/modules/workspaces.hpp new file mode 100644 index 00000000..7e79bfaf --- /dev/null +++ b/include/modules/workspaces.hpp @@ -0,0 +1,27 @@ +#pragma once + +#include +#include +#include "bar.hpp" +#include "client.hpp" +#include "util/chrono.hpp" + +namespace waybar::modules { + + class WorkspaceSelector { + public: + WorkspaceSelector(waybar::Bar &bar); + auto update() -> void; + operator Gtk::Widget &(); + private: + void _addWorkspace(Json::Value node); + Json::Value _getWorkspaces(); + Bar &_bar; + Gtk::Box *_box; + std::unordered_map _buttons; + util::SleeperThread _thread; + int _ipcSocketfd; + int _ipcEventSocketfd; + }; + +} diff --git a/include/util/algorithm.hpp b/include/util/algorithm.hpp new file mode 100644 index 00000000..365c1e60 --- /dev/null +++ b/include/util/algorithm.hpp @@ -0,0 +1,823 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +namespace waybar::util { + + /// Joins a sequence of strings, separating them using `js` + template // Models InputIterator + std::string join_strings(StrIterator b, StrIterator e, std::string_view js = ", ") + { + std::string result; + std::for_each(b, e, [&](auto&& s) { + if (!result.empty()) { + result.append(js); + } + result.append(s); + }); + return result; + } + + inline const char* nonull(const char* str) { + if (str == nullptr) return ""; + return str; + }; + + inline bool iequals(std::string_view a, std::string_view b) + { + return std::equal(a.begin(), a.end(), b.begin(), b.end(), + [](char a, char b) { return tolower(a) == tolower(b); }); + } + + inline bool starts_with(std::string_view prefix, std::string_view a) + { + return a.compare(0, prefix.size(), prefix) == 0; + } + + inline bool ends_with(std::string_view prefix, std::string_view a) + { + return a.compare(a.size() - prefix.size(), prefix.size(), prefix) == 0; + } + + /// Return a closure which compares the adress any reference to T to the address of t + template + constexpr auto addr_eq(T&& t) { + return [&t] (auto&& t2) { + return &t == &t2; + }; + } + + template + bool erase_this(std::vector& cont, T* el) + { + if (el < cont.data() && el >= cont.data() + cont.size()) return false; + cont.erase(cont.begin() + (el - cont.data())); + return true; + } + + template + bool erase_this(std::vector& cont, T& el) + { + return erase_this(cont, &el); + } + + namespace detail { + template + constexpr auto generate_array_impl(std::integer_sequence&&, Func&& gen) + { + return std::array()))>, + sizeof...(ns)>{{std::invoke(gen, ns)...}}; + } + } // namespace detail + + template + constexpr auto generate_array(Func&& gen) + { + auto intseq = std::make_integer_sequence(); + return detail::generate_array_impl(std::move(intseq), std::forward(gen)); + } + + namespace view { + + namespace detail { + template + using store_or_ref_t = std::conditional_t, std::decay_t, T&>; + } + + template + struct reverse { + + reverse(Cont&& cont) noexcept : _container(std::forward(cont)) {} + + auto begin() + { + return std::rbegin(_container); + } + + auto end() + { + return std::rend(_container); + } + + auto begin() const + { + return std::rbegin(_container); + } + + auto end() const + { + return std::rend(_container); + } + + auto cbegin() const + { + return std::crbegin(_container); + } + + auto cend() const + { + return std::crend(_container); + } + + detail::store_or_ref_t _container; + }; + + template + reverse(ContRef&& cont) -> reverse; + + template + struct constant { + constant(Cont&& cont) noexcept : _container(std::forward(cont)){}; + + auto begin() const + { + return std::cbegin(_container); + } + + auto end() const + { + return std::cend(_container); + } + + auto cbegin() const + { + return std::cbegin(_container); + } + + auto cend() const + { + return std::cend(_container); + } + + detail::store_or_ref_t _container; + }; + + template + constant(ContRef&& cont) -> constant; + + } // namespace view + + + /* + * Range algorithms + */ + + template + constexpr InputIt for_each_n(InputIt&& first, Size n, F&& f) + { + for (Size i = 0; i < n; ++first, ++i) { + std::invoke(f, *first); + } + return first; + } + + /// `for_each` with access to an index value. Function called as `f(*it, i)` + /// + /// For each item in range `[first, last)`, invoke `f` with args + /// `*iter, i` where `iter` is the current iterator, and `i` is + /// an incrementing value, starting at zero. Use this instead of + /// raw indexed loops wherever possible. + /// + /// \param first Input iterator to the begining of the range + /// \param last Input iterator to the end of the range + /// \param f Must be invocable with arguments `value_type`, `std::size_t` + /// \returns The number of iterations performed + template + constexpr std::size_t indexed_for(InputIt&& first, InputIt&& last, F&& f) + { + std::size_t i = 0; + std::for_each(std::forward(first), std::forward(last), [&](auto&& a) { + std::invoke(f, a, i); + i++; + }); + return i; + } + + template + constexpr std::size_t indexed_for(Rng&& rng, F&& f) + { + return indexed_for(std::begin(rng), std::end(rng), std::forward(f)); + } + + + /// `for_each_n` with access to an index value. Function called as `f(*it, i)` + /// + /// for `n` iterations, invoke `f` with args `*iter, i` + /// where `iter` is the current iterator starting with `first`, + /// and `i` is an incrementing value, starting at zero. + /// Use this instead of raw indexed loops wherever possible. + /// + /// \param first Input iterator to the begining of the range + /// \param n Number of iterations to go through + /// \param f Must be invocable with arguments `value_type`, `std::size_t` + /// \returns An iterator one past the last one visited + template + constexpr InputIt indexed_for_n(InputIt first, Size n, F&& f) + { + for (Size i = 0; i < n; ++first, ++i) { + std::invoke(f, *first, i); + } + return first; + } + + template + constexpr std::size_t indexed_for_n(Rng&& rng, Size n, F&& f) + { + return indexed_for_n(std::begin(rng), std::end(rng), n, std::forward(f)); + } + + template + constexpr void for_both(Iter1&& f1, Iter1&& l1, Iter2&& f2, Iter2&& l2, F&& f) + { + Iter1 i1 = std::forward(f1); + Iter2 i2 = std::forward(f2); + for (; i1 != l1 && i2 != l2; i1++, i2++) { + std::invoke(f, *i1, *i2); + } + } + + template + constexpr void for_both(Rng1&& r1, Rng2&& r2, F&& f) + { + for_both(std::begin(r1), std::end(r1), std::begin(r2), std::end(r2), std::forward(f)); + } + + /* + * Range based standard algorithms + * + * Thanks, chris from SO! + */ + + template + constexpr auto accumulate(Cont&& cont, T&& init) + { + // TODO C++20: std::accumulate is constexpr + using std::begin, std::end; + auto first = begin(cont); + auto last = end(cont); + for (; first != last; ++first) init = init + *first; + return init; + } + + template + constexpr auto accumulate(Cont&& cont, T&& init, BinaryOperation&& op) + { + // TODO C++20: std::accumulate is constexpr + using std::begin, std::end; + auto first = begin(cont); + auto last = end(cont); + for (; first != last; ++first) init = op(init, *first); + return init; + } + + template + decltype(auto) adjacent_difference(Cont&& cont, OutputIterator&& first) + { + using std::begin; + using std::end; + return std::adjacent_difference(begin(cont), end(cont), std::forward(first)); + } + + template + decltype(auto) prev_permutation(Cont&& cont) + { + using std::begin; + using std::end; + return std::prev_permutation(begin(cont), end(cont)); + } + + template + decltype(auto) prev_permutation(Cont&& cont, Compare&& comp) + { + using std::begin; + using std::end; + return std::prev_permutation(begin(cont), end(cont), std::forward(comp)); + } + + template + decltype(auto) push_heap(Cont&& cont) + { + using std::begin; + using std::end; + return std::push_heap(begin(cont), end(cont)); + } + + template + decltype(auto) push_heap(Cont&& cont, Compare&& comp) + { + using std::begin; + using std::end; + return std::push_heap(begin(cont), end(cont), std::forward(comp)); + } + + template + decltype(auto) remove(Cont&& cont, T&& value) + { + using std::begin; + using std::end; + return std::remove(begin(cont), end(cont), std::forward(value)); + } + + template + decltype(auto) remove_copy(Cont&& cont, OutputIterator&& first, T&& value) + { + using std::begin; + using std::end; + return std::remove_copy(begin(cont), end(cont), std::forward(first), + std::forward(value)); + } + + template + decltype(auto) remove_copy_if(Cont&& cont, OutputIterator&& first, UnaryPredicate&& p) + { + using std::begin; + using std::end; + return std::remove_copy_if(begin(cont), end(cont), std::forward(first), + std::forward(p)); + } + + template + decltype(auto) remove_if(Cont&& cont, UnaryPredicate&& p) + { + using std::begin; + using std::end; + return std::remove_if(begin(cont), end(cont), std::forward(p)); + } + + template + decltype(auto) replace(Cont&& cont, T&& old_value, T2&& new_value) + { + using std::begin; + using std::end; + return std::replace(begin(cont), end(cont), std::forward(old_value), + std::forward(new_value)); + } + + template + decltype(auto) replace_copy(Cont&& cont, OutputIterator&& first, T&& old_value, T2&& new_value) + { + using std::begin; + using std::end; + return std::replace_copy(begin(cont), end(cont), std::forward(first), + std::forward(old_value), std::forward(old_value)); + } + + template + decltype(auto) replace_copy_if(Cont&& cont, + OutputIterator&& first, + UnaryPredicate&& p, + T&& new_value) + { + using std::begin; + using std::end; + return std::replace_copy(begin(cont), end(cont), std::forward(first), + std::forward(p), std::forward(new_value)); + } + + template + decltype(auto) replace_if(Cont&& cont, UnaryPredicate&& p, T&& new_value) + { + using std::begin; + using std::end; + return std::replace_if(begin(cont), end(cont), std::forward(p), + std::forward(new_value)); + } + + template + decltype(auto) reverse(Cont&& cont) + { + using std::begin; + using std::end; + return std::reverse(begin(cont), end(cont)); + } + + template + decltype(auto) reverse_copy(Cont&& cont, OutputIterator&& first) + { + using std::begin; + using std::end; + return std::reverse_copy(begin(cont), end(cont), std::forward(first)); + } + + template + decltype(auto) rotate(Cont&& cont, ForwardIterator&& new_first) + { + using std::begin; + using std::end; + return std::rotate(begin(cont), std::forward(new_first), end(cont)); + } + + template + decltype(auto) rotate_copy(Cont&& cont, ForwardIterator&& new_first, OutputIterator&& first) + { + using std::begin; + using std::end; + return std::rotate_copy(begin(cont), std::forward(new_first), end(cont), + std::forward(first)); + } + + template + decltype(auto) search(Cont&& cont, Cont2&& cont2) + { + using std::begin; + using std::end; + return std::search(begin(cont), end(cont), begin(cont2), end(cont2)); + } + + template + decltype(auto) search(Cont&& cont, Cont2&& cont2, BinaryPredicate&& p) + { + using std::begin; + using std::end; + return std::search(begin(cont), end(cont), begin(cont2), end(cont2), + std::forward(p)); + } + + template + decltype(auto) search_n(Cont&& cont, Size count, T&& value) + { + using std::begin; + using std::end; + return std::search_n(begin(cont), end(cont), count, std::forward(value)); + } + + template + decltype(auto) search_n(Cont&& cont, Size count, T&& value, BinaryPredicate&& p) + { + using std::begin; + using std::end; + return std::search_n(begin(cont), end(cont), count, std::forward(value), + std::forward(p)); + } + + template + decltype(auto) set_difference(Cont&& cont, Cont2&& cont2, OutputIterator&& first) + { + using std::begin; + using std::end; + return std::set_difference(begin(cont), end(cont), begin(cont2), end(cont2), + std::forward(first)); + } + + template + decltype(auto) set_difference(Cont&& cont, Cont2&& cont2, OutputIterator&& first, Compare&& comp) + { + using std::begin; + using std::end; + return std::set_difference(begin(cont), end(cont), begin(cont2), end(cont2), + std::forward(first), std::forward(comp)); + } + + template + decltype(auto) set_intersection(Cont&& cont, Cont2&& cont2, OutputIterator&& first) + { + using std::begin; + using std::end; + return std::set_intersection(begin(cont), end(cont), begin(cont2), end(cont2), + std::forward(first)); + } + + template + decltype(auto) set_intersection(Cont&& cont, + Cont2&& cont2, + OutputIterator&& first, + Compare&& comp) + { + using std::begin; + using std::end; + return std::set_intersection(begin(cont), end(cont), begin(cont2), end(cont2), + std::forward(first), std::forward(comp)); + } + + template + decltype(auto) set_symmetric_difference(Cont&& cont, Cont2&& cont2, OutputIterator&& first) + { + using std::begin; + using std::end; + return std::set_symmetric_difference(begin(cont), end(cont), begin(cont2), end(cont2), + std::forward(first)); + } + + template + decltype(auto) set_symmetric_difference(Cont&& cont, + Cont2&& cont2, + OutputIterator&& first, + Compare&& comp) + { + using std::begin; + using std::end; + return std::set_symmetric_difference(begin(cont), end(cont), begin(cont2), end(cont2), + std::forward(first), + std::forward(comp)); + } + + template + decltype(auto) set_union(Cont&& cont, Cont2&& cont2, OutputIterator&& first) + { + using std::begin; + using std::end; + return std::set_union(begin(cont), end(cont), begin(cont2), end(cont2), + std::forward(first)); + } + + template + decltype(auto) set_union(Cont&& cont, Cont2&& cont2, OutputIterator&& first, Compare&& comp) + { + using std::begin; + using std::end; + return std::set_union(begin(cont), end(cont), begin(cont2), end(cont2), + std::forward(first), std::forward(comp)); + } + + template + decltype(auto) shuffle(Cont&& cont, UniformRandomNumberGenerator&& g) + { + using std::begin; + using std::end; + return std::shuffle(begin(cont), end(cont), std::forward(g)); + } + + template + decltype(auto) sort(Cont&& cont) + { + using std::begin; + using std::end; + return std::sort(begin(cont), end(cont)); + } + + template + decltype(auto) sort(Cont&& cont, Compare&& comp) + { + using std::begin; + using std::end; + return std::sort(begin(cont), end(cont), std::forward(comp)); + } + + template + decltype(auto) sort_heap(Cont&& cont) + { + using std::begin; + using std::end; + return std::sort_heap(begin(cont), end(cont)); + } + + template + decltype(auto) sort_heap(Cont&& cont, Compare&& comp) + { + using std::begin; + using std::end; + return std::sort_heap(begin(cont), end(cont), std::forward(comp)); + } + + template + decltype(auto) stable_partition(Cont&& cont, UnaryPredicate&& p) + { + using std::begin; + using std::end; + return std::stable_partition(begin(cont), end(cont), std::forward(p)); + } + + template + decltype(auto) stable_sort(Cont&& cont) + { + using std::begin; + using std::end; + return std::stable_sort(begin(cont), end(cont)); + } + + template + decltype(auto) stable_sort(Cont&& cont, Compare&& comp) + { + using std::begin; + using std::end; + return std::stable_sort(begin(cont), end(cont), std::forward(comp)); + } + + template + decltype(auto) swap_ranges(Cont&& cont, ForwardIterator&& first) + { + using std::begin; + using std::end; + return std::swap_ranges(begin(cont), end(cont), std::forward(first)); + } + + template + auto transform(Cont&& cont, Cont2&& cont2, F&& f) -> decltype(begin(cont2)) + { + using std::begin; + using std::end; + return std::transform(begin(cont), end(cont), begin(cont2), std::forward(f)); + } + + template + decltype(auto) transform(Cont&& cont, Iter&& iter, F&& f) + { + using std::begin; + using std::end; + return std::transform(begin(cont), end(cont), std::forward(iter), std::forward(f)); + } + + template + auto transform(Cont&& cont, Cont2&& cont2, Cont3&& cont3, BinaryPredicate&& f) + -> decltype(begin(cont2), begin(cont3)) + { + using std::begin; + using std::end; + return std::transform(begin(cont), end(cont), begin(cont2), begin(cont3), + std::forward(f)); + } + + template + auto transform(Cont&& cont, InputIterator&& iter, Cont3&& cont3, BinaryPredicate&& f) + -> decltype(begin(cont), begin(cont3)) + { + using std::begin; + using std::end; + return std::transform(begin(cont), end(cont), std::forward(iter), begin(cont3), + std::forward(f)); + } + + template + auto transform(Cont&& cont, Cont2&& cont2, InputIterator&& iter, BinaryPredicate&& f) + -> decltype(begin(cont), begin(cont2), iter) + { + using std::begin; + using std::end; + return std::transform(begin(cont), end(cont), begin(cont2), std::forward(iter), + std::forward(f)); + } + + template + decltype(auto) transform(Cont&& cont, + InputIterator&& firstIn, + OutputIterator&& firstOut, + BinaryOperation&& op) + { + using std::begin; + using std::end; + return std::transform(begin(cont), end(cont), std::forward(firstIn), + std::forward(firstOut), + std::forward(op)); + } + + template + decltype(auto) unique(Cont&& cont) + { + using std::begin; + using std::end; + return std::unique(begin(cont), end(cont)); + } + + template + decltype(auto) unique(Cont&& cont, BinaryPredicate&& p) + { + using std::begin; + using std::end; + return std::unique(begin(cont), end(cont), std::forward(p)); + } + + template + decltype(auto) unique_copy(Cont&& cont, OutputIterator&& first) + { + using std::begin; + using std::end; + return std::unique_copy(begin(cont), end(cont), std::forward(first)); + } + + template + decltype(auto) unique_copy(Cont&& cont, OutputIterator&& first, BinaryPredicate&& p) + { + using std::begin; + using std::end; + return std::unique_copy(begin(cont), end(cont), std::forward(first), + std::forward(p)); + } + + template + decltype(auto) upper_bound(Cont&& cont, T&& value) + { + using std::begin; + using std::end; + return std::upper_bound(begin(cont), end(cont), std::forward(value)); + } + + template + decltype(auto) upper_bound(Cont&& cont, T&& value, Compare&& comp) + { + using std::begin; + using std::end; + return std::upper_bound(begin(cont), end(cont), std::forward(value), + std::forward(comp)); + } + + template + decltype(auto) copy(Cont&& cont, OutputIterator&& first) + { + using std::begin; + using std::end; + return std::copy(begin(cont), end(cont), std::forward(first)); + } + + template + decltype(auto) copy_if(Cont&& cont, OutputIterator&& first, UnaryPredicate&& p) + { + using std::begin; + using std::end; + return std::copy_if(begin(cont), end(cont), std::forward(first), + std::forward(p)); + } + + template + decltype(auto) fill(Cont&& cont, T&& value) + { + using std::begin; + using std::end; + return std::fill(begin(cont), end(cont), std::forward(value)); + } + + template + decltype(auto) fill_n(Cont&& cont, std::size_t n, T&& value) + { + using std::begin; + using std::end; + return std::fill_n(begin(cont), n, std::forward(value)); + } + + template + decltype(auto) any_of(Cont&& cont, UnaryPredicate&& p) + { + using std::begin; + using std::end; + return std::any_of(begin(cont), end(cont), std::forward(p)); + } + + template + decltype(auto) all_of(Cont&& cont, UnaryPredicate&& p) + { + using std::begin; + using std::end; + return std::all_of(begin(cont), end(cont), std::forward(p)); + } + + template + decltype(auto) none_of(Cont&& cont, UnaryPredicate&& p) + { + using std::begin; + using std::end; + return std::none_of(begin(cont), end(cont), std::forward(p)); + } + + template + decltype(auto) max_element(Cont&& cont) + { + using std::begin; + using std::end; + return std::max_element(begin(cont), end(cont)); + } + + template + decltype(auto) min_element(Cont&& cont) + { + using std::begin; + using std::end; + return std::min_element(begin(cont), end(cont)); + } + + template + decltype(auto) min_element(Cont&& cont, Compare&& f) + { + using std::begin; + using std::end; + return std::min_element(begin(cont), end(cont), std::forward(f)); + } + + template + decltype(auto) max_element(Cont&& cont, Compare&& f) + { + using std::begin; + using std::end; + return std::max_element(begin(cont), end(cont), std::forward(f)); + } + + template + decltype(auto) find(Cont&& cont, T&& t) + { + using std::begin; + using std::end; + return std::find(begin(cont), end(cont), std::forward(t)); + } + + template + decltype(auto) find_if(Cont&& cont, UnaryPredicate&& f) + { + using std::begin; + using std::end; + return std::find_if(begin(cont), end(cont), std::forward(f)); + } + +} // namespace waybar::util diff --git a/include/util/chrono.hpp b/include/util/chrono.hpp new file mode 100644 index 00000000..e1f51f78 --- /dev/null +++ b/include/util/chrono.hpp @@ -0,0 +1,86 @@ +#pragma once + +#include +#include +#include +#include +#include + +namespace waybar::chrono { + + using namespace std::chrono; + + using clock = std::chrono::system_clock; + using duration = clock::duration; + using time_point = std::chrono::time_point; + + inline struct timespec to_timespec(time_point t) noexcept + { + long secs = duration_cast(t.time_since_epoch()).count(); + long nsc = duration_cast(t.time_since_epoch() % seconds(1)).count(); + return {secs, nsc}; + } + + inline time_point to_time_point(struct timespec t) noexcept + { + return time_point(duration_cast(seconds(t.tv_sec) + nanoseconds(t.tv_nsec))); + } + +} + +namespace waybar::util { + + struct SleeperThread { + SleeperThread() = default; + + SleeperThread(std::function func) + : thread{[this, func] { + do { + func(); + } while (do_run); + }} + {} + + SleeperThread& operator=(std::function func) + { + thread = std::thread([this, func] { + do { + func(); + } while (do_run); + }); + return *this; + } + + + auto sleep_for(chrono::duration dur) + { + auto lock = std::unique_lock(mutex); + return condvar.wait_for(lock, dur); + } + + auto sleep_until(chrono::time_point time) + { + auto lock = std::unique_lock(mutex); + return condvar.wait_until(lock, time); + } + + auto wake_up() + { + condvar.notify_all(); + } + + ~SleeperThread() + { + do_run = false; + condvar.notify_all(); + thread.join(); + } + + private: + std::thread thread; + std::condition_variable condvar; + std::mutex mutex; + bool do_run = true; + }; + +} diff --git a/include/util/ptr_vec.hpp b/include/util/ptr_vec.hpp new file mode 100644 index 00000000..6e970485 --- /dev/null +++ b/include/util/ptr_vec.hpp @@ -0,0 +1,581 @@ +#pragma once + +#include +#include +#include +#include +#include "algorithm.hpp" + +namespace waybar::util { + + /// An iterator wrapper that dereferences twice. + template + struct double_iterator { + using wrapped = Iter; + + using value_type = std::decay_t())>; + using difference_type = typename wrapped::difference_type; + using reference = value_type&; + using pointer = value_type*; + using iterator_category = std::random_access_iterator_tag; + + using self_t = double_iterator; + + double_iterator(wrapped w) : _iter(std::move(w)) {} + double_iterator() : _iter() {} + + reference operator*() const + { + return (**_iter); + } + pointer operator->() const + { + return &(**_iter); + } + + self_t& operator++() + { + _iter.operator++(); + return *this; + } + self_t operator++(int i) + { + return _iter.operator++(i); + } + self_t& operator--() + { + _iter.operator--(); + return *this; + } + self_t operator--(int i) + { + return _iter.operator--(i); + } + + auto operator==(const self_t& rhs) const noexcept + { + return _iter == rhs._iter; + } + auto operator!=(const self_t& rhs) const noexcept + { + return _iter != rhs._iter; + } + auto operator<(const self_t& rhs) const noexcept + { + return _iter < rhs._iter; + } + auto operator>(const self_t& rhs) const noexcept + { + return _iter > rhs._iter; + } + auto operator<=(const self_t& rhs) const noexcept + { + return _iter <= rhs._iter; + } + auto operator>=(const self_t& rhs) const noexcept + { + return _iter >= rhs._iter; + } + + self_t operator+(difference_type d) const noexcept + { + return _iter + d; + } + self_t operator-(difference_type d) const noexcept + { + return _iter - d; + } + auto operator-(const self_t& rhs) const noexcept + { + return _iter - rhs._iter; + } + + self_t& operator+=(difference_type d) + { + _iter += d; + return *this; + } + self_t& operator-=(difference_type d) + { + _iter -= d; + return *this; + } + + operator wrapped&() + { + return _iter; + } + operator const wrapped&() const + { + return _iter; + } + + wrapped& data() + { + return _iter; + } + const wrapped& data() const + { + return _iter; + } + + private: + wrapped _iter; + }; + + template + auto operator+(typename double_iterator::difference_type diff, double_iterator iter) + { + return iter + diff; + } + + /// To avoid clients being moved, they are stored in unique_ptrs, which are + /// moved around in a vector. This class is purely for convenience, to still + /// have iterator semantics, and a few other utility functions + template + struct ptr_vec { + using value_type = T; + + std::vector> _order; + + using iterator = double_iterator; + using const_iterator = double_iterator; + + using reverse_iterator = double_iterator; + using const_reverse_iterator = + double_iterator; + + value_type& push_back(const value_type& v) + { + auto ptr = std::make_unique(v); + auto res = ptr.get(); + _order.push_back(std::move(ptr)); + return *res; + } + + value_type& push_back(value_type&& v) + { + auto ptr = std::make_unique(std::move(v)); + auto res = ptr.get(); + _order.push_back(std::move(ptr)); + return *res; + } + + value_type& push_back(std::unique_ptr ptr) + { + auto res = ptr.get(); + _order.push_back(std::move(ptr)); + return *res; + } + + template + value_type& emplace_back(Args&&... args) + { + return push_back(std::make_unique(std::forward(args)...)); + } + + std::unique_ptr erase(const value_type& v) + { + auto iter = + std::find_if(_order.begin(), _order.end(), [&v](auto&& uptr) { return uptr.get() == &v; }); + if (iter != _order.end()) { + auto uptr = std::move(*iter); + _order.erase(iter); + return uptr; + } + return nullptr; + } + + iterator rotate_to_back(const value_type& v) + { + auto iter = + std::find_if(_order.begin(), _order.end(), [&v](auto&& uptr) { return uptr.get() == &v; }); + return rotate_to_back(iter); + } + + iterator rotate_to_back(iterator iter) + { + if (iter != _order.end()) { + { + return std::rotate(iter.data(), iter.data() + 1, _order.end()); + } + } + return end(); + } + + iterator rotate_to_front(const value_type& v) + { + auto iter = + std::find_if(_order.begin(), _order.end(), [&v](auto&& uptr) { return uptr.get() == &v; }); + return rotate_to_front(iter); + } + + iterator rotate_to_front(iterator iter) + { + if (iter != _order.end()) { + { + return std::rotate(_order.begin(), iter.data(), iter.data() + 1); + } + } + return end(); + } + + std::size_t size() const noexcept + { + return _order.size(); + } + + bool empty() const noexcept + { + return _order.empty(); + } + + std::size_t capacity() const noexcept + { + return _order.capacity(); + } + + std::size_t max_size() const noexcept + { + return _order.max_size(); + } + + void reserve(std::size_t new_cap) + { + _order.reserve(new_cap); + } + + void shrink_to_fit() + { + _order.shrink_to_fit(); + } + + value_type& operator[](std::size_t n) + { + return *_order[n]; + } + + const value_type& operator[](std::size_t n) const + { + return *_order[n]; + } + + value_type& at(std::size_t n) + { + return *_order.at(n); + } + + const value_type& at(std::size_t n) const + { + return *_order.at(n); + } + + iterator begin() + { + return _order.begin(); + } + iterator end() + { + return _order.end(); + } + const_iterator begin() const + { + return _order.begin(); + } + const_iterator end() const + { + return _order.end(); + } + + reverse_iterator rbegin() + { + return _order.rbegin(); + } + reverse_iterator rend() + { + return _order.rend(); + } + const_reverse_iterator rbegin() const + { + return _order.rbegin(); + } + const_reverse_iterator rend() const + { + return _order.rend(); + } + + value_type& front() + { + return *_order.front(); + } + + value_type& back() + { + return *_order.back(); + } + + const value_type& front() const + { + return *_order.front(); + } + + const value_type& back() const + { + return *_order.back(); + } + + std::vector>& underlying() { + return _order; + } + }; + + template + std::unique_ptr erase_this(ptr_vec& vec, T2* el) + { + return vec.erase(*el); + } + + template + std::unique_ptr erase_this(ptr_vec& vec, T2& el) + { + return vec.erase(el); + } + + template + struct non_null_ptr { + non_null_ptr() = delete; + constexpr non_null_ptr(T* ptr) : _ptr(ptr) + { + assert(ptr != nullptr); + } + non_null_ptr(std::nullptr_t) = delete; + + constexpr non_null_ptr(const non_null_ptr&) = default; + constexpr non_null_ptr(non_null_ptr&&) = default; + constexpr non_null_ptr& operator=(const non_null_ptr&) = default; + constexpr non_null_ptr& operator=(non_null_ptr&&) = default; + + constexpr T& operator*() const noexcept + { + return *_ptr; + } + + constexpr T* operator->() const noexcept + { + return _ptr; + } + + constexpr operator T*() noexcept + { + return _ptr; + } + + constexpr operator T* const() const noexcept + { + return _ptr; + } + + private: + T* _ptr; + }; + + template + struct ref_vec { + using value_type = T; + + std::vector _order; + + using iterator = double_iterator; + using const_iterator = double_iterator; + + using reverse_iterator = double_iterator; + using const_reverse_iterator = + double_iterator; + + ref_vec() = default; + + ref_vec(std::initializer_list lst) : _order {lst} { }; + + template()), value_type&>>> + ref_vec(InputIter iter1, InputIter iter2) { + _order.reserve(std::distance(iter1, iter2)); + std::transform(iter1, iter2, std::back_inserter(_order), [] (auto& v) {return &v; }); + } + + template().begin()), value_type&>>> + ref_vec(Range&& rng) : ref_vec (std::begin(rng), std::end(rng)) { } + + value_type& push_back(value_type& v) + { + _order.push_back(&v); + return v; + } + + value_type& push_back(non_null_ptr ptr) + { + _order.push_back(ptr); + return *ptr; + } + + value_type& emplace_back(value_type& v) + { + return push_back(v); + } + + std::unique_ptr erase(const value_type& v) + { + auto iter = + std::find_if(_order.begin(), _order.end(), [&v](auto&& ptr) { return ptr == &v; }); + if (iter != _order.end()) { + auto uptr = std::move(*iter); + _order.erase(iter); + return uptr; + } + return nullptr; + } + + iterator rotate_to_back(const value_type& v) + { + auto iter = + std::find_if(_order.begin(), _order.end(), [&v](auto&& ptr) { return ptr == &v; }); + return rotate_to_back(iter); + } + + iterator rotate_to_back(iterator iter) + { + if (iter != _order.end()) { + { + return std::rotate(iter.data(), iter.data() + 1, _order.end()); + } + } + return end(); + } + + iterator rotate_to_front(const value_type& v) + { + auto iter = + std::find_if(_order.begin(), _order.end(), [&v](auto&& ptr) { return ptr == &v; }); + return rotate_to_front(iter); + } + + iterator rotate_to_front(iterator iter) + { + if (iter != _order.end()) { + { + return std::rotate(_order.begin(), iter.data(), iter.data() + 1); + } + } + return end(); + } + + std::size_t size() const noexcept + { + return _order.size(); + } + + bool empty() const noexcept + { + return _order.empty(); + } + + std::size_t capacity() const noexcept + { + return _order.capacity(); + } + + std::size_t max_size() const noexcept + { + return _order.max_size(); + } + + void reserve(std::size_t new_cap) + { + _order.reserve(new_cap); + } + + void shrink_to_fit() + { + _order.shrink_to_fit(); + } + + value_type& operator[](std::size_t n) + { + return *_order[n]; + } + + const value_type& operator[](std::size_t n) const + { + return *_order[n]; + } + + value_type& at(std::size_t n) + { + return *_order.at(n); + } + + const value_type& at(std::size_t n) const + { + return *_order.at(n); + } + + iterator begin() + { + return _order.begin(); + } + iterator end() + { + return _order.end(); + } + const_iterator begin() const + { + return _order.begin(); + } + const_iterator end() const + { + return _order.end(); + } + + reverse_iterator rbegin() + { + return _order.rbegin(); + } + reverse_iterator rend() + { + return _order.rend(); + } + const_reverse_iterator rbegin() const + { + return _order.rbegin(); + } + const_reverse_iterator rend() const + { + return _order.rend(); + } + + value_type& front() + { + return *_order.front(); + } + + value_type& back() + { + return *_order.back(); + } + + const value_type& front() const + { + return *_order.front(); + } + + const value_type& back() const + { + return *_order.back(); + } + + std::vector& underlying() { + return _order; + } + }; + + +} // namespace waybar::util diff --git a/meson.build b/meson.build new file mode 100644 index 00000000..17dbf7fa --- /dev/null +++ b/meson.build @@ -0,0 +1,49 @@ +project('waybar', 'cpp', 'c', default_options : ['cpp_std=c++17']) + +cpp_args = [] +cpp_link_args = [] + +if false # libc++ + cpp_args += ['-stdlib=libc++'] + cpp_link_args += ['-stdlib=libc++', '-lc++abi'] + + cpp_link_args += ['-lc++fs'] +else + # TODO: For std::filesystem in libstdc++. Still unstable? Or why is it not in libstdc++ proper yet? + cpp_link_args += ['-lstdc++fs'] +endif + +add_global_arguments(cpp_args, language : 'cpp') +add_global_link_arguments(cpp_link_args, language : 'cpp') + +thread_dep = dependency('threads') +libinput = dependency('libinput') +fmt = dependency('fmt', fallback: ['fmtlib', 'fmt_dep']) +wayland_client = dependency('wayland-client') +wayland_cursor = dependency('wayland-cursor') +wayland_protos = dependency('wayland-protocols') +wlroots = dependency('wlroots', fallback: ['wlroots', 'wlroots']) +gtkmm = dependency('gtkmm-3.0') +jsoncpp = dependency('jsoncpp') +sigcpp = dependency('sigc++-2.0') + +subdir('protocol') + +executable( + 'waybar', + run_command('find', './src', '-name', '*.cpp').stdout().strip().split('\n'), + dependencies: [ + thread_dep, + wlroots, + client_protos, + wayland_client, + fmt, + sigcpp, + jsoncpp, + libinput, + wayland_cursor, + gtkmm, + ], + include_directories: [include_directories('include')], + install: true, +) diff --git a/protocol/meson.build b/protocol/meson.build new file mode 100644 index 00000000..9f961f4e --- /dev/null +++ b/protocol/meson.build @@ -0,0 +1,47 @@ +wl_protocol_dir = wayland_protos.get_pkgconfig_variable('pkgdatadir') + +wayland_scanner = find_program('wayland-scanner') + +# should check wayland_scanner's version, but it is hard to get +if wayland_client.version().version_compare('>=1.14.91') + code_type = 'private-code' +else + code_type = 'code' +endif + +wayland_scanner_code = generator( + wayland_scanner, + output: '@BASENAME@-protocol.c', + arguments: [code_type, '@INPUT@', '@OUTPUT@'], +) + +wayland_scanner_client = generator( + wayland_scanner, + output: '@BASENAME@-client-protocol.h', + arguments: ['client-header', '@INPUT@', '@OUTPUT@'], +) + +client_protocols = [ + [wl_protocol_dir, 'stable/xdg-shell/xdg-shell.xml'], + ['wlr-layer-shell-unstable-v1.xml'], +] + +client_protos_src = [] +client_protos_headers = [] + +foreach p : client_protocols + xml = join_paths(p) + client_protos_src += wayland_scanner_code.process(xml) + client_protos_headers += wayland_scanner_client.process(xml) +endforeach + +lib_client_protos = static_library( + 'client_protos', + client_protos_src + client_protos_headers, + dependencies: [wayland_client] +) # for the include directory + +client_protos = declare_dependency( + link_with: lib_client_protos, + sources: client_protos_headers, +) diff --git a/protocol/wlr-layer-shell-unstable-v1.xml b/protocol/wlr-layer-shell-unstable-v1.xml new file mode 100644 index 00000000..fb4f6b23 --- /dev/null +++ b/protocol/wlr-layer-shell-unstable-v1.xml @@ -0,0 +1,285 @@ + + + + Copyright © 2017 Drew DeVault + + Permission to use, copy, modify, distribute, and sell this + software and its documentation for any purpose is hereby granted + without fee, provided that the above copyright notice appear in + all copies and that both that copyright notice and this permission + notice appear in supporting documentation, and that the name of + the copyright holders not be used in advertising or publicity + pertaining to distribution of the software without specific, + written prior permission. The copyright holders make no + representations about the suitability of this software for any + purpose. It is provided "as is" without express or implied + warranty. + + THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS + SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY + SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN + AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, + ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF + THIS SOFTWARE. + + + + + Clients can use this interface to assign the surface_layer role to + wl_surfaces. Such surfaces are assigned to a "layer" of the output and + rendered with a defined z-depth respective to each other. They may also be + anchored to the edges and corners of a screen and specify input handling + semantics. This interface should be suitable for the implementation of + many desktop shell components, and a broad number of other applications + that interact with the desktop. + + + + + Create a layer surface for an existing surface. This assigns the role of + layer_surface, or raises a protocol error if another role is already + assigned. + + Creating a layer surface from a wl_surface which has a buffer attached + or committed is a client error, and any attempts by a client to attach + or manipulate a buffer prior to the first layer_surface.configure call + must also be treated as errors. + + You may pass NULL for output to allow the compositor to decide which + output to use. Generally this will be the one that the user most + recently interacted with. + + Clients can specify a namespace that defines the purpose of the layer + surface. + + + + + + + + + + + + + + + + + These values indicate which layers a surface can be rendered in. They + are ordered by z depth, bottom-most first. Traditional shell surfaces + will typically be rendered between the bottom and top layers. + Fullscreen shell surfaces are typically rendered at the top layer. + Multiple surfaces can share a single layer, and ordering within a + single layer is undefined. + + + + + + + + + + + + An interface that may be implemented by a wl_surface, for surfaces that + are designed to be rendered as a layer of a stacked desktop-like + environment. + + Layer surface state (size, anchor, exclusive zone, margin, interactivity) + is double-buffered, and will be applied at the time wl_surface.commit of + the corresponding wl_surface is called. + + + + + Sets the size of the surface in surface-local coordinates. The + compositor will display the surface centered with respect to its + anchors. + + If you pass 0 for either value, the compositor will assign it and + inform you of the assignment in the configure event. You must set your + anchor to opposite edges in the dimensions you omit; not doing so is a + protocol error. Both values are 0 by default. + + Size is double-buffered, see wl_surface.commit. + + + + + + + + Requests that the compositor anchor the surface to the specified edges + and corners. If two orthoginal edges are specified (e.g. 'top' and + 'left'), then the anchor point will be the intersection of the edges + (e.g. the top left corner of the output); otherwise the anchor point + will be centered on that edge, or in the center if none is specified. + + Anchor is double-buffered, see wl_surface.commit. + + + + + + + Requests that the compositor avoids occluding an area of the surface + with other surfaces. The compositor's use of this information is + implementation-dependent - do not assume that this region will not + actually be occluded. + + A positive value is only meaningful if the surface is anchored to an + edge, rather than a corner. The zone is the number of surface-local + coordinates from the edge that are considered exclusive. + + Surfaces that do not wish to have an exclusive zone may instead specify + how they should interact with surfaces that do. If set to zero, the + surface indicates that it would like to be moved to avoid occluding + surfaces with a positive excluzive zone. If set to -1, the surface + indicates that it would not like to be moved to accomodate for other + surfaces, and the compositor should extend it all the way to the edges + it is anchored to. + + For example, a panel might set its exclusive zone to 10, so that + maximized shell surfaces are not shown on top of it. A notification + might set its exclusive zone to 0, so that it is moved to avoid + occluding the panel, but shell surfaces are shown underneath it. A + wallpaper or lock screen might set their exclusive zone to -1, so that + they stretch below or over the panel. + + The default value is 0. + + Exclusive zone is double-buffered, see wl_surface.commit. + + + + + + + Requests that the surface be placed some distance away from the anchor + point on the output, in surface-local coordinates. Setting this value + for edges you are not anchored to has no effect. + + The exclusive zone includes the margin. + + Margin is double-buffered, see wl_surface.commit. + + + + + + + + + + Set to 1 to request that the seat send keyboard events to this layer + surface. For layers below the shell surface layer, the seat will use + normal focus semantics. For layers above the shell surface layers, the + seat will always give exclusive keyboard focus to the top-most layer + which has keyboard interactivity set to true. + + Layer surfaces receive pointer, touch, and tablet events normally. If + you do not want to receive them, set the input region on your surface + to an empty region. + + Events is double-buffered, see wl_surface.commit. + + + + + + + This assigns an xdg_popup's parent to this layer_surface. This popup + should have been created via xdg_surface::get_popup with the parent set + to NULL, and this request must be invoked before committing the popup's + initial state. + + See the documentation of xdg_popup for more details about what an + xdg_popup is and how it is used. + + + + + + + When a configure event is received, if a client commits the + surface in response to the configure event, then the client + must make an ack_configure request sometime before the commit + request, passing along the serial of the configure event. + + If the client receives multiple configure events before it + can respond to one, it only has to ack the last configure event. + + A client is not required to commit immediately after sending + an ack_configure request - it may even ack_configure several times + before its next surface commit. + + A client may send multiple ack_configure requests before committing, but + only the last request sent before a commit indicates which configure + event the client really is responding to. + + + + + + + This request destroys the layer surface. + + + + + + The configure event asks the client to resize its surface. + + Clients should arrange their surface for the new states, and then send + an ack_configure request with the serial sent in this configure event at + some point before committing the new surface. + + The client is free to dismiss all but the last configure event it + received. + + The width and height arguments specify the size of the window in + surface-local coordinates. + + The size is a hint, in the sense that the client is free to ignore it if + it doesn't resize, pick a smaller size (to satisfy aspect ratio or + resize in steps of NxM pixels). If the client picks a smaller size and + is anchored to two opposite anchors (e.g. 'top' and 'bottom'), the + surface will be centered on this axis. + + If the width or height arguments are zero, it means the client should + decide its own window dimension. + + + + + + + + + The closed event is sent by the compositor when the surface will no + longer be shown. The output may have been destroyed or the user may + have asked for it to be removed. Further changes to the surface will be + ignored. The client should destroy the resource after receiving this + event, and create a new surface if they so choose. + + + + + + + + + + + + + + + + + diff --git a/resources/style.css b/resources/style.css new file mode 100644 index 00000000..518f32a5 --- /dev/null +++ b/resources/style.css @@ -0,0 +1,45 @@ +* { + border: none; + border-radius: 0; + font-family: Roboto, Helvetica, Arial, sans-serif; + font-size: 13px; +} + +window { + background: rgba(43, 48, 59, 0.5); + border-bottom: 3px solid rgba(100, 114, 125, 0.5); + color: white; +} + +.focused-window-title { + padding: 0px 10px; + min-height: 0px; +} + +.workspace-selector button { + padding: 0 5px; + background: transparent; + color: white; + border-bottom: 3px solid transparent; +} + +.workspace-selector button.current { + background: #64727D; + border-bottom: 3px solid white; +} + +.clock-widget { + background-color: #64727D; + padding: 0 10px; + margin: 0 5px; + border-bottom: 2px solid transparent; +} + +.battery-status { + padding: 0 10px; + border-bottom: 2px solid transparent; +} + +.battery-status.battery-charging { + background-color: #26A65B; +} diff --git a/src/bar.cpp b/src/bar.cpp new file mode 100644 index 00000000..797263f3 --- /dev/null +++ b/src/bar.cpp @@ -0,0 +1,169 @@ +#include +#include +#include +#include "bar.hpp" +#include "client.hpp" +#include "util/chrono.hpp" +#include "modules/clock.hpp" +#include "modules/workspaces.hpp" +#include "modules/battery.hpp" + +static void handle_geometry(void *data, struct wl_output *wl_output, int32_t x, + int32_t y, int32_t physical_width, int32_t physical_height, int32_t subpixel, + const char *make, const char *model, int32_t transform) +{ + // Nothing here +} + +static void handle_mode(void *data, struct wl_output *wl_output, uint32_t f, + int32_t w, int32_t h, int32_t refresh) +{ + auto o = reinterpret_cast(data); + std::cout << fmt::format("Bar width configured: {}", w) << std::endl; + o->set_width(w); +} + +static void handle_done(void *data, struct wl_output *) +{ + // Nothing here +} + +static void handle_scale(void *data, struct wl_output *wl_output, + int32_t factor) +{ + // Nothing here +} + +static const struct wl_output_listener outputListener = { + .geometry = handle_geometry, + .mode = handle_mode, + .done = handle_done, + .scale = handle_scale, +}; + +static void layer_surface_handle_configure( + void *data, struct zwlr_layer_surface_v1 *surface, uint32_t serial, + uint32_t width, uint32_t height) +{ + auto o = reinterpret_cast(data); + o->window.show_all(); + zwlr_layer_surface_v1_ack_configure(surface, serial); + if (o->client.height != height) + { + height = o->client.height; + std::cout << fmt::format("New Height: {}", height) << std::endl; + zwlr_layer_surface_v1_set_size(surface, width, height); + zwlr_layer_surface_v1_set_exclusive_zone(surface, o->visible ? height : 0); + wl_surface_commit(o->surface); + } +} + +static void layer_surface_handle_closed(void *data, + struct zwlr_layer_surface_v1 *surface) +{ + auto o = reinterpret_cast(data); + zwlr_layer_surface_v1_destroy(o->layer_surface); + o->layer_surface = NULL; + wl_surface_destroy(o->surface); + o->surface = NULL; + o->window.close(); +} + +static const struct zwlr_layer_surface_v1_listener layerSurfaceListener = { + .configure = layer_surface_handle_configure, + .closed = layer_surface_handle_closed, +}; + +waybar::Bar::Bar(Client &client, std::unique_ptr &&p_output) + : client(client), window{Gtk::WindowType::WINDOW_TOPLEVEL}, + output(std::move(p_output)) +{ + wl_output_add_listener(*output, &outputListener, this); + window.set_title("waybar"); + window.set_decorated(false); + // window.set_resizable(false); + setup_css(); + setup_widgets(); + gtk_widget_realize(GTK_WIDGET(window.gobj())); + GdkWindow *gdkWindow = gtk_widget_get_window(GTK_WIDGET(window.gobj())); + gdk_wayland_window_set_use_custom_surface(gdkWindow); + surface = gdk_wayland_window_get_wl_surface(gdkWindow); + layer_surface = zwlr_layer_shell_v1_get_layer_surface( + client.layer_shell, surface, *output, ZWLR_LAYER_SHELL_V1_LAYER_TOP, + "waybar"); + zwlr_layer_surface_v1_set_anchor(layer_surface, + ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT |ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP | + ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT); + zwlr_layer_surface_v1_set_size(layer_surface, width, client.height); + zwlr_layer_surface_v1_add_listener(layer_surface, &layerSurfaceListener, + this); + wl_surface_commit(surface); +} + +auto waybar::Bar::setup_css() -> void +{ + css_provider = Gtk::CssProvider::create(); + style_context = Gtk::StyleContext::create(); + + // load our css file, wherever that may be hiding + if (css_provider->load_from_path(client.css_file)) + { + Glib::RefPtr screen = window.get_screen(); + style_context->add_provider_for_screen(screen, css_provider, + GTK_STYLE_PROVIDER_PRIORITY_USER); + } +} + +auto waybar::Bar::set_width(int width) -> void +{ + this->width = width; + window.set_size_request(width); + window.resize(width, client.height); + zwlr_layer_surface_v1_set_size(layer_surface, width, 40); + wl_surface_commit(surface); +} + +auto waybar::Bar::toggle() -> void +{ + visible = !visible; + auto zone = visible ? client.height : 0; + zwlr_layer_surface_v1_set_exclusive_zone(layer_surface, zone); + wl_surface_commit(surface); +} + +auto waybar::Bar::setup_widgets() -> void +{ + auto &left = *Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL, 0)); + auto ¢er = *Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL, 0)); + auto &right = *Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL, 0)); + + auto &box1 = *Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL, 0)); + window.add(box1); + box1.set_homogeneous(true); + box1.pack_start(left, true, true); + box1.pack_start(center, false, false); + box1.pack_end(right, true, true); + + auto &focused_window = *Gtk::manage(new Gtk::Label()); + focused_window.get_style_context()->add_class("focused-window-title"); + client.signals.focused_window_name.connect( + [&focused_window](std::string focused_window_name) { + if (focused_window_name.size() > 70) + { + focused_window_name.erase(67); + focused_window_name += "..."; + } + focused_window.set_text(focused_window_name); + }); + + focused_window.set_hexpand(false); + + auto &clock = *new waybar::modules::Clock(); + auto &workspace_selector = *new waybar::modules::WorkspaceSelector(*this); + auto &battery = *new waybar::modules::Battery(); + + left.pack_start(workspace_selector, false, true, 0); + // center.pack_start(workspace_selector, true, false, 10); + right.pack_end(clock, false, false, 0); + right.pack_end(battery, false, false, 0); +} diff --git a/src/client.cpp b/src/client.cpp new file mode 100644 index 00000000..d3c912d3 --- /dev/null +++ b/src/client.cpp @@ -0,0 +1,47 @@ +#include "client.hpp" + +static void handle_global(void *data, struct wl_registry *registry, + uint32_t name, const char *interface, uint32_t version) +{ + auto o = reinterpret_cast(data); + if (!strcmp(interface, zwlr_layer_shell_v1_interface.name)) { + o->layer_shell = (zwlr_layer_shell_v1 *)wl_registry_bind(registry, name, + &zwlr_layer_shell_v1_interface, version); + } else if (!strcmp(interface, wl_output_interface.name)) { + auto output = std::make_unique(); + *output = (struct wl_output *)wl_registry_bind(registry, name, + &wl_output_interface, version); + o->bars.emplace_back(*o, std::move(output)); + } +} + +static void handle_global_remove(void *data, + struct wl_registry *registry, uint32_t name) +{ + // TODO +} + +static const struct wl_registry_listener registry_listener = { + .global = handle_global, + .global_remove = handle_global_remove, +}; + +waybar::Client::Client(int argc, char* argv[]) + : gtk_main(argc, argv), + gdk_display(Gdk::Display::get_default()), + wlDisplay(gdk_wayland_display_get_wl_display(gdk_display->gobj())) +{} + +void waybar::Client::bind_interfaces() +{ + registry = wl_display_get_registry(wlDisplay); + wl_registry_add_listener(registry, ®istry_listener, this); + wl_display_roundtrip(wlDisplay); +} + +int waybar::Client::main(int argc, char* argv[]) +{ + bind_interfaces(); + gtk_main.run(); + return 0; +} diff --git a/src/ipc/client.cpp b/src/ipc/client.cpp new file mode 100644 index 00000000..fce99ca6 --- /dev/null +++ b/src/ipc/client.cpp @@ -0,0 +1,101 @@ +#define _POSIX_C_SOURCE 200809L +#include +#include +#include +#include +#include "ipc/client.hpp" + +static const char ipc_magic[] = {'i', '3', '-', 'i', 'p', 'c'}; +static const size_t ipc_header_size = sizeof(ipc_magic)+8; + +std::string get_socketpath(void) { + const char *env = getenv("SWAYSOCK"); + if (env) return std::string(env); + std::string str; + { + std::string str_buf; + FILE* in; + char buf[512] = { 0 }; + if (!(in = popen("sway --get-socketpath 2>/dev/null", "r"))) { + throw std::runtime_error("Failed to get socket path"); + } + while (fgets(buf, sizeof(buf), in) != nullptr) { + str_buf.append(buf, sizeof(buf)); + } + pclose(in); + str = str_buf; + } + if (str.back() == '\n') { + str.pop_back(); + } + return str; +} + +int ipc_open_socket(std::string socket_path) { + struct sockaddr_un addr; + int socketfd; + if ((socketfd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) { + throw std::runtime_error("Unable to open Unix socket"); + } + addr.sun_family = AF_UNIX; + strncpy(addr.sun_path, socket_path.c_str(), sizeof(addr.sun_path) - 1); + addr.sun_path[sizeof(addr.sun_path) - 1] = 0; + int l = sizeof(struct sockaddr_un); + if (connect(socketfd, (struct sockaddr *)&addr, l) == -1) { + throw std::runtime_error("Unable to connect to " + socket_path); + } + return socketfd; +} + +struct ipc_response ipc_recv_response(int socketfd) { + char data[ipc_header_size]; + uint32_t *data32 = (uint32_t *)(data + sizeof(ipc_magic)); + + size_t total = 0; + while (total < ipc_header_size) { + ssize_t received = recv(socketfd, data + total, ipc_header_size - total, 0); + if (received <= 0) { + throw std::runtime_error("Unable to receive IPC response"); + } + total += received; + } + + struct ipc_response response; + + total = 0; + response.size = data32[0]; + response.type = data32[1]; + char payload[response.size + 1]; + + while (total < response.size) { + ssize_t received = recv(socketfd, payload + total, response.size - total, 0); + if (received < 0) { + throw std::runtime_error("Unable to receive IPC response"); + } + total += received; + } + payload[response.size] = '\0'; + response.payload = std::string(payload); + return response; +} + +std::string ipc_single_command(int socketfd, uint32_t type, const char *payload, uint32_t *len) { + char data[ipc_header_size]; + uint32_t *data32 = (uint32_t *)(data + sizeof(ipc_magic)); + memcpy(data, ipc_magic, sizeof(ipc_magic)); + data32[0] = *len; + data32[1] = type; + + if (send(socketfd, data, ipc_header_size, 0) == -1) { + throw std::runtime_error("Unable to send IPC header"); + } + + if (send(socketfd, payload, *len, 0) == -1) { + throw std::runtime_error("Unable to send IPC payload"); + } + + struct ipc_response resp = ipc_recv_response(socketfd); + std::string response = resp.payload; + *len = resp.size; + return response; +} diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 00000000..9c4bad58 --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,30 @@ +#include +#include +#include +#include +#include "client.hpp" + +namespace waybar { + static Client* client; +} + +int main(int argc, char* argv[]) +{ + try { + waybar::Client c(argc, argv); + waybar::client = &c; + std::signal(SIGUSR1, [] (int signal) { + for (auto& bar : waybar::client->bars) { + bar.toggle(); + } + }); + + return c.main(argc, argv); + } catch (const std::exception& e) { + std::cerr << e.what() << std::endl; + return 1; + } catch (const Glib::Exception& e) { + std::cerr << e.what().c_str() << std::endl; + return 1; + } +} diff --git a/src/modules/battery.cpp b/src/modules/battery.cpp new file mode 100644 index 00000000..ede5852f --- /dev/null +++ b/src/modules/battery.cpp @@ -0,0 +1,49 @@ +#include "modules/battery.hpp" + +waybar::modules::Battery::Battery() +{ + try { + for (auto &node : fs::directory_iterator(_data_dir)) { + if (fs::is_directory(node) && fs::exists(node / "charge_now") && + fs::exists(node / "charge_full")) { + _batteries.push_back(node); + } + } + } catch (fs::filesystem_error &e) { + std::cerr << e.what() << std::endl; + } + + _label.get_style_context()->add_class("battery-status"); + + _thread = [this] { + update(); + _thread.sleep_for(chrono::minutes(1)); + }; +} + +auto waybar::modules::Battery::update() -> void +{ + try { + for (auto &bat : _batteries) { + int full, now; + std::string status; + std::ifstream(bat / "charge_now") >> now; + std::ifstream(bat / "charge_full") >> full; + std::ifstream(bat / "status") >> status; + if (status == "Charging") { + _label.get_style_context()->add_class("battery-charging"); + } else { + _label.get_style_context()->remove_class("battery-charging"); + } + int pct = float(now) / float(full) * 100.f; + _label.set_text_with_mnemonic(fmt::format("{}% {}", pct, "")); + } + } catch (std::exception &e) { + std::cerr << e.what() << std::endl; + } +} + +waybar::modules::Battery::operator Gtk::Widget &() +{ + return _label; +} diff --git a/src/modules/clock.cpp b/src/modules/clock.cpp new file mode 100644 index 00000000..9ca8b2aa --- /dev/null +++ b/src/modules/clock.cpp @@ -0,0 +1,20 @@ +#include "modules/clock.hpp" + +waybar::modules::Clock::Clock() +{ + _label.get_style_context()->add_class("clock-widget"); + _thread = [this] { + auto now = waybar::chrono::clock::now(); + auto t = std::time(nullptr); + auto localtime = std::localtime(&t); + _label.set_text( + fmt::format("{:02}:{:02}", localtime->tm_hour, localtime->tm_min)); + auto timeout = + std::chrono::floor(now + std::chrono::minutes(1)); + _thread.sleep_until(timeout); + }; +}; + +waybar::modules::Clock::operator Gtk::Widget &() { + return _label; +} diff --git a/src/modules/workspaces.cpp b/src/modules/workspaces.cpp new file mode 100644 index 00000000..c8904952 --- /dev/null +++ b/src/modules/workspaces.cpp @@ -0,0 +1,83 @@ +#include "modules/workspaces.hpp" +#include "ipc/client.hpp" + +waybar::modules::WorkspaceSelector::WorkspaceSelector(Bar &bar) + : _bar(bar), _box(Gtk::manage(new Gtk::Box)) +{ + _box->get_style_context()->add_class("workspace-selector"); + std::string socketPath = get_socketpath(); + _ipcSocketfd = ipc_open_socket(socketPath); + _ipcEventSocketfd = ipc_open_socket(socketPath); + const char *subscribe = "[ \"workspace\", \"mode\" ]"; + uint32_t len = strlen(subscribe); + ipc_single_command(_ipcEventSocketfd, IPC_SUBSCRIBE, subscribe, &len); + _thread = [this] { + update(); + }; +} + +auto waybar::modules::WorkspaceSelector::update() -> void +{ + Json::Value workspaces = _getWorkspaces(); + for (auto it = _buttons.begin(); it != _buttons.end(); ++it) { + auto ws = std::find_if(workspaces.begin(), workspaces.end(), + [it](auto node) -> bool { return node["num"].asInt() == it->first; }); + if (ws == workspaces.end()) { + it->second.hide(); + } + } + for (auto node : workspaces) { + auto it = _buttons.find(node["num"].asInt()); + if (it == _buttons.end()) { + _addWorkspace(node); + } else { + auto styleContext = it->second.get_style_context(); + bool isCurrent = node["focused"].asBool(); + if (styleContext->has_class("current") && !isCurrent) { + styleContext->remove_class("current"); + } else if (!styleContext->has_class("current") && isCurrent) { + styleContext->add_class("current"); + } + it->second.show(); + } + } +} + +void waybar::modules::WorkspaceSelector::_addWorkspace(Json::Value node) +{ + auto pair = _buttons.emplace(node["num"].asInt(), node["name"].asString()); + auto &button = pair.first->second; + button.set_relief(Gtk::RELIEF_NONE); + button.signal_clicked().connect([this, pair] { + auto value = fmt::format("workspace \"{}\"", pair.first->first); + uint32_t size = value.size(); + ipc_single_command(_ipcSocketfd, IPC_COMMAND, value.c_str(), &size); + }); + _box->pack_start(button, false, false, 0); + if (node["focused"].asBool()) { + button.get_style_context()->add_class("current"); + } + button.show(); +} + +Json::Value waybar::modules::WorkspaceSelector::_getWorkspaces() +{ + uint32_t len = 0; + Json::Value root; + Json::CharReaderBuilder builder; + Json::CharReader* reader = builder.newCharReader(); + std::string err; + std::string str = ipc_single_command(_ipcSocketfd, IPC_GET_WORKSPACES, + nullptr, &len); + bool res = reader->parse(str.c_str(), str.c_str() + str.size(), &root, &err); + delete reader; + if (!res) { + std::cerr << err << std::endl; + return nullptr; + } + return root; +} + +waybar::modules::WorkspaceSelector::operator Gtk::Widget &() { + return *_box; +} diff --git a/subprojects/fmtlib.wrap b/subprojects/fmtlib.wrap new file mode 100644 index 00000000..7d833c9e --- /dev/null +++ b/subprojects/fmtlib.wrap @@ -0,0 +1,10 @@ +[wrap-file] +directory = fmt-4.1.0 + +source_url = https://github.com/fmtlib/fmt/archive/4.1.0.tar.gz +source_filename = fmt-4.1.0.tar.gz +source_hash = 46628a2f068d0e33c716be0ed9dcae4370242df135aed663a180b9fd8e36733d + +patch_url = https://wrapdb.mesonbuild.com/v1/projects/fmt/4.1.0/1/get_zip +patch_filename = fmt-4.1.0-1-wrap.zip +patch_hash = 741931f01e558491724fc1c67bff996d1df79c0277626fc463de138052c9ecc0