This commit is contained in:
Adam D. Ruppe 2020-01-31 18:31:15 -05:00
commit d3c545b0ce
6 changed files with 322 additions and 62 deletions

View File

@ -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. 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. 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
View File

@ -1590,7 +1590,11 @@ class Cgi {
sendAll(ir.source, d); 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) { version(linux) {
listener = new Socket(AddressFamily.UNIX, SocketType.STREAM); listener = new Socket(AddressFamily.UNIX, SocketType.STREAM);
string filename = "\0" ~ host["abstract:".length .. $]; 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)); listener.bind(new UnixAddress(filename));
tcp = false; tcp = false;
} else { } else {

View File

@ -116,7 +116,17 @@
}, },
"sourceFiles": ["png.d"] "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", "name": "htmltotext",
"description": "HTML to plain text conversion", "description": "HTML to plain text conversion",
@ -300,6 +310,25 @@
"sourceFiles": ["database.d"], "sourceFiles": ["database.d"],
"importPaths": ["."], "importPaths": ["."],
"dflags": ["-mv=arsd.database=database.d"] "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
View File

@ -36,6 +36,11 @@ version(arsd_http_winhttp_implementation) {
pragma(lib, "winhttp") pragma(lib, "winhttp")
import core.sys.windows.winhttp; import core.sys.windows.winhttp;
// FIXME: alter the dub package file too // 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; 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 { struct Uri {
alias toString this; // blargh idk a url really is a string, but should it be implicit? alias toString this; // blargh idk a url really is a string, but should it be implicit?
@ -339,6 +361,22 @@ struct Uri {
reparse(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) { private void reparse(string uri) {
// from RFC 3986 // from RFC 3986
// the ctRegex triples the compile time and makes ugly errors for no real benefit // the ctRegex triples the compile time and makes ugly errors for no real benefit
@ -545,6 +583,11 @@ struct Uri {
n.removeDots(); 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; return n;
} }
@ -630,6 +673,7 @@ class HttpRequest {
auto parts = where; auto parts = where;
finalUrl = where.toString(); finalUrl = where.toString();
requestParameters.method = method; requestParameters.method = method;
requestParameters.unixSocketPath = where.unixSocketPath;
requestParameters.host = parts.host; requestParameters.host = parts.host;
requestParameters.port = cast(ushort) parts.port; requestParameters.port = cast(ushort) parts.port;
requestParameters.ssl = parts.scheme == "https"; 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 openNewConnection() {
Socket socket; Socket socket;
if(ssl) { if(ssl) {
version(with_openssl) version(with_openssl)
socket = new SslClientSocket(AddressFamily.INET, SocketType.STREAM); socket = new SslClientSocket(family(unixSocketPath), SocketType.STREAM);
else else
throw new Exception("SSL not compiled in"); throw new Exception("SSL not compiled in");
} else } 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); debug(arsd_http2) writeln("opening to ", host, ":", port, " ", cast(void*) socket);
assert(socket.handle() !is socket_t.init); assert(socket.handle() !is socket_t.init);
return socket; return socket;
@ -903,7 +955,7 @@ class HttpRequest {
continue; 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) { if(socket !is null) {
activeRequestOnSocket[socket] = pc; activeRequestOnSocket[socket] = pc;
@ -1384,6 +1436,8 @@ struct HttpRequestParameters {
string contentType; /// string contentType; ///
ubyte[] bodyData; /// ubyte[] bodyData; ///
string unixSocketPath;
} }
interface IHttpClient { interface IHttpClient {
@ -2209,11 +2263,11 @@ wss://echo.websocket.org
if(ssl) { if(ssl) {
version(with_openssl) version(with_openssl)
socket = new SslClientSocket(AddressFamily.INET, SocketType.STREAM); socket = new SslClientSocket(family(uri.unixSocketPath), SocketType.STREAM);
else else
throw new Exception("SSL not compiled in"); throw new Exception("SSL not compiled in");
} else } 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 /// Group: foundational
void connect() { 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. // FIXME: websocket handshake could and really should be async too.
auto uri = this.uri.path.length ? this.uri.path : "/"; auto uri = this.uri.path.length ? this.uri.path : "/";
@ -2924,8 +2981,8 @@ private {
return needsMoreData(); return needsMoreData();
} }
msg.data = d[0 .. msg.realLength]; msg.data = d[0 .. cast(size_t) msg.realLength];
d = d[msg.realLength .. $]; d = d[cast(size_t) msg.realLength .. $];
if(msg.masked) { if(msg.masked) {
// let's just unmask it now // let's just unmask it now

View File

@ -1991,7 +1991,7 @@ class SimpleWindow : CapableOfHandlingNativeEvent, CapableOfBeingDrawnUpon {
XChangeProperty( XChangeProperty(
display, display,
impl.window, impl.window,
GetAtom!"_NET_WM_ICON"(display), GetAtom!("_NET_WM_ICON", true)(display),
GetAtom!"CARDINAL"(display), GetAtom!"CARDINAL"(display),
32 /* bits */, 32 /* bits */,
0 /*PropModeReplace*/, 0 /*PropModeReplace*/,
@ -4217,7 +4217,7 @@ class Timer {
} }
version(Windows) 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 { class WindowsHandleReader {
/// ///
this(void delegate() onReady, HANDLE handle) { this(void delegate() onReady, HANDLE handle) {
@ -6618,10 +6618,9 @@ struct ScreenPainter {
impl.drawArc(upperLeft.x, upperLeft.y, width, height, start, finish); 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 //this function draws a circle with the drawEllipse() function above, it requires the upper left point and the radius
//will be drawn at as a Point struct and the radius as an int
void drawCircle(Point upperLeft, int 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) { if(!xshmQueryCompleted) {
int i1, i2, i3; int i1, i2, i3;
xshmQueryCompleted = true; 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; return _xshmAvailable;
} }
@ -14309,51 +14316,55 @@ void demandAttention(Window window, bool needs = true) {
/// X-specific /// X-specific
TrueColorImage getWindowNetWmIcon(Window window) { 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) { if (data.length > arch_ulong.sizeof * 2) {
auto meta = cast(arch_ulong[]) (data[0 .. 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 // these are an array of rgba images that we have to convert into pixmaps ourself
int width = cast(int) meta[0]; int width = cast(int) meta[0];
int height = cast(int) meta[1]; 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) { static if(arch_ulong.sizeof == 4) {
bytes = bytes[0 .. width * height * 4]; bytes = bytes[0 .. width * height * 4];
alias imageData = bytes; alias imageData = bytes;
} else static if(arch_ulong.sizeof == 8) { } else static if(arch_ulong.sizeof == 8) {
bytes = bytes[0 .. width * height * 8]; bytes = bytes[0 .. width * height * 8];
auto imageData = new ubyte[](4 * width * height); auto imageData = new ubyte[](4 * width * height);
} else static assert(0); } else static assert(0);
// this returns ARGB. Remember it is little-endian so // this returns ARGB. Remember it is little-endian so
// we have BGRA // we have BGRA
// our thing uses RGBA, which in little endian, is ABGR // 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) { for(int idx = 0, idx2 = 0; idx < bytes.length; idx += arch_ulong.sizeof, idx2 += 4) {
auto r = bytes[idx + 2]; auto r = bytes[idx + 2];
auto g = bytes[idx + 1]; auto g = bytes[idx + 1];
auto b = bytes[idx + 0]; auto b = bytes[idx + 0];
auto a = bytes[idx + 3]; auto a = bytes[idx + 3];
imageData[idx2 + 0] = r; imageData[idx2 + 0] = r;
imageData[idx2 + 1] = g; imageData[idx2 + 1] = g;
imageData[idx2 + 2] = b; imageData[idx2 + 2] = b;
imageData[idx2 + 3] = a; 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 () { void loadBinNameToWindowClassName () {

View File

@ -63,7 +63,7 @@
module arsd.terminal; module arsd.terminal;
// FIXME: needs to support VT output on Windows too in certain situations // 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) $(H3 Get Line)
@ -190,7 +190,6 @@ version(Windows)
version(Win32Console) { version(Win32Console) {
import core.sys.windows.windows; import core.sys.windows.windows;
import std.string : toStringz;
private { private {
enum RED_BIT = 4; enum RED_BIT = 4;
enum GREEN_BIT = 2; enum GREEN_BIT = 2;
@ -294,7 +293,7 @@ vt|vt100|DEC vt100 compatible:\
# Entry for an xterm. Insert mode has been disabled. # 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:\ :am:bs:mi@:km:co#80:li#55:\
:im@:ei@:\ :im@:ei@:\
:cl=\E[H\E[J:\ :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 // 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!) // doesn't break anything else. (If you know a better way, let me know!)
bool isMacTerminal() { bool isMacTerminal() {
// it gives 1,2 in getTerminalCapabilities...
// FIXME
import std.process; import std.process;
import std.string; import std.string;
auto term = environment.get("TERM"); auto term = environment.get("TERM");
@ -853,13 +854,16 @@ struct Terminal {
return; return;
} }
auto info = getTerminalCapabilities(fdIn, fdOut);
//writeln(info);
if(type == ConsoleOutputType.cellular) { if(type == ConsoleOutputType.cellular) {
doTermcap("ti"); doTermcap("ti");
clear(); 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 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) 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) { if(type == ConsoleOutputType.cellular) {
doTermcap("te"); doTermcap("te");
} }
if(terminalInFamily("xterm", "rxvt", "screen")) { if(terminalInFamily("xterm", "rxvt", "screen", "tmux")) {
writeStringRaw("\033[23;0t"); // restore window title from the stack writeStringRaw("\033[23;0t"); // restore window title from the stack
} }
cursor = TerminalCursor.DEFAULT; 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 /// Changes the terminal's title
void setTitle(string t) { void setTitle(string t) {
version(Win32Console) { version(Win32Console) {
// FIXME: use the W version wchar[256] buffer;
SetConsoleTitleA(toStringz(t)); 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 { } else {
import std.string; import std.string;
if(terminalInFamily("xterm", "rxvt", "screen")) if(terminalInFamily("xterm", "rxvt", "screen", "tmux"))
writeStringRaw(format("\033]0;%s\007", t)); writeStringRaw(format("\033]0;%s\007", t));
} }
} }
@ -1619,6 +1631,8 @@ struct RealTimeConsoleInput {
if(!(flags & ConsoleInputFlags.echo)) if(!(flags & ConsoleInputFlags.echo))
f |= ECHO; f |= ECHO;
// \033Z or \033[c
n.c_lflag &= ~f; n.c_lflag &= ~f;
tcsetattr(fdIn, TCSANOW, &n); tcsetattr(fdIn, TCSANOW, &n);
} }
@ -1672,20 +1686,20 @@ struct RealTimeConsoleInput {
// this is vt200 mouse with full motion tracking, supported by xterm // this is vt200 mouse with full motion tracking, supported by xterm
terminal.writeStringRaw("\033[?1003h"); terminal.writeStringRaw("\033[?1003h");
destructor ~= { terminal.writeStringRaw("\033[?1003l"); }; 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 terminal.writeStringRaw("\033[?1002h"); // this is vt200 mouse with press/release and motion notification iff buttons are pressed
destructor ~= { terminal.writeStringRaw("\033[?1002l"); }; destructor ~= { terminal.writeStringRaw("\033[?1002l"); };
} }
} }
if(flags & ConsoleInputFlags.paste) { if(flags & ConsoleInputFlags.paste) {
if(terminal.terminalInFamily("xterm", "rxvt", "screen")) { if(terminal.terminalInFamily("xterm", "rxvt", "screen", "tmux")) {
terminal.writeStringRaw("\033[?2004h"); // bracketed paste mode terminal.writeStringRaw("\033[?2004h"); // bracketed paste mode
destructor ~= { terminal.writeStringRaw("\033[?2004l"); }; destructor ~= { terminal.writeStringRaw("\033[?2004l"); };
} }
} }
// try to ensure the terminal is in UTF-8 mode // 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"); terminal.writeStringRaw("\033%G");
} }
@ -2416,7 +2430,7 @@ struct RealTimeConsoleInput {
default: default:
// screen doesn't actually do the modifiers, but // screen doesn't actually do the modifiers, but
// it uses the same format so this branch still works fine. // 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; import std.conv, std.string;
auto terminator = sequence[$ - 1]; auto terminator = sequence[$ - 1];
auto parts = sequence[2 .. $ - 1].split(";"); auto parts = sequence[2 .. $ - 1].split(";");
@ -2490,6 +2504,7 @@ struct RealTimeConsoleInput {
// starting at 70 i do some magic for like shift+enter etc. // starting at 70 i do some magic for like shift+enter etc.
// this only happens on my own terminal emulator. // this only happens on my own terminal emulator.
case "70": return keyPressAndRelease(NonCharacterKeyEvent.Key.ScrollLock, modifierState);
case "78": return keyPressAndRelease2('\b', modifierState); case "78": return keyPressAndRelease2('\b', modifierState);
case "79": return keyPressAndRelease2('\t', modifierState); case "79": return keyPressAndRelease2('\t', modifierState);
case "83": return keyPressAndRelease2('\n', modifierState); case "83": return keyPressAndRelease2('\n', modifierState);
@ -2610,6 +2625,7 @@ struct KeyboardEvent {
End = 0x23 + 0xF0000, /// . End = 0x23 + 0xF0000, /// .
PageUp = 0x21 + 0xF0000, /// . PageUp = 0x21 + 0xF0000, /// .
PageDown = 0x22 + 0xF0000, /// . PageDown = 0x22 + 0xF0000, /// .
ScrollLock = 0x91 + 0xF0000, /// unlikely to work outside my custom terminal emulator
} }
@ -2665,6 +2681,7 @@ struct NonCharacterKeyEvent {
End = 0x23, /// . End = 0x23, /// .
PageUp = 0x21, /// . PageUp = 0x21, /// .
PageDown = 0x22, /// . PageDown = 0x22, /// .
ScrollLock = 0x91, /// unlikely to work outside my terminal emulator
} }
Key key; /// . 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: support lines that wrap
FIXME: better controls maybe FIXME: better controls maybe