mirror of https://github.com/adamdruppe/arsd.git
This commit is contained in:
commit
d3c545b0ce
20
README.md
20
README.md
|
@ -15,3 +15,23 @@ I have [a patreon](https://www.patreon.com/adam_d_ruppe) and my (almost) [weekly
|
|||
Thanks go to Nick Sabalausky, Trass3r, Stanislav Blinov, ketmar, maartenvd, and many others over the years for input and patches.
|
||||
|
||||
Several of the modules are also ports of other C code, see the comments in those files for their original authors.
|
||||
|
||||
# Conventions
|
||||
|
||||
Many http-based functions in the lib also support unix sockets as an alternative to tcp.
|
||||
|
||||
With cgi.d, use
|
||||
|
||||
--host unix:/path/here
|
||||
|
||||
or, on Linux:
|
||||
|
||||
--host abstract:/path/here
|
||||
|
||||
after compiling with `-version=embedded_httpd_thread` to serve http on the given socket. (`abstract:` does a unix socket in the Linux-specific abstract namespace).
|
||||
|
||||
With http2.d, use
|
||||
|
||||
Uri("http://whatever_host/path?args").viaUnixSocket("/path/here")
|
||||
|
||||
any time you are constructing a client. Note that `navigateTo` may lose the unix socket unless you specify it again.
|
||||
|
|
7
cgi.d
7
cgi.d
|
@ -1590,7 +1590,11 @@ class Cgi {
|
|||
sendAll(ir.source, d);
|
||||
}
|
||||
|
||||
this(ir, ir.source.remoteAddress().toString(), 80 /* FIXME */, 0, false, &rdo, null, closeConnection);
|
||||
auto ira = ir.source.remoteAddress();
|
||||
|
||||
// that check for UnixAddress is to work around a Phobos bug
|
||||
// see: https://github.com/dlang/phobos/pull/7383
|
||||
this(ir, cast(UnixAddress) ira ? "unix:" : ira.toString(), 80 /* FIXME */, 0, false, &rdo, null, closeConnection);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -4219,6 +4223,7 @@ class ListeningConnectionManager {
|
|||
version(linux) {
|
||||
listener = new Socket(AddressFamily.UNIX, SocketType.STREAM);
|
||||
string filename = "\0" ~ host["abstract:".length .. $];
|
||||
import std.stdio; stderr.writeln("Listening to abstract unix domain socket: ", host["abstract:".length .. $]);
|
||||
listener.bind(new UnixAddress(filename));
|
||||
tcp = false;
|
||||
} else {
|
||||
|
|
31
dub.json
31
dub.json
|
@ -116,7 +116,17 @@
|
|||
},
|
||||
"sourceFiles": ["png.d"]
|
||||
},
|
||||
|
||||
{
|
||||
"name": "bmp",
|
||||
"description": "BMP file format support",
|
||||
"importPaths": ["."],
|
||||
"targetType": "library",
|
||||
"dflags": ["-mv=arsd.bmp=bmp.d"],
|
||||
"dependencies": {
|
||||
"arsd-official:color_base":"*"
|
||||
},
|
||||
"sourceFiles": ["bmp.d"]
|
||||
},
|
||||
{
|
||||
"name": "htmltotext",
|
||||
"description": "HTML to plain text conversion",
|
||||
|
@ -300,6 +310,25 @@
|
|||
"sourceFiles": ["database.d"],
|
||||
"importPaths": ["."],
|
||||
"dflags": ["-mv=arsd.database=database.d"]
|
||||
},
|
||||
{
|
||||
"name": "libssh2_bindings",
|
||||
"description": "My bindings to libssh2",
|
||||
"targetType": "library",
|
||||
"sourceFiles": ["libssh2.d"],
|
||||
"importPaths": ["."],
|
||||
"libs": ["ssh2"],
|
||||
"dflags": ["-mv=arsd.libssh2=libssh2.d"]
|
||||
},
|
||||
{
|
||||
"name": "eventloop",
|
||||
"description": "My DEPRECATED event loop. Do NOT use in new programs, only offered here because I use it in a lot of old programs.",
|
||||
"targetType": "sourceLibrary",
|
||||
"versions": ["with_eventloop"],
|
||||
"sourceFiles": ["eventloop.d"],
|
||||
"importPaths": ["."],
|
||||
"dflags": ["-mv=arsd.eventloop=eventloop.d"]
|
||||
}
|
||||
|
||||
]
|
||||
}
|
||||
|
|
79
http2.d
79
http2.d
|
@ -36,6 +36,11 @@ version(arsd_http_winhttp_implementation) {
|
|||
pragma(lib, "winhttp")
|
||||
import core.sys.windows.winhttp;
|
||||
// FIXME: alter the dub package file too
|
||||
|
||||
// https://docs.microsoft.com/en-us/windows/win32/api/winhttp/nf-winhttp-winhttpreaddata
|
||||
// https://docs.microsoft.com/en-us/windows/win32/api/winhttp/nf-winhttp-winhttpsendrequest
|
||||
// https://docs.microsoft.com/en-us/windows/win32/api/winhttp/nf-winhttp-winhttpopenrequest
|
||||
// https://docs.microsoft.com/en-us/windows/win32/api/winhttp/nf-winhttp-winhttpconnect
|
||||
}
|
||||
|
||||
|
||||
|
@ -318,8 +323,25 @@ import std.conv;
|
|||
import std.range;
|
||||
|
||||
|
||||
private AddressFamily family(string unixSocketPath) {
|
||||
if(unixSocketPath.length)
|
||||
return AddressFamily.UNIX;
|
||||
else // FIXME: what about ipv6?
|
||||
return AddressFamily.INET;
|
||||
}
|
||||
|
||||
// Copy pasta from cgi.d, then stripped down
|
||||
version(Windows)
|
||||
private class UnixAddress : Address {
|
||||
this(string) {
|
||||
throw new Exception("No unix address support on this system in lib yet :(");
|
||||
}
|
||||
override sockaddr* name() { assert(0); }
|
||||
override const(sockaddr)* name() const { assert(0); }
|
||||
override int nameLen() const { assert(0); }
|
||||
}
|
||||
|
||||
|
||||
// Copy pasta from cgi.d, then stripped down. unix path thing added tho
|
||||
///
|
||||
struct Uri {
|
||||
alias toString this; // blargh idk a url really is a string, but should it be implicit?
|
||||
|
@ -339,6 +361,22 @@ struct Uri {
|
|||
reparse(uri);
|
||||
}
|
||||
|
||||
private string unixSocketPath = null;
|
||||
/// Indicates it should be accessed through a unix socket instead of regular tcp. Returns new version without modifying this object.
|
||||
Uri viaUnixSocket(string path) const {
|
||||
Uri copy = this;
|
||||
copy.unixSocketPath = path;
|
||||
return copy;
|
||||
}
|
||||
|
||||
/// Goes through a unix socket in the abstract namespace (linux only). Returns new version without modifying this object.
|
||||
version(linux)
|
||||
Uri viaAbstractSocket(string path) const {
|
||||
Uri copy = this;
|
||||
copy.unixSocketPath = "\0" ~ path;
|
||||
return copy;
|
||||
}
|
||||
|
||||
private void reparse(string uri) {
|
||||
// from RFC 3986
|
||||
// the ctRegex triples the compile time and makes ugly errors for no real benefit
|
||||
|
@ -545,6 +583,11 @@ struct Uri {
|
|||
|
||||
n.removeDots();
|
||||
|
||||
// if still basically talking to the same thing, we should inherit the unix path
|
||||
// too since basically the unix path is saying for this service, always use this override.
|
||||
if(n.host == baseUrl.host && n.scheme == baseUrl.scheme && n.port == baseUrl.port)
|
||||
n.unixSocketPath = baseUrl.unixSocketPath;
|
||||
|
||||
return n;
|
||||
}
|
||||
|
||||
|
@ -630,6 +673,7 @@ class HttpRequest {
|
|||
auto parts = where;
|
||||
finalUrl = where.toString();
|
||||
requestParameters.method = method;
|
||||
requestParameters.unixSocketPath = where.unixSocketPath;
|
||||
requestParameters.host = parts.host;
|
||||
requestParameters.port = cast(ushort) parts.port;
|
||||
requestParameters.ssl = parts.scheme == "https";
|
||||
|
@ -812,18 +856,26 @@ class HttpRequest {
|
|||
}
|
||||
}
|
||||
|
||||
Socket getOpenSocketOnHost(string host, ushort port, bool ssl) {
|
||||
Socket getOpenSocketOnHost(string host, ushort port, bool ssl, string unixSocketPath) {
|
||||
|
||||
Socket openNewConnection() {
|
||||
Socket socket;
|
||||
if(ssl) {
|
||||
version(with_openssl)
|
||||
socket = new SslClientSocket(AddressFamily.INET, SocketType.STREAM);
|
||||
socket = new SslClientSocket(family(unixSocketPath), SocketType.STREAM);
|
||||
else
|
||||
throw new Exception("SSL not compiled in");
|
||||
} else
|
||||
socket = new Socket(AddressFamily.INET, SocketType.STREAM);
|
||||
socket = new Socket(family(unixSocketPath), SocketType.STREAM);
|
||||
|
||||
if(unixSocketPath) {
|
||||
import std.stdio; writeln(cast(ubyte[]) unixSocketPath);
|
||||
socket.connect(new UnixAddress(unixSocketPath));
|
||||
} else {
|
||||
// FIXME: i should prolly do ipv6 if available too.
|
||||
socket.connect(new InternetAddress(host, port));
|
||||
}
|
||||
|
||||
socket.connect(new InternetAddress(host, port));
|
||||
debug(arsd_http2) writeln("opening to ", host, ":", port, " ", cast(void*) socket);
|
||||
assert(socket.handle() !is socket_t.init);
|
||||
return socket;
|
||||
|
@ -903,7 +955,7 @@ class HttpRequest {
|
|||
continue;
|
||||
}
|
||||
|
||||
auto socket = getOpenSocketOnHost(pc.requestParameters.host, pc.requestParameters.port, pc.requestParameters.ssl);
|
||||
auto socket = getOpenSocketOnHost(pc.requestParameters.host, pc.requestParameters.port, pc.requestParameters.ssl, pc.requestParameters.unixSocketPath);
|
||||
|
||||
if(socket !is null) {
|
||||
activeRequestOnSocket[socket] = pc;
|
||||
|
@ -1384,6 +1436,8 @@ struct HttpRequestParameters {
|
|||
|
||||
string contentType; ///
|
||||
ubyte[] bodyData; ///
|
||||
|
||||
string unixSocketPath;
|
||||
}
|
||||
|
||||
interface IHttpClient {
|
||||
|
@ -2209,11 +2263,11 @@ wss://echo.websocket.org
|
|||
|
||||
if(ssl) {
|
||||
version(with_openssl)
|
||||
socket = new SslClientSocket(AddressFamily.INET, SocketType.STREAM);
|
||||
socket = new SslClientSocket(family(uri.unixSocketPath), SocketType.STREAM);
|
||||
else
|
||||
throw new Exception("SSL not compiled in");
|
||||
} else
|
||||
socket = new Socket(AddressFamily.INET, SocketType.STREAM);
|
||||
socket = new Socket(family(uri.unixSocketPath), SocketType.STREAM);
|
||||
|
||||
}
|
||||
|
||||
|
@ -2222,7 +2276,10 @@ wss://echo.websocket.org
|
|||
+/
|
||||
/// Group: foundational
|
||||
void connect() {
|
||||
socket.connect(new InternetAddress(host, port)); // FIXME: ipv6 support...
|
||||
if(uri.unixSocketPath)
|
||||
socket.connect(new UnixAddress(uri.unixSocketPath));
|
||||
else
|
||||
socket.connect(new InternetAddress(host, port)); // FIXME: ipv6 support...
|
||||
// FIXME: websocket handshake could and really should be async too.
|
||||
|
||||
auto uri = this.uri.path.length ? this.uri.path : "/";
|
||||
|
@ -2924,8 +2981,8 @@ private {
|
|||
return needsMoreData();
|
||||
}
|
||||
|
||||
msg.data = d[0 .. msg.realLength];
|
||||
d = d[msg.realLength .. $];
|
||||
msg.data = d[0 .. cast(size_t) msg.realLength];
|
||||
d = d[cast(size_t) msg.realLength .. $];
|
||||
|
||||
if(msg.masked) {
|
||||
// let's just unmask it now
|
||||
|
|
|
@ -1991,7 +1991,7 @@ class SimpleWindow : CapableOfHandlingNativeEvent, CapableOfBeingDrawnUpon {
|
|||
XChangeProperty(
|
||||
display,
|
||||
impl.window,
|
||||
GetAtom!"_NET_WM_ICON"(display),
|
||||
GetAtom!("_NET_WM_ICON", true)(display),
|
||||
GetAtom!"CARDINAL"(display),
|
||||
32 /* bits */,
|
||||
0 /*PropModeReplace*/,
|
||||
|
@ -4217,7 +4217,7 @@ class Timer {
|
|||
}
|
||||
|
||||
version(Windows)
|
||||
/// Lets you add HANDLEs to the event loop. Not meant to be used for async I/O per se, but for other handles (it can only handle a few handles at a time.)
|
||||
/// Lets you add HANDLEs to the event loop. Not meant to be used for async I/O per se, but for other handles (it can only handle a few handles at a time.) Only works on certain types of handles! see: https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-msgwaitformultipleobjectsex
|
||||
class WindowsHandleReader {
|
||||
///
|
||||
this(void delegate() onReady, HANDLE handle) {
|
||||
|
@ -6618,10 +6618,9 @@ struct ScreenPainter {
|
|||
impl.drawArc(upperLeft.x, upperLeft.y, width, height, start, finish);
|
||||
}
|
||||
|
||||
//this function draws a circle using the drawArc() function above, it requires you to pass the point it
|
||||
//will be drawn at as a Point struct and the radius as an int
|
||||
//this function draws a circle with the drawEllipse() function above, it requires the upper left point and the radius
|
||||
void drawCircle(Point upperLeft, int radius) {
|
||||
this.drawArc(upperLeft, radius, radius, 0, 0);
|
||||
drawEllipse(upperLeft, Point(upperLeft.x + radius, upperLeft.y + radius));
|
||||
}
|
||||
|
||||
/// .
|
||||
|
@ -9382,7 +9381,15 @@ version(X11) {
|
|||
if(!xshmQueryCompleted) {
|
||||
int i1, i2, i3;
|
||||
xshmQueryCompleted = true;
|
||||
_xshmAvailable = XQueryExtension(XDisplayConnection.get(), "MIT-SHM", &i1, &i2, &i3) != 0;
|
||||
|
||||
auto str = XDisplayConnection.get().display_name;
|
||||
// only if we are actually on the same machine does this
|
||||
// have any hope, and the query extension only asks if
|
||||
// the server can in theory, not in practice.
|
||||
if(str is null || str[0] != ':')
|
||||
_xshmAvailable = false;
|
||||
else
|
||||
_xshmAvailable = XQueryExtension(XDisplayConnection.get(), "MIT-SHM", &i1, &i2, &i3) != 0;
|
||||
}
|
||||
return _xshmAvailable;
|
||||
}
|
||||
|
@ -14309,51 +14316,55 @@ void demandAttention(Window window, bool needs = true) {
|
|||
|
||||
/// X-specific
|
||||
TrueColorImage getWindowNetWmIcon(Window window) {
|
||||
auto display = XDisplayConnection.get;
|
||||
try {
|
||||
auto display = XDisplayConnection.get;
|
||||
|
||||
auto data = getX11PropertyData (window, GetAtom!"_NET_WM_ICON"(display), XA_CARDINAL);
|
||||
auto data = getX11PropertyData (window, GetAtom!"_NET_WM_ICON"(display), XA_CARDINAL);
|
||||
|
||||
if (data.length > arch_ulong.sizeof * 2) {
|
||||
auto meta = cast(arch_ulong[]) (data[0 .. arch_ulong.sizeof * 2]);
|
||||
// these are an array of rgba images that we have to convert into pixmaps ourself
|
||||
if (data.length > arch_ulong.sizeof * 2) {
|
||||
auto meta = cast(arch_ulong[]) (data[0 .. arch_ulong.sizeof * 2]);
|
||||
// these are an array of rgba images that we have to convert into pixmaps ourself
|
||||
|
||||
int width = cast(int) meta[0];
|
||||
int height = cast(int) meta[1];
|
||||
int width = cast(int) meta[0];
|
||||
int height = cast(int) meta[1];
|
||||
|
||||
auto bytes = cast(ubyte[]) (data[arch_ulong.sizeof * 2 .. $]);
|
||||
auto bytes = cast(ubyte[]) (data[arch_ulong.sizeof * 2 .. $]);
|
||||
|
||||
static if(arch_ulong.sizeof == 4) {
|
||||
bytes = bytes[0 .. width * height * 4];
|
||||
alias imageData = bytes;
|
||||
} else static if(arch_ulong.sizeof == 8) {
|
||||
bytes = bytes[0 .. width * height * 8];
|
||||
auto imageData = new ubyte[](4 * width * height);
|
||||
} else static assert(0);
|
||||
static if(arch_ulong.sizeof == 4) {
|
||||
bytes = bytes[0 .. width * height * 4];
|
||||
alias imageData = bytes;
|
||||
} else static if(arch_ulong.sizeof == 8) {
|
||||
bytes = bytes[0 .. width * height * 8];
|
||||
auto imageData = new ubyte[](4 * width * height);
|
||||
} else static assert(0);
|
||||
|
||||
|
||||
|
||||
// this returns ARGB. Remember it is little-endian so
|
||||
// we have BGRA
|
||||
// our thing uses RGBA, which in little endian, is ABGR
|
||||
for(int idx = 0, idx2 = 0; idx < bytes.length; idx += arch_ulong.sizeof, idx2 += 4) {
|
||||
auto r = bytes[idx + 2];
|
||||
auto g = bytes[idx + 1];
|
||||
auto b = bytes[idx + 0];
|
||||
auto a = bytes[idx + 3];
|
||||
// this returns ARGB. Remember it is little-endian so
|
||||
// we have BGRA
|
||||
// our thing uses RGBA, which in little endian, is ABGR
|
||||
for(int idx = 0, idx2 = 0; idx < bytes.length; idx += arch_ulong.sizeof, idx2 += 4) {
|
||||
auto r = bytes[idx + 2];
|
||||
auto g = bytes[idx + 1];
|
||||
auto b = bytes[idx + 0];
|
||||
auto a = bytes[idx + 3];
|
||||
|
||||
imageData[idx2 + 0] = r;
|
||||
imageData[idx2 + 1] = g;
|
||||
imageData[idx2 + 2] = b;
|
||||
imageData[idx2 + 3] = a;
|
||||
imageData[idx2 + 0] = r;
|
||||
imageData[idx2 + 1] = g;
|
||||
imageData[idx2 + 2] = b;
|
||||
imageData[idx2 + 3] = a;
|
||||
}
|
||||
|
||||
return new TrueColorImage(width, height, imageData);
|
||||
}
|
||||
|
||||
return new TrueColorImage(width, height, imageData);
|
||||
return null;
|
||||
} catch(Exception e) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
} /* UsingSimpledisplayX11 */
|
||||
|
||||
|
||||
void loadBinNameToWindowClassName () {
|
||||
|
|
162
terminal.d
162
terminal.d
|
@ -63,7 +63,7 @@
|
|||
module arsd.terminal;
|
||||
|
||||
// FIXME: needs to support VT output on Windows too in certain situations
|
||||
// FIXME: paste on Windows and alt+NNNN codes in getline function
|
||||
// detect VT on windows by trying to set the flag. if this succeeds, ask it for caps. if this replies with my code we good to do extended output.
|
||||
|
||||
/++
|
||||
$(H3 Get Line)
|
||||
|
@ -190,7 +190,6 @@ version(Windows)
|
|||
|
||||
version(Win32Console) {
|
||||
import core.sys.windows.windows;
|
||||
import std.string : toStringz;
|
||||
private {
|
||||
enum RED_BIT = 4;
|
||||
enum GREEN_BIT = 2;
|
||||
|
@ -294,7 +293,7 @@ vt|vt100|DEC vt100 compatible:\
|
|||
|
||||
|
||||
# Entry for an xterm. Insert mode has been disabled.
|
||||
vs|xterm|screen|screen.xterm|screen.xterm-256color|xterm-color|xterm-256color|vs100|xterm terminal emulator (X Window System):\
|
||||
vs|xterm|tmux|tmux-256color|screen|screen.xterm|screen.xterm-256color|xterm-color|xterm-256color|vs100|xterm terminal emulator (X Window System):\
|
||||
:am:bs:mi@:km:co#80:li#55:\
|
||||
:im@:ei@:\
|
||||
:cl=\E[H\E[J:\
|
||||
|
@ -560,6 +559,8 @@ struct Terminal {
|
|||
// work the way they're advertised. I just have to best-guess hack and hope it
|
||||
// doesn't break anything else. (If you know a better way, let me know!)
|
||||
bool isMacTerminal() {
|
||||
// it gives 1,2 in getTerminalCapabilities...
|
||||
// FIXME
|
||||
import std.process;
|
||||
import std.string;
|
||||
auto term = environment.get("TERM");
|
||||
|
@ -853,13 +854,16 @@ struct Terminal {
|
|||
return;
|
||||
}
|
||||
|
||||
auto info = getTerminalCapabilities(fdIn, fdOut);
|
||||
//writeln(info);
|
||||
|
||||
if(type == ConsoleOutputType.cellular) {
|
||||
doTermcap("ti");
|
||||
clear();
|
||||
moveTo(0, 0, ForceOption.alwaysSend); // we need to know where the cursor is for some features to work, and moving it is easier than querying it
|
||||
}
|
||||
|
||||
if(terminalInFamily("xterm", "rxvt", "screen")) {
|
||||
if(terminalInFamily("xterm", "rxvt", "screen", "tmux")) {
|
||||
writeStringRaw("\033[22;0t"); // save window title on a stack (support seems spotty, but it doesn't hurt to have it)
|
||||
}
|
||||
}
|
||||
|
@ -941,7 +945,7 @@ http://msdn.microsoft.com/en-us/library/windows/desktop/ms683193%28v=vs.85%29.as
|
|||
if(type == ConsoleOutputType.cellular) {
|
||||
doTermcap("te");
|
||||
}
|
||||
if(terminalInFamily("xterm", "rxvt", "screen")) {
|
||||
if(terminalInFamily("xterm", "rxvt", "screen", "tmux")) {
|
||||
writeStringRaw("\033[23;0t"); // restore window title from the stack
|
||||
}
|
||||
cursor = TerminalCursor.DEFAULT;
|
||||
|
@ -1267,11 +1271,19 @@ http://msdn.microsoft.com/en-us/library/windows/desktop/ms683193%28v=vs.85%29.as
|
|||
/// Changes the terminal's title
|
||||
void setTitle(string t) {
|
||||
version(Win32Console) {
|
||||
// FIXME: use the W version
|
||||
SetConsoleTitleA(toStringz(t));
|
||||
wchar[256] buffer;
|
||||
size_t bufferLength;
|
||||
foreach(wchar ch; t)
|
||||
if(bufferLength < buffer.length)
|
||||
buffer[bufferLength++] = ch;
|
||||
if(bufferLength < buffer.length)
|
||||
buffer[bufferLength++] = 0;
|
||||
else
|
||||
buffer[$-1] = 0;
|
||||
SetConsoleTitleW(buffer.ptr);
|
||||
} else {
|
||||
import std.string;
|
||||
if(terminalInFamily("xterm", "rxvt", "screen"))
|
||||
if(terminalInFamily("xterm", "rxvt", "screen", "tmux"))
|
||||
writeStringRaw(format("\033]0;%s\007", t));
|
||||
}
|
||||
}
|
||||
|
@ -1619,6 +1631,8 @@ struct RealTimeConsoleInput {
|
|||
if(!(flags & ConsoleInputFlags.echo))
|
||||
f |= ECHO;
|
||||
|
||||
// \033Z or \033[c
|
||||
|
||||
n.c_lflag &= ~f;
|
||||
tcsetattr(fdIn, TCSANOW, &n);
|
||||
}
|
||||
|
@ -1672,20 +1686,20 @@ struct RealTimeConsoleInput {
|
|||
// this is vt200 mouse with full motion tracking, supported by xterm
|
||||
terminal.writeStringRaw("\033[?1003h");
|
||||
destructor ~= { terminal.writeStringRaw("\033[?1003l"); };
|
||||
} else if(terminal.terminalInFamily("rxvt", "screen") || environment.get("MOUSE_HACK") == "1002") {
|
||||
} else if(terminal.terminalInFamily("rxvt", "screen", "tmux") || environment.get("MOUSE_HACK") == "1002") {
|
||||
terminal.writeStringRaw("\033[?1002h"); // this is vt200 mouse with press/release and motion notification iff buttons are pressed
|
||||
destructor ~= { terminal.writeStringRaw("\033[?1002l"); };
|
||||
}
|
||||
}
|
||||
if(flags & ConsoleInputFlags.paste) {
|
||||
if(terminal.terminalInFamily("xterm", "rxvt", "screen")) {
|
||||
if(terminal.terminalInFamily("xterm", "rxvt", "screen", "tmux")) {
|
||||
terminal.writeStringRaw("\033[?2004h"); // bracketed paste mode
|
||||
destructor ~= { terminal.writeStringRaw("\033[?2004l"); };
|
||||
}
|
||||
}
|
||||
|
||||
// try to ensure the terminal is in UTF-8 mode
|
||||
if(terminal.terminalInFamily("xterm", "screen", "linux") && !terminal.isMacTerminal()) {
|
||||
if(terminal.terminalInFamily("xterm", "screen", "linux", "tmux") && !terminal.isMacTerminal()) {
|
||||
terminal.writeStringRaw("\033%G");
|
||||
}
|
||||
|
||||
|
@ -2416,7 +2430,7 @@ struct RealTimeConsoleInput {
|
|||
default:
|
||||
// screen doesn't actually do the modifiers, but
|
||||
// it uses the same format so this branch still works fine.
|
||||
if(terminal.terminalInFamily("xterm", "screen")) {
|
||||
if(terminal.terminalInFamily("xterm", "screen", "tmux")) {
|
||||
import std.conv, std.string;
|
||||
auto terminator = sequence[$ - 1];
|
||||
auto parts = sequence[2 .. $ - 1].split(";");
|
||||
|
@ -2490,6 +2504,7 @@ struct RealTimeConsoleInput {
|
|||
|
||||
// starting at 70 i do some magic for like shift+enter etc.
|
||||
// this only happens on my own terminal emulator.
|
||||
case "70": return keyPressAndRelease(NonCharacterKeyEvent.Key.ScrollLock, modifierState);
|
||||
case "78": return keyPressAndRelease2('\b', modifierState);
|
||||
case "79": return keyPressAndRelease2('\t', modifierState);
|
||||
case "83": return keyPressAndRelease2('\n', modifierState);
|
||||
|
@ -2610,6 +2625,7 @@ struct KeyboardEvent {
|
|||
End = 0x23 + 0xF0000, /// .
|
||||
PageUp = 0x21 + 0xF0000, /// .
|
||||
PageDown = 0x22 + 0xF0000, /// .
|
||||
ScrollLock = 0x91 + 0xF0000, /// unlikely to work outside my custom terminal emulator
|
||||
}
|
||||
|
||||
|
||||
|
@ -2665,6 +2681,7 @@ struct NonCharacterKeyEvent {
|
|||
End = 0x23, /// .
|
||||
PageUp = 0x21, /// .
|
||||
PageDown = 0x22, /// .
|
||||
ScrollLock = 0x91, /// unlikely to work outside my terminal emulator
|
||||
}
|
||||
Key key; /// .
|
||||
|
||||
|
@ -3011,6 +3028,127 @@ void main() {
|
|||
}
|
||||
}
|
||||
|
||||
version(Posix)
|
||||
private int[] getTerminalCapabilities(int fdIn, int fdOut) {
|
||||
if(fdIn == -1 || fdOut == -1)
|
||||
return null;
|
||||
|
||||
import std.conv;
|
||||
import core.stdc.errno;
|
||||
import core.sys.posix.unistd;
|
||||
|
||||
ubyte[128] hack2;
|
||||
termios old;
|
||||
ubyte[128] hack;
|
||||
tcgetattr(fdIn, &old);
|
||||
auto n = old;
|
||||
n.c_lflag &= ~(ICANON | ECHO);
|
||||
tcsetattr(fdIn, TCSANOW, &n);
|
||||
scope(exit)
|
||||
tcsetattr(fdIn, TCSANOW, &old);
|
||||
|
||||
// drain the buffer? meh
|
||||
|
||||
string cmd = "\033[c";
|
||||
auto err = write(fdOut, cmd.ptr, cmd.length);
|
||||
if(err != cmd.length) {
|
||||
throw new Exception("couldn't ask terminal for ID");
|
||||
}
|
||||
|
||||
// reading directly to bypass any buffering
|
||||
int retries = 16;
|
||||
int len;
|
||||
ubyte[96] buffer;
|
||||
try_again:
|
||||
|
||||
|
||||
timeval tv;
|
||||
tv.tv_sec = 0;
|
||||
tv.tv_usec = 10 * 1000;
|
||||
|
||||
fd_set fs;
|
||||
FD_ZERO(&fs);
|
||||
|
||||
FD_SET(fdIn, &fs);
|
||||
if(select(fdIn + 1, &fs, null, null, &tv) == -1) {
|
||||
goto try_again;
|
||||
}
|
||||
|
||||
if(FD_ISSET(fdIn, &fs)) {
|
||||
auto len2 = read(fdIn, &buffer[len], buffer.length - len);
|
||||
if(len2 <= 0) {
|
||||
retries--;
|
||||
if(retries > 0)
|
||||
goto try_again;
|
||||
throw new Exception("can't get terminal id");
|
||||
} else {
|
||||
len += len2;
|
||||
}
|
||||
} else {
|
||||
// no data... assume terminal doesn't support giving an answer
|
||||
return null;
|
||||
}
|
||||
|
||||
ubyte[] answer;
|
||||
bool hasAnswer(ubyte[] data) {
|
||||
if(data.length < 4)
|
||||
return false;
|
||||
answer = null;
|
||||
size_t start;
|
||||
int position = 0;
|
||||
foreach(idx, ch; data) {
|
||||
switch(position) {
|
||||
case 0:
|
||||
if(ch == '\033') {
|
||||
start = idx;
|
||||
position++;
|
||||
}
|
||||
break;
|
||||
case 1:
|
||||
if(ch == '[')
|
||||
position++;
|
||||
else
|
||||
position = 0;
|
||||
break;
|
||||
case 2:
|
||||
if(ch == '?')
|
||||
position++;
|
||||
else
|
||||
position = 0;
|
||||
break;
|
||||
case 3:
|
||||
// body
|
||||
if(ch == 'c') {
|
||||
answer = data[start .. idx + 1];
|
||||
return true;
|
||||
} else if(ch == ';' || (ch >= '0' && ch <= '9')) {
|
||||
// good, keep going
|
||||
} else {
|
||||
// invalid, drop it
|
||||
position = 0;
|
||||
}
|
||||
break;
|
||||
default: assert(0);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
auto got = buffer[0 .. len];
|
||||
if(!hasAnswer(got)) {
|
||||
goto try_again;
|
||||
}
|
||||
auto gots = cast(char[]) answer[3 .. $-1];
|
||||
|
||||
import std.string;
|
||||
|
||||
auto pieces = split(gots, ";");
|
||||
int[] ret;
|
||||
foreach(p; pieces)
|
||||
ret ~= p.to!int;
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
FIXME: support lines that wrap
|
||||
FIXME: better controls maybe
|
||||
|
|
Loading…
Reference in New Issue