A thread reads them from a file descriptor and writes them to a channel. You can look at the code which gets linked into the binary:
/sys/src/libdraw/mouse.c:61
Essentially, the loop in _ioproc is: while(read(fd, event)){
parse(event);
send(mousechan, event);
}
And yes, once you have an open FD, read() and write() act similar to how they would elsewhere. The difference is that there are no OTHER cases. All the code works that way, not just draw events.And getting the FD is also done via 9p, which means that it naturally respects namespaces and can be interposed. For example, sshnet just mounts itself over /net, and replaces all network calls transparently for all programs in its namespace. Because there's no special case API for opening sockets: it's all 9p.