kranewm

An ICCCM & EWMH compliant X11 reparenting, dynamic window manager, written in C++
git clone git clone git://git.deurzen.net/kranewm.git
Log | Files | Refs | LICENSE

commit 73a48d19aa6ade265811bc8ebc19eae0eb400bb8
parent 8a0056464b8cf801a46a046a6e58c7f85ed624e1
Author: deurzen <m.deurzen@tum.de>
Date:   Fri,  6 Aug 2021 03:22:06 +0200

adds initial IPC message handling functionality

Diffstat:
Msrc/core/main.cc | 3++-
Msrc/core/model.cc | 139+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
Msrc/core/model.hh | 39++++++++++++++++++++++++++++++++++++++-
Msrc/winsys/connection.hh | 18+++++++++++-------
Msrc/winsys/event.hh | 2--
Asrc/winsys/message.hh | 52++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/winsys/util.cc | 2++
Msrc/winsys/xdata/xconnection.cc | 189+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------
Msrc/winsys/xdata/xconnection.hh | 24++++++++++++++++++------
9 files changed, 434 insertions(+), 34 deletions(-)

diff --git a/src/core/main.cc b/src/core/main.cc @@ -1,12 +1,13 @@ #include <iostream> #include "../winsys/xdata/xconnection.hh" +#include "defaults.hh" #include "model.hh" int main(int, char **) { - XConnection conn = {}; + XConnection conn = { WM_NAME }; Model model(conn); model.run(); return 0; diff --git a/src/core/model.cc b/src/core/model.cc @@ -7,14 +7,18 @@ #include <cstdlib> #include <cstring> #include <fstream> +#include <functional> #include <iostream> #include <set> #include <sstream> -#include <sys/wait.h> -#include <unistd.h> #include <unordered_set> #include <vector> +extern "C" { +#include <sys/wait.h> +#include <unistd.h> +} + #ifdef DEBUG #define SPDLOG_ACTIVE_LEVEL SPDLOG_LEVEL_DEBUG #endif @@ -863,13 +867,29 @@ Model::~Model() m_client_map.clear(); } + void Model::run() { while (m_running) - std::visit(event_visitor, m_conn.step()); + if (m_conn.block()) { + // process IPC message + m_conn.process_messages( + [=,this](winsys::Message message) { + std::visit(m_message_visitor, message); + } + ); + + // process windowing system event + m_conn.process_events( + [=,this](winsys::Event event) { + std::visit(m_event_visitor, event); + } + ); + } } + void Model::init_signals() const { @@ -3978,6 +3998,119 @@ Model::handle_screen_change() } +void +Model::process_command(winsys::CommandMessage message) +{ + static const std::unordered_map<std::string_view, winsys::Direction> directions = { + { "Forward", winsys::Direction::Forward }, + { "forward", winsys::Direction::Forward }, + { "fwd", winsys::Direction::Forward }, + { "Backward", winsys::Direction::Backward }, + { "backward", winsys::Direction::Backward }, + { "bwd", winsys::Direction::Backward }, + }; + + static const std::unordered_map<std::string_view, std::function<void(void)>> commands = { + { "toggle_partition", + [&,this]() { + toggle_partition(); + } + }, + { "activate_next_partition", + [&,this]() { + if (!message.args.empty() && directions.count(message.args.front())) + activate_next_partition(directions.at(message.args.front())); + } + }, + { "activate_partition", + [&,this]() { + if (!message.args.empty()) { + std::istringstream index_stream(message.args.front()); + Index index = 0; + + if (index_stream >> index) + activate_partition(index); + } + } + }, + + { "toggle_context", + [&,this]() { + toggle_context(); + } + }, + { "activate_next_context", + [&,this]() { + if (!message.args.empty() && directions.count(message.args.front())) + activate_next_context(directions.at(message.args.front())); + } + }, + { "activate_context", + [&,this]() { + if (!message.args.empty()) { + std::istringstream index_stream(message.args.front()); + Index index = 0; + + if (index_stream >> index) + activate_context(index); + } + } + }, + + { "toggle_workspace", + [&,this]() { + toggle_workspace(); + } + }, + { "activate_next_workspace", + [&,this]() { + if (!message.args.empty() && directions.count(message.args.front())) + activate_next_workspace(directions.at(message.args.front())); + } + }, + { "activate_workspace", + [&,this]() { + if (!message.args.empty()) { + std::istringstream index_stream(message.args.front()); + Index index = 0; + + if (index_stream >> index) + activate_workspace(index); + } + } + }, + }; + + if (!message.args.empty()) { + std::string command = message.args.front(); + message.args.pop_front(); + + if (commands.count(command) > 0) + commands.at(command)(); + } +} + +void +Model::process_config(winsys::ConfigMessage message) +{ +} + +void +Model::process_client(winsys::WindowMessage message) +{ +} + +void +Model::process_workspace(winsys::WorkspaceMessage message) +{ +} + +void +Model::process_query(winsys::QueryMessage message) +{ +} + + // static methods void diff --git a/src/core/model.hh b/src/core/model.hh @@ -55,6 +55,12 @@ private: void handle_frame_extents_request(winsys::FrameExtentsRequestEvent); void handle_screen_change(); + void process_command(winsys::CommandMessage); + void process_config(winsys::ConfigMessage); + void process_client(winsys::WindowMessage); + void process_workspace(winsys::WorkspaceMessage); + void process_query(winsys::QueryMessage); + void acquire_partitions(); const winsys::Screen& active_screen() const; @@ -324,7 +330,38 @@ private: private: Model& m_model; - } event_visitor = EventVisitor(*this); + } m_event_visitor = EventVisitor(*this); + + struct MessageVisitor final + { + MessageVisitor(Model& model): m_model(model) {} + + void operator()(std::monostate) {} + + void operator()(winsys::CommandMessage message) { + m_model.process_command(message); + } + + void operator()(winsys::ConfigMessage message) { + m_model.process_config(message); + } + + void operator()(winsys::WindowMessage message) { + m_model.process_client(message); + } + + void operator()(winsys::WorkspaceMessage message) { + m_model.process_workspace(message); + } + + void operator()(winsys::QueryMessage message) { + m_model.process_query(message); + } + + private: + Model& m_model; + + } m_message_visitor = MessageVisitor(*this); }; diff --git a/src/winsys/connection.hh b/src/winsys/connection.hh @@ -1,18 +1,20 @@ #ifndef __WINSYS_CONNECTION_H_GUARD__ #define __WINSYS_CONNECTION_H_GUARD__ -#include <optional> -#include <string> -#include <tuple> -#include <unordered_set> -#include <vector> - #include "common.hh" #include "event.hh" #include "hints.hh" +#include "message.hh" #include "screen.hh" #include "window.hh" +#include <functional> +#include <optional> +#include <string> +#include <tuple> +#include <unordered_set> +#include <vector> + namespace winsys { @@ -22,7 +24,9 @@ namespace winsys virtual ~Connection() {}; virtual bool flush() = 0; - virtual Event step() = 0; + virtual bool block() = 0; + virtual void process_events(std::function<void(Event)>) = 0; + virtual void process_messages(std::function<void(Message)>) = 0; virtual std::vector<Screen> connected_outputs() = 0; virtual std::vector<Window> top_level_windows() = 0; virtual Pos get_pointer_position() = 0; diff --git a/src/winsys/event.hh b/src/winsys/event.hh @@ -10,8 +10,6 @@ #include <optional> #include <variant> -#include "spdlog/spdlog.h" - namespace winsys { diff --git a/src/winsys/message.hh b/src/winsys/message.hh @@ -0,0 +1,52 @@ +#ifndef __WINSYS_MESSAGE_H_GUARD__ +#define __WINSYS_MESSAGE_H_GUARD__ + +#include "common.hh" +#include "geometry.hh" +#include "input.hh" +#include "window.hh" + +#include <string> +#include <variant> +#include <deque> + +namespace winsys +{ + + struct CommandMessage final + { + std::deque<std::string> args; + }; + + struct ConfigMessage final + { + std::deque<std::string> args; + }; + + struct WindowMessage final + { + std::deque<std::string> args; + }; + + struct WorkspaceMessage final + { + std::deque<std::string> args; + }; + + struct QueryMessage final + { + std::deque<std::string> args; + }; + + typedef std::variant< + std::monostate, + CommandMessage, + ConfigMessage, + WindowMessage, + WorkspaceMessage, + QueryMessage + > Message; + +} + +#endif//__WINSYS_MESSAGE_H_GUARD__ diff --git a/src/winsys/util.cc b/src/winsys/util.cc @@ -1,6 +1,8 @@ #include "util.hh" +extern "C" { #include <unistd.h> +} void Util::die(const std::string&& msg) diff --git a/src/winsys/xdata/xconnection.cc b/src/winsys/xdata/xconnection.cc @@ -2,12 +2,13 @@ #include "../util.hh" #include "xconnection.hh" +#include <algorithm> #include <cstring> +#include <iomanip> #include <iterator> #include <numeric> -#include <proc/readproc.h> #include <sstream> -#include <unistd.h> +#include <deque> extern "C" { #include <X11/XF86keysym.h> @@ -18,18 +19,22 @@ extern "C" { #include <X11/extensions/Xrandr.h> #include <X11/keysym.h> #include <X11/keysymdef.h> +#include <fcntl.h> +#include <proc/readproc.h> +#include <signal.h> +#include <sys/socket.h> +#include <unistd.h> +#include <xcb/xcb.h> +#include <xcb/xproto.h> #include <xkbcommon/xkbcommon-keysyms.h> } -#include <xcb/xcb.h> -#include <xcb/xproto.h> #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wold-style-cast" -XConnection::XConnection() +XConnection::XConnection(const std::string_view wm_name) : mp_dpy(XOpenDisplay(NULL)), - m_conn_number(XConnectionNumber(mp_dpy)), m_root(XDefaultRootWindow(mp_dpy)), m_check_window(XCreateWindow( mp_dpy, @@ -39,7 +44,10 @@ XConnection::XConnection() CopyFromParent, 0, InputOnly, CopyFromParent, 0, NULL )), - m_conn(XConnectionNumber(mp_dpy)), + m_dpy_fd(XConnectionNumber(mp_dpy)), + m_sock_fd(-1), + m_client_fd(-1), + m_max_fd(-1), m_interned_atoms({}), m_atom_names({}), m_keys({}), @@ -118,6 +126,76 @@ XConnection::XConnection() m_event_dispatcher[event_base + RRScreenChangeNotify] = &XConnection::on_screen_change; } + + std::string socket_name = wm_name.data() + std::string("_SOCKET"); + std::string wm_socket; + + if (const char* env_wm_socket = std::getenv(socket_name.c_str())) + wm_socket = std::string(env_wm_socket); + else { + static const std::string delim = "_"; + std::string display_string = XDisplayString(mp_dpy); + int default_screen = XDefaultScreen(mp_dpy); + + std::string host_name = "localhost"; + std::string display_nr; + std::string screen_nr = std::to_string(default_screen); + + std::string::size_type display_nr_pos = display_string.find_last_of(':'); + + if (display_nr_pos != std::string::npos) { + if (display_nr_pos != 0) + host_name = display_string.substr(0, display_nr_pos); + + display_string = display_string.substr(display_nr_pos + 1); + } + + std::string::size_type screen_nr_pos = display_string.find_last_of('.'); + + if (screen_nr_pos != std::string::npos + && display_nr_pos != std::string::npos + && display_nr_pos < screen_nr_pos) + { + display_nr = display_string.substr(display_nr_pos + 1, screen_nr_pos - (display_nr_pos + 1)); + } else + display_nr = display_string.substr(0, screen_nr_pos); + + wm_socket = "/tmp/" + + std::string(wm_name) + + delim + host_name + + delim + display_nr + + delim + screen_nr; + } + + memset(m_sock_path, 0, 256); + memset(&m_sock_addr, 0, sizeof(m_sock_addr)); + + strncpy(m_sock_path, + wm_socket.c_str(), + wm_socket.length() < 256 + ? wm_socket.length() + : 255 + ); + + m_sock_addr.sun_family = AF_UNIX; + + if (snprintf(m_sock_addr.sun_path, sizeof(m_sock_addr.sun_path), "%s", m_sock_path) < 0) + Util::die("unable to write IPC socket path"); + + m_sock_fd = socket(AF_UNIX, SOCK_STREAM, 0); + + if (m_sock_fd == -1) + Util::die("unable to set up IPC socket"); + + unlink(m_sock_path); + + if (bind(m_sock_fd, (struct sockaddr*)&m_sock_addr, sizeof(m_sock_addr)) == -1) + Util::die("unable to bind name to IPC socket"); + + if (listen(m_sock_fd, SOMAXCONN) == -1) + Util::die("unable to listen to IPC socket"); + + fcntl(m_sock_fd, F_SETFD, FD_CLOEXEC | fcntl(m_sock_fd, F_GETFD)); } XConnection::~XConnection() @@ -131,15 +209,98 @@ XConnection::flush() return true; } -winsys::Event -XConnection::step() +bool +XConnection::block() { - next_event(m_current_event); + XFlush(mp_dpy); - if (m_current_event.type >= 0 && m_current_event.type <= 256) - return (this->*(m_event_dispatcher[m_current_event.type]))(); + FD_ZERO(&m_descr); + FD_SET(m_sock_fd, &m_descr); + FD_SET(m_dpy_fd, &m_descr); + m_max_fd = std::max(m_sock_fd, m_dpy_fd); - return std::monostate{}; + return select(m_max_fd + 1, &m_descr, NULL, NULL, NULL) > 0; +} + +void +XConnection::process_events(std::function<void(winsys::Event)> callback) +{ + if (FD_ISSET(m_dpy_fd, &m_descr)) { + while (XPending(mp_dpy)) { + next_event(m_current_event); + + if (m_current_event.type >= 0 && m_current_event.type <= 256) + callback((this->*(m_event_dispatcher[m_current_event.type]))()); + } + } +} + +void +XConnection::process_messages(std::function<void(winsys::Message)> callback) +{ + enum MessageType { + Command, + Config, + Window, + Workspace, + Query + }; + + static const std::unordered_map<std::string_view, MessageType> message_types = { + { "command", Command }, + { "cmd", Command }, + { "config", Config }, + { "conf", Config }, + { "cfg", Config }, + { "client", Window }, + { "cli", Window }, + { "workspace", Workspace }, + { "ws", Workspace }, + { "query", Query }, + { "qr", Query }, + }; + + static int n = 0; + static char msg[BUFSIZ] = { 0 }; + + if (FD_ISSET(m_sock_fd, &m_descr)) { + m_client_fd = accept(m_sock_fd, NULL, 0); + + if (m_client_fd > 0 && (n = recv(m_client_fd, msg, sizeof(msg) - 1, 0)) > 0) { + msg[n] = '\0'; + FILE* rply = fdopen(m_client_fd, "w"); + + if (rply != NULL) { + winsys::Message message = std::monostate{}; + + std::deque<std::string> words = {}; + std::istringstream word_stream(msg); + std::string word; + + while (word_stream >> std::quoted(word)) + words.push_back(word); + + if (!words.empty()) { + std::string_view area = words.front(); + + if (message_types.count(area) > 0) { + words.pop_front(); + + switch (message_types.at(area)) { + case Command: message = winsys::CommandMessage{words}; break; + case Config: message = winsys::ConfigMessage{words}; break; + case Window: message = winsys::WindowMessage{words}; break; + case Workspace: message = winsys::WorkspaceMessage{words}; break; + case Query: message = winsys::QueryMessage{words}; break; + } + } + } + + callback(message); + } else + close(m_client_fd); + } + } } std::vector<winsys::Screen> @@ -319,7 +480,7 @@ XConnection::call_external_command(std::string& command) { if (!fork()) { if (mp_dpy) - close(m_conn_number); + close(m_dpy_fd); setsid(); execl("/bin/sh", "/bin/sh", "-c", ("exec " + command).c_str(), NULL); diff --git a/src/winsys/xdata/xconnection.hh b/src/winsys/xdata/xconnection.hh @@ -6,26 +6,30 @@ #include "../input.hh" #include <cstddef> +#include <functional> #include <iostream> #include <string> #include <unordered_map> #include <variant> extern "C" { -#include <X11/Xlib.h> -#include <X11/cursorfont.h> #include <X11/Xatom.h> +#include <X11/Xlib.h> #include <X11/Xmd.h> +#include <X11/cursorfont.h> +#include <sys/un.h> } class XConnection final: public winsys::Connection { public: - XConnection(); + XConnection(const std::string_view); ~XConnection(); virtual bool flush() override; - virtual winsys::Event step() override; + virtual bool block() override; + virtual void process_events(std::function<void(winsys::Event)>) override; + virtual void process_messages(std::function<void(winsys::Message)>) override; virtual std::vector<winsys::Screen> connected_outputs() override; virtual std::vector<winsys::Window> top_level_windows() override; virtual winsys::Pos get_pointer_position() override; @@ -175,10 +179,18 @@ private: } Display* mp_dpy; - int m_conn_number; winsys::Window m_root; winsys::Window m_check_window; - int m_conn; + + int m_dpy_fd; + int m_sock_fd; + int m_client_fd; + int m_max_fd; + + fd_set m_descr; + char m_sock_path[256]; + char m_state_path[256] = { 0 }; + struct sockaddr_un m_sock_addr; XEvent m_current_event;