https://github.com/akkartik/mu/blob/master/091socket.cc
  1 :(before "End Types")
  2 struct socket_t {
  3   int fd;
  4   sockaddr_in addr;
  5   bool polled;
  6   socket_t() {
  7     fd = 0;
  8     polled = false;
  9     bzero(&addr, sizeof(addr));
 10   }
 11 };
 12 
 13 :(before "End Primitive Recipe Declarations")
 14 _OPEN_CLIENT_SOCKET,
 15 :(before "End Primitive Recipe Numbers")
 16 put(Recipe_ordinal, "$open-client-socket", _OPEN_CLIENT_SOCKET);
 17 :(before "End Primitive Recipe Checks")
 18 case _OPEN_CLIENT_SOCKET: {
 19   if (SIZE(inst.ingredients) != 2) {
 20     raise << maybe(get(Recipe, r).name) << "'$open-client-socket' requires exactly two ingredients, but got '" << to_original_string(inst) << "'\n" << end();
 21     break;
 22   }
 23   if (!is_mu_text(inst.ingredients.at(0))) {
 24     raise << maybe(get(Recipe, r).name) << "first ingredient of '$open-client-socket' should be text (the hostname), but got '" << to_string(inst.ingredients.at(0)) << "'\n" << end();
 25     break
/* See LICENSE file for copyright and license details. */

/* appearance */
static const char *fonts[] = {
    "Sans:size=10.5",
    "VL Gothic:size=10.5",
    "WenQuanYi Micro Hei:size=10.5",
};
static const char dmenufont[] = "-*-terminus-medium-r-*-*-16-*-*-*-*-*-*-*";
static const char normbordercolor[] = "#444444";
static const char normbgcolor[]     = "#222222";
static const char normfgcolor[]     = "#bbbbbb";
static const char selbordercolor[]  = "#005577";
static const char selbgcolor[]      = "#005577";
static const char selfgcolor[]      = "#eeeeee";
static const unsigned int borderpx  = 1;        /* border pixel of windows */
static const unsigned int snap      = 32;       /* snap pixel */
static const Bool showbar           = True;     /* False means no bar */
static const Bool topbar            = True;     /* False means bottom bar */

/* tagging */
static const char *tags[] = { "1", "2", "3", "4", "5", "6", "7", "8", "9" };

static const Rule rules[] = {
	/* xprop(1):
	 *	WM_CLASS(STRING) = instance, class
	 *	WM_NAME(STRING) = title
	 */
	/* class      instance    title       tags mask     isfloating   monitor */
	{ "Gimp",     NULL,       NULL,       0,            True,        -1 },
	{ "Firefox",  NULL,       NULL,       1 << 8,       False,       -1 },
};

/* layout(s) */
static const float mfact      = 0.55; /* factor of master area size [0.05..0.95] */
static const int nmaster      = 1;    /* number of clients in master area */
static const Bool resizehints = True; /* True means respect size hints in tiled resizals */

static const Layout layouts[] = {
	/* symbol     arrange function */
	{ "[]=",      tile },    /* first entry is default */
	{ "><>",      NULL },    /* no layout function means floating behavior */
	{ "[M]",      monocle },
};

/* key definitions */
#define MODKEY Mod1Mask
#define TAGKEYS(KEY,TAG) \
	{ MODKEY,                       KEY,      view,           {.ui = 1 << TAG} }, \
	{ MODKEY|ControlMask,           KEY,      toggleview,     {.ui = 1 << TAG} }, \
	{ MODKEY|ShiftMask,             KEY,      tag,            {.ui = 1 << TAG} }, \
	{ MODKEY|ControlMask|ShiftMask, KEY,      toggletag,      {.ui = 1 << TAG} },

/* helper for spawning shell commands in the pre dwm-5.0 fashion */
#define SHCMD(cmd) { .v = (const char*[]){ "/bin/sh", "-c", cmd, NULL } }

/* commands */
static char dmenumon[2] = "0"; /* component of dmenucmd, manipulated in spawn() */
static const char *dmenucmd[] = { "dmenu_run", "-m", dmenumon, "-fn", dmenufont, "-nb", normbgcolor, "-nf", normfgcolor, "-sb", selbgcolor, "-sf", selfgcolor, NULL };
static const char *termcmd[]  = { "st", NULL };

static Key keys[] = {
	/* modifier                     key        function        argument */
	{ MODKEY,                       XK_p,      spawn,          {.v = dmenucmd } },
	{ MODKEY|ShiftMask,             XK_Return, spawn,          {.v = termcmd } },
	{ MODKEY,                       XK_b,      togglebar,      {0} },
	{ MODKEY,                       XK_j,      focusstack,     {.i = +1 } },
	{ MODKEY,                       XK_k,      focusstack,     {.i = -1 } },
	{ MODKEY,                       XK_i,      incnmaster,     {.i = +1 } },
	{ MODKEY,                       XK_d,      incnmaster,     {.i = -1 } },
	{ MODKEY,                       XK_h,      setmfact,       {.f = -0.05} },
	{ MODKEY,                       XK_l,      setmfact,       {.f = +0.05} },
	{ MODKEY,                       XK_Return, zoom,           {0} },
	{ MODKEY,                       XK_Tab,    view,           {0} },
	{ MODKEY|ShiftMask,             XK_c,      killclient,     {0} },
	{ MODKEY,                       XK_t,      setlayout,      {.v = &layouts[0]} },
	{ MODKEY,                       XK_f,      setlayout,      {.v = &layouts[1]} },
	{ MODKEY,                       XK_m,      setlayout,      {.v = &layouts[2]} },
	{ MODKEY,                       XK_space,  setlayout,      {0} },
	{ MODKEY|ShiftMask,             XK_space,  togglefloating, {0} },
	{ MODKEY,                       XK_0,      view,           {.ui = ~0 } },
	{ MODKEY|ShiftMask,             XK_0,      tag,            {.ui = ~0 } },
	{ MODKEY,                       XK_comma,  focusmon,       {.i = -1 } },
	{ MODKEY,                       XK_period, focusmon,       {.i = +1 } },
	{ MODKEY|ShiftMask,             XK_comma,  tagmon,         {.i = -1 } },
	{ MODKEY|ShiftMask,             XK_period, tagmon,         {.i = +1 } },
	TAGKEYS(                        XK_1,                      0)
	TAGKEYS(                        XK_2,                      1)
	TAGKEYS(                        XK_3,                      2)
	TAGKEYS(                        XK_4,                      3)
	TAGKEYS(                        XK_5,                      4)
	TAGKEYS(                        XK_6,                      5)
	TAGKEYS(                        XK_7,                      6)
	TAGKEYS(                        XK_8,                      7)
	TAGKEYS(                        XK_9,                      8)
	{ MODKEY|ShiftMask,             XK_q,      quit,           {0} },
};

/* button definitions */
/* click can be ClkLtSymbol, ClkStatusText, ClkWinTitle, ClkClientWin, or ClkRootWin */
static Button buttons[] = {
	/* click                event mask      button          function        argument */
	{ ClkLtSymbol,          0,              Button1,        setlayout,      {0} },
	{ ClkLtSymbol,          0,              Button3,        setlayout,      {.v = &layouts[2]} },
	{ ClkWinTitle,          0,              Button2,        zoom,           {0} },
	{ ClkStatusText,        0,              Button2,        spawn,          {.v = termcmd } },
	{ ClkClientWin,         MODKEY,         Button1,        movemouse,      {0} },
	{ ClkClientWin,         MODKEY,         Button2,        togglefloating, {0} },
	{ ClkClientWin,         MODKEY,         Button3,        resizemouse,    {0} },
	{ ClkTagBar,            0,              Button1,        view,           {0} },
	{ ClkTagBar,            0,              Button3,        toggleview,     {0} },
	{ ClkTagBar,            MODKEY,         Button1,        tag,            {0} },
	{ ClkTagBar,            MODKEY,         Button3,        toggletag,      {0} },
};
ss="Delimiter">(Recipe, r).name) << "first product of '$read-from-socket' should be a character, but got '" << to_string(inst.products.at(0)) << "'\n" << end(); 208 break; 209 } 210 if (nprod > 1 && !is_mu_boolean(inst.products.at(1))) { 211 raise << maybe(get(Recipe, r).name) << "second product of '$read-from-socket' should be a boolean (data received?), but got '" << to_string(inst.products.at(1)) << "'\n" << end(); 212 break; 213 } 214 if (nprod > 2 && !is_mu_boolean(inst.products.at(2))) { 215 raise << maybe(get(Recipe, r).name) << "third product of '$read-from-socket' should be a boolean (eof?), but got '" << to_string(inst.products.at(2)) << "'\n" << end(); 216 break; 217 } 218 if (nprod > 3 && !is_mu_number(inst.products.at(3))) { 219 raise << maybe(get(Recipe, r).name) << "fourth product of '$read-from-socket' should be a number (error code), but got '" << to_string(inst.products.at(3)) << "'\n" << end(); 220 break; 221 } 222 break; 223 } 224 :(before "End Primitive Recipe Implementations") 225 case _READ_FROM_SOCKET: { 226 products.resize(4); 227 long long int x = static_cast<long long int>(ingredients.at(0).at(0)); 228 socket_t* socket = reinterpret_cast<socket_t*>(x); 229 // 1. we'd like to simply read() from the socket 230 // however read() on a socket never returns EOF, so we wouldn't know when to stop 231 // 2. recv() can signal EOF, but it also signals "no data yet" in the beginning 232 // so use poll() in the beginning to wait for data before calling recv() 233 // 3. but poll() will block on EOF, so only use poll() on the very first 234 // $read-from-socket on a socket 235 // 236 // Also, there was an unresolved issue where attempts to read() a small 237 // number of bytes (less than 447 on Linux and Mac) would cause browsers to 238 // prematurely close the connection. See commit 3403. That seems to be gone 239 // after moving to recv()+poll(). It was never observed on OpenBSD. 240 if (!socket->polled) { 241 pollfd p; 242 bzero(&p, sizeof(p)); 243 p.fd = socket->fd; 244 p.events = POLLIN | POLLHUP; 245 int poll_result = poll(&p, /*num pollfds*/1, /*timeout*/100/*ms*/); 246 if (poll_result == 0) { 247 products.at(0).push_back(/*no data*/0); 248 products.at(1).push_back(/*found*/false); 249 products.at(2).push_back(/*eof*/false); 250 products.at(3).push_back(/*error*/0); 251 break; 252 } 253 else if (poll_result < 0) { 254 int error_code = errno; 255 raise << maybe(current_recipe_name()) << "error in $read-from-socket\n" << end(); 256 products.at(0).push_back(/*no data*/0); 257 products.at(1).push_back(/*found*/false); 258 products.at(2).push_back(/*eof*/false); 259 products.at(3).push_back(error_code); 260 break; 261 } 262 socket->polled = true; 263 } 264 char c = '\0'; 265 int error_code = 0; 266 int bytes_read = recv(socket->fd, &c, /*single byte*/1, MSG_DONTWAIT); 267 if (bytes_read < 0) error_code = errno; 268 //? if (error_code) { 269 //? ostringstream out; 270 //? out << "error in $read-from-socket " << socket->fd; 271 //? perror(out.str().c_str()); 272 //? } 273 products.at(0).push_back(c); 274 products.at(1).push_back(/*found*/true); 275 products.at(2).push_back(/*eof*/bytes_read <= 0); 276 products.at(3).push_back(error_code); 277 break; 278 } 279 280 :(before "End Primitive Recipe Declarations") 281 _WRITE_TO_SOCKET, 282 :(before "End Primitive Recipe Numbers") 283 put(Recipe_ordinal, "$write-to-socket", _WRITE_TO_SOCKET); 284 :(before "End Primitive Recipe Checks") 285 case _WRITE_TO_SOCKET: { 286 if (SIZE(inst.ingredients) != 2) { 287 raise << maybe(get(Recipe, r).name) << "'$write-to-socket' requires exactly two ingredient, but got '" << to_original_string(inst) << "'\n" << end(); 288 break; 289 } 290 break; 291 } 292 :(before "End Primitive Recipe Implementations") 293 case _WRITE_TO_SOCKET: { 294 long long int x = static_cast<long long int>(ingredients.at(0).at(0)); 295 socket_t* socket = reinterpret_cast<socket_t*>(x); 296 // write just one character at a time to the socket 297 long long int y = static_cast<long long int>(ingredients.at(1).at(0)); 298 char c = static_cast<char>(y); 299 if (write(socket->fd, &c, 1) != 1) { 300 raise << maybe(current_recipe_name()) << "failed to write to socket\n" << end(); 301 exit(0); 302 } 303 products.resize(1); 304 products.at(0).push_back(ingredients.at(0).at(0)); 305 break; 306 } 307 308 :(before "End Primitive Recipe Declarations") 309 _CLOSE_SOCKET, 310 :(before "End Primitive Recipe Numbers") 311 put(Recipe_ordinal, "$close-socket", _CLOSE_SOCKET); 312 :(before "End Primitive Recipe Checks") 313 case _CLOSE_SOCKET: { 314 if (SIZE(inst.ingredients) != 1) { 315 raise << maybe(get(Recipe, r).name) << "'$close-socket' requires exactly two ingredient, but got '" << to_original_string(inst) << "'\n" << end(); 316 break; 317 } 318 if (!is_mu_number(inst.ingredients.at(0))) { 319 raise << maybe(get(Recipe, r).name) << "first ingredient of '$close-socket' should be a number, but got '" << to_string(inst.ingredients.at(0)) << "'\n" << end(); 320 break; 321 } 322 if (SIZE(inst.products) != 1) { 323 raise << maybe(get(Recipe, r).name) << "'$close-socket' requires exactly one product, but got '" << to_original_string(inst) << "'\n" << end(); 324 break; 325 } 326 if (inst.products.at(0).name != inst.ingredients.at(0).name) { 327 raise << maybe(get(Recipe, r).name) << "product of '$close-socket' must be first ingredient '" << inst.ingredients.at(0).original_string << "', but got '" << inst.products.at(0).original_string << "'\n" << end(); 328 break; 329 } 330 break; 331 } 332 :(before "End Primitive Recipe Implementations") 333 case _CLOSE_SOCKET: { 334 long long int x = static_cast<long long int>(ingredients.at(0).at(0)); 335 socket_t* socket = reinterpret_cast<socket_t*>(x); 336 close(socket->fd); 337 delete socket; 338 products.resize(1); 339 products.at(0).push_back(0); // make sure we can't reuse the socket 340 break; 341 } 342 343 :(before "End Includes") 344 #include <netinet/in.h> 345 #include <netdb.h> 346 #include <poll.h> 347 #include <sys/socket.h> 348 #include <unistd.h>