mirror of https://github.com/adamdruppe/arsd.git
Merge branch 'master' of github.com:adamdruppe/arsd
This commit is contained in:
commit
bfc8afad87
21
apng.d
21
apng.d
|
@ -57,6 +57,7 @@ class ApngFrame {
|
|||
auto height = frameControlChunk.height;
|
||||
|
||||
auto bytesPerLine = bytesPerLineOfPng(parent.header.depth, parent.header.type, width);
|
||||
bytesPerLine--; // removing filter byte from this calculation since we handle separtely
|
||||
|
||||
int idataIdx;
|
||||
ubyte[] idata;
|
||||
|
@ -79,11 +80,7 @@ class ApngFrame {
|
|||
this.data = idata;
|
||||
}
|
||||
|
||||
// then need to uncompress it
|
||||
// and unfilter it...
|
||||
// and then convert it to the right format.
|
||||
|
||||
MemoryImage frameData;
|
||||
//MemoryImage frameData;
|
||||
}
|
||||
|
||||
class ApngAnimation {
|
||||
|
@ -109,16 +106,16 @@ enum APNG_BLEND_OP : byte {
|
|||
OVER = 1
|
||||
}
|
||||
|
||||
void readApng(in ubyte[] data) {
|
||||
ApngAnimation readApng(in ubyte[] data) {
|
||||
auto png = readPng(data);
|
||||
auto header = PngHeader.fromChunk(png.chunks[0]);
|
||||
Color[] palette;
|
||||
if(header.type == 3) {
|
||||
palette = fetchPalette(png);
|
||||
}
|
||||
|
||||
auto obj = new ApngAnimation();
|
||||
|
||||
if(header.type == 3) {
|
||||
obj.palette = fetchPalette(png);
|
||||
}
|
||||
|
||||
bool seenIdat = false;
|
||||
bool seenFctl = false;
|
||||
|
||||
|
@ -227,11 +224,13 @@ void readApng(in ubyte[] data) {
|
|||
expectedSequenceNumber++;
|
||||
|
||||
// and the rest of it is a datastream...
|
||||
obj.frames[frameNumber - 1].compressedDatastream ~= chunk.payload;
|
||||
obj.frames[frameNumber - 1].compressedDatastream ~= chunk.payload[offset .. $];
|
||||
break;
|
||||
default:
|
||||
// ignore
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,91 @@
|
|||
/++
|
||||
My minimal interface to https://github.com/p-h-c/phc-winner-argon2
|
||||
|
||||
You must compile and install the C library separately.
|
||||
+/
|
||||
module arsd.argon2;
|
||||
|
||||
// it is conceivably useful to hash the password with a secret key before passing to this function,
|
||||
// but I'm not going to do that automatically here just to keep this thin and simple.
|
||||
|
||||
import core.stdc.stdint;
|
||||
|
||||
pragma(lib, "argon2");
|
||||
|
||||
extern(C)
|
||||
int argon2id_hash_encoded(
|
||||
const uint32_t t_cost,
|
||||
const uint32_t m_cost,
|
||||
const uint32_t parallelism,
|
||||
const void *pwd, const size_t pwdlen,
|
||||
const void *salt, const size_t saltlen,
|
||||
const size_t hashlen, char *encoded,
|
||||
const size_t encodedlen);
|
||||
|
||||
extern(C)
|
||||
int argon2id_verify(const char *encoded, const void *pwd,
|
||||
const size_t pwdlen);
|
||||
|
||||
enum ARGON2_OK = 0;
|
||||
|
||||
/// Parameters to the argon2 function. Bigger numbers make it harder to
|
||||
/// crack, but also take more resources for legitimate users too
|
||||
/// (e.g. making logins and signups slower and more memory-intensive). Some
|
||||
/// examples are provided. HighSecurity is about 3/4 second on my computer,
|
||||
/// MediumSecurity about 1/3 second, LowSecurity about 1/10 second.
|
||||
struct SecurityParameters {
|
||||
uint cpuCost;
|
||||
uint memoryCost; /// in KiB fyi
|
||||
uint parallelism;
|
||||
}
|
||||
|
||||
/// ditto
|
||||
enum HighSecurity = SecurityParameters(8, 512_000, 8);
|
||||
/// ditto
|
||||
enum MediumSecurity = SecurityParameters(4, 256_000, 4);
|
||||
/// ditto
|
||||
enum LowSecurity = SecurityParameters(2, 128_000, 4);
|
||||
|
||||
/// Check's a user's provided password against the saved password, and returns true if they matched. Neither string can be empty.
|
||||
bool verify(string savedPassword, string providedPassword) {
|
||||
return argon2id_verify((savedPassword[$-1] == 0 ? savedPassword : (savedPassword ~ '\0')).ptr, providedPassword.ptr, providedPassword.length) == ARGON2_OK;
|
||||
}
|
||||
|
||||
/// encode a password for secure storage. verify later with [verify]
|
||||
string encode(string password, SecurityParameters params = MediumSecurity) {
|
||||
char[256] buffer;
|
||||
enum HASHLEN = 80;
|
||||
|
||||
import core.stdc.string;
|
||||
|
||||
ubyte[32] salt = void;
|
||||
|
||||
version(linux) {{
|
||||
import core.sys.posix.unistd;
|
||||
import core.sys.posix.fcntl;
|
||||
int fd = open("/dev/urandom", O_RDONLY);
|
||||
auto ret = read(fd, salt.ptr, salt.length);
|
||||
assert(ret == salt.length);
|
||||
close(fd);
|
||||
}} else {
|
||||
import std.random;
|
||||
foreach(ref s; salt)
|
||||
s = cast(ubyte) uniform(0, 256);
|
||||
}
|
||||
|
||||
auto ret = argon2id_hash_encoded(
|
||||
params.cpuCost,
|
||||
params.memoryCost,
|
||||
params.parallelism,
|
||||
password.ptr, password.length,
|
||||
salt.ptr, salt.length,
|
||||
HASHLEN, // desired size of hash. I think this is fine being arbitrary
|
||||
buffer.ptr,
|
||||
buffer.length
|
||||
);
|
||||
|
||||
if(ret != ARGON2_OK)
|
||||
throw new Exception("wtf");
|
||||
|
||||
return buffer[0 .. strlen(buffer.ptr) + 1].idup;
|
||||
}
|
193
cgi.d
193
cgi.d
|
@ -1109,7 +1109,7 @@ class Cgi {
|
|||
|
||||
|
||||
///
|
||||
void writeToFile(string filenameToSaveTo) {
|
||||
void writeToFile(string filenameToSaveTo) const {
|
||||
import std.file;
|
||||
if(contentInMemory)
|
||||
std.file.write(filenameToSaveTo, content);
|
||||
|
@ -5003,6 +5003,143 @@ private struct SerializationBuffer {
|
|||
will have to have dump and restore too, so i can restart without losing stuff.
|
||||
*/
|
||||
|
||||
|
||||
/++
|
||||
A convenience object for talking to the [BasicDataServer] from a higher level.
|
||||
See: [getSessionObject]
|
||||
|
||||
You pass it a `Data` struct describing the data you want saved in the session.
|
||||
Then, this class will generate getter and setter properties that allow access
|
||||
to that data.
|
||||
|
||||
Note that each load and store will be done as-accessed; it doesn't front-load
|
||||
mutable data nor does it batch updates out of fear of read-modify-write race
|
||||
conditions. (In fact, right now it does this for everything, but in the future,
|
||||
I might batch load `immutable` members of the Data struct.)
|
||||
|
||||
At some point in the future, I might also let it do different backends, like
|
||||
a client-side cookie store too, but idk.
|
||||
+/
|
||||
class Session(Data) {
|
||||
private Cgi cgi;
|
||||
private string sessionId;
|
||||
|
||||
/// You probably don't want to call this directly, see [getSessionObject] instead.
|
||||
this(Cgi cgi) {
|
||||
this.cgi = cgi;
|
||||
if(auto ptr = "sessionId" in cgi.cookies)
|
||||
sessionId = (*ptr).length ? *ptr : null;
|
||||
}
|
||||
|
||||
/++
|
||||
Starts a new session. Note that a session is also
|
||||
implicitly started as soon as you write data to it,
|
||||
so if you need to alter these parameters from their
|
||||
defaults, be sure to explicitly call this BEFORE doing
|
||||
any writes to session data.
|
||||
|
||||
Params:
|
||||
idleLifetime = How long, in seconds, the session
|
||||
should remain in memory when not being read from
|
||||
or written to. The default is one day.
|
||||
|
||||
NOT IMPLEMENTED
|
||||
|
||||
useExtendedLifetimeCookie = The session ID is always
|
||||
stored in a HTTP cookie, and by default, that cookie
|
||||
is discarded when the user closes their browser.
|
||||
|
||||
But if you set this to true, it will use a non-perishable
|
||||
cookie for the given idleLifetime.
|
||||
|
||||
NOT IMPLEMENTED
|
||||
+/
|
||||
void start(int idleLifetime = 2600 * 24, bool useExtendedLifetimeCookie = false) {
|
||||
assert(sessionId is null);
|
||||
|
||||
// FIXME: what if there is a session ID cookie, but no corresponding session on the server?
|
||||
|
||||
import std.random, std.conv;
|
||||
sessionId = to!string(uniform(1, long.max));
|
||||
|
||||
BasicDataServer.connection.createSession(sessionId, idleLifetime);
|
||||
setCookie();
|
||||
}
|
||||
|
||||
private void setCookie() {
|
||||
cgi.setCookie(
|
||||
"sessionId", sessionId,
|
||||
0 /* expiration */,
|
||||
null /* path */,
|
||||
null /* domain */,
|
||||
true /* http only */,
|
||||
cgi.https /* if the session is started on https, keep it there, otherwise, be flexible */);
|
||||
}
|
||||
|
||||
/++
|
||||
Regenerates the session ID and updates the associated
|
||||
cookie.
|
||||
|
||||
This is also your chance to change immutable data
|
||||
(not yet implemented).
|
||||
+/
|
||||
void regenerateId() {
|
||||
if(sessionId is null) {
|
||||
start();
|
||||
return;
|
||||
}
|
||||
import std.random, std.conv;
|
||||
auto oldSessionId = sessionId;
|
||||
sessionId = to!string(uniform(1, long.max));
|
||||
BasicDataServer.connection.renameSession(oldSessionId, sessionId);
|
||||
setCookie();
|
||||
}
|
||||
|
||||
/++
|
||||
Terminates this session, deleting all saved data.
|
||||
+/
|
||||
void terminate() {
|
||||
BasicDataServer.connection.destroySession(sessionId);
|
||||
sessionId = null;
|
||||
setCookie();
|
||||
}
|
||||
|
||||
/++
|
||||
Plain-old-data members of your `Data` struct are wrapped here via
|
||||
the opDispatch property getters and setters.
|
||||
|
||||
If the member is a non-string array, it returns a magical array proxy
|
||||
object which allows for atomic appends and replaces via overloaded operators.
|
||||
You can slice this to get a range representing a $(B const) view of the array.
|
||||
This is to protect you against read-modify-write race conditions.
|
||||
+/
|
||||
@property typeof(__traits(getMember, Data, name)) opDispatch(string name)() {
|
||||
if(sessionId is null)
|
||||
return typeof(return).init;
|
||||
|
||||
auto v = BasicDataServer.connection.getSessionData(sessionId, name);
|
||||
if(v.length == 0)
|
||||
return typeof(return).init;
|
||||
import std.conv;
|
||||
return to!(typeof(return))(v);
|
||||
}
|
||||
|
||||
/// ditto
|
||||
@property void opDispatch(string name)(typeof(__traits(getMember, Data, name)) value) {
|
||||
if(sessionId is null)
|
||||
start();
|
||||
import std.conv;
|
||||
BasicDataServer.connection.setSessionData(sessionId, name, to!string(value));
|
||||
}
|
||||
}
|
||||
|
||||
/++
|
||||
Gets a session object associated with the `cgi` request.
|
||||
+/
|
||||
Session!Data getSessionObject(Data)(Cgi cgi) {
|
||||
return new Session!Data(cgi);
|
||||
}
|
||||
|
||||
/++
|
||||
|
||||
+/
|
||||
|
@ -5047,10 +5184,20 @@ final class BasicDataServerImplementation : BasicDataServer, EventIoServer {
|
|||
sessions.remove(oldSessionId);
|
||||
}
|
||||
void setSessionData(string sessionId, string dataKey, string dataValue) {
|
||||
if(sessionId !in sessions)
|
||||
createSession(sessionId, 3600); // FIXME?
|
||||
sessions[sessionId].values[dataKey.idup] = dataValue.idup;
|
||||
}
|
||||
string getSessionData(string sessionId, string dataKey) {
|
||||
return sessions[sessionId].values[dataKey];
|
||||
if(auto session = sessionId in sessions) {
|
||||
if(auto data = dataKey in (*session).values)
|
||||
return *data;
|
||||
else
|
||||
return null; // no such data
|
||||
|
||||
} else {
|
||||
return null; // no session
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -5757,10 +5904,17 @@ ssize_t read_fd(int fd, void *ptr, size_t nbytes, int *recvfd) {
|
|||
switch to choose if you want to override.
|
||||
*/
|
||||
|
||||
struct DispatcherDefinition(alias dispatchHandler) {// if(is(typeof(dispatchHandler("str", Cgi.init) == bool))) { // bool delegate(string urlPrefix, Cgi cgi) dispatchHandler;
|
||||
struct DispatcherDefinition(alias dispatchHandler) {// if(is(typeof(dispatchHandler("str", Cgi.init, void) == bool))) { // bool delegate(string urlPrefix, Cgi cgi) dispatchHandler;
|
||||
alias handler = dispatchHandler;
|
||||
string urlPrefix;
|
||||
bool rejectFurther;
|
||||
DispatcherDetails details;
|
||||
}
|
||||
|
||||
// tbh I am really unhappy with this part
|
||||
struct DispatcherDetails {
|
||||
string filename;
|
||||
string contentType;
|
||||
}
|
||||
|
||||
private string urlify(string name) {
|
||||
|
@ -6428,7 +6582,7 @@ auto serveApi(T)(string urlPrefix) {
|
|||
import arsd.dom;
|
||||
import arsd.jsvar;
|
||||
|
||||
static bool handler(string urlPrefix, Cgi cgi) {
|
||||
static bool handler(string urlPrefix, Cgi cgi, DispatcherDetails details) {
|
||||
|
||||
auto obj = new T();
|
||||
obj.initialize(cgi);
|
||||
|
@ -6780,7 +6934,7 @@ class CollectionOf(Obj, Helper = void) : RestObject!(Helper) {
|
|||
+/
|
||||
auto serveRestObject(T)(string urlPrefix) {
|
||||
assert(urlPrefix[$ - 1] != '/', "Do NOT use a trailing slash on REST objects.");
|
||||
static bool handler(string urlPrefix, Cgi cgi) {
|
||||
static bool handler(string urlPrefix, Cgi cgi, DispatcherDetails details) {
|
||||
string url = cgi.pathInfo[urlPrefix.length .. $];
|
||||
|
||||
if(url.length && url[$ - 1] == '/') {
|
||||
|
@ -7071,15 +7225,26 @@ auto serveStaticFile(string urlPrefix, string filename = null, string contentTyp
|
|||
if(filename is null)
|
||||
filename = urlPrefix[1 .. $];
|
||||
if(contentType is null) {
|
||||
|
||||
if(filename.endsWith(".png"))
|
||||
contentType = "image/png";
|
||||
if(filename.endsWith(".jpg"))
|
||||
contentType = "image/jpeg";
|
||||
if(filename.endsWith(".html"))
|
||||
contentType = "text/html";
|
||||
if(filename.endsWith(".css"))
|
||||
contentType = "text/css";
|
||||
if(filename.endsWith(".js"))
|
||||
contentType = "application/javascript";
|
||||
}
|
||||
static bool handler(string urlPrefix, Cgi cgi) {
|
||||
//cgi.setResponseContentType(contentType);
|
||||
//cgi.write(std.file.read(filename), true);
|
||||
cgi.write(std.file.read(urlPrefix[1 .. $]), true);
|
||||
|
||||
static bool handler(string urlPrefix, Cgi cgi, DispatcherDetails details) {
|
||||
if(details.contentType.indexOf("image/") == 0)
|
||||
cgi.setCache(true);
|
||||
cgi.setResponseContentType(details.contentType);
|
||||
cgi.write(std.file.read(details.filename), true);
|
||||
return true;
|
||||
}
|
||||
return DispatcherDefinition!handler(urlPrefix, true);
|
||||
return DispatcherDefinition!handler(urlPrefix, true, DispatcherDetails(filename, contentType));
|
||||
}
|
||||
|
||||
auto serveRedirect(string urlPrefix, string redirectTo) {
|
||||
|
@ -7108,15 +7273,15 @@ auto serveStaticData(string urlPrefix, const(void)[] data, string contentType) {
|
|||
+/
|
||||
bool dispatcher(definitions...)(Cgi cgi) {
|
||||
// I can prolly make this more efficient later but meh.
|
||||
foreach(definition; definitions) {
|
||||
static foreach(definition; definitions) {
|
||||
if(definition.rejectFurther) {
|
||||
if(cgi.pathInfo == definition.urlPrefix) {
|
||||
auto ret = definition.handler(definition.urlPrefix, cgi);
|
||||
auto ret = definition.handler(definition.urlPrefix, cgi, definition.details);
|
||||
if(ret)
|
||||
return true;
|
||||
}
|
||||
} else if(cgi.pathInfo.startsWith(definition.urlPrefix)) {
|
||||
auto ret = definition.handler(definition.urlPrefix, cgi);
|
||||
auto ret = definition.handler(definition.urlPrefix, cgi, definition.details);
|
||||
if(ret)
|
||||
return true;
|
||||
}
|
||||
|
|
2
dom.d
2
dom.d
|
@ -819,7 +819,7 @@ class Document : FileResource {
|
|||
}
|
||||
|
||||
if(strict)
|
||||
enforce(data[pos] == '>');//, format("got %s when expecting >\nContext:\n%s", data[pos], data[pos - 100 .. pos + 100]));
|
||||
enforce(data[pos] == '>', format("got %s when expecting > (possible missing attribute name)\nContext:\n%s", data[pos], data[pos - 100 .. pos + 100]));
|
||||
else {
|
||||
// if we got here, it's probably because a slash was in an
|
||||
// unquoted attribute - don't trust the selfClosed value
|
||||
|
|
10
jsvar.d
10
jsvar.d
|
@ -654,6 +654,10 @@ struct var {
|
|||
static if(is(typeof(__traits(getMember, t, member)) == function)) {
|
||||
// skipping these because the delegate we get isn't going to work anyway; the object may be dead and certainly won't be updated
|
||||
//this[member] = &__traits(getMember, proxyObject, member);
|
||||
|
||||
//but for simple toString, I'll allow it. or maybe not it doesn't work right.
|
||||
//static if(member == "toString" && is(typeof(&__traits(getMember, t, member)) == string delegate()))
|
||||
//this[member] = &__traits(getMember, t, member);
|
||||
} else
|
||||
this[member] = __traits(getMember, t, member);
|
||||
}
|
||||
|
@ -1241,6 +1245,12 @@ struct var {
|
|||
return v;
|
||||
}
|
||||
|
||||
@property static var emptyObject(var prototype) {
|
||||
if(prototype._type == Type.Object)
|
||||
return var.emptyObject(prototype._payload._object);
|
||||
return var.emptyObject();
|
||||
}
|
||||
|
||||
@property PrototypeObject prototypeObject() {
|
||||
var v = prototype();
|
||||
if(v._type == Type.Object)
|
||||
|
|
3
png.d
3
png.d
|
@ -231,7 +231,7 @@ void convertPngData(ubyte type, ubyte depth, const(ubyte)[] data, int width, uby
|
|||
break;
|
||||
default: assert(0);
|
||||
}
|
||||
assert(data.length == 0, "not all consumed, wtf ");// ~ to!string(h));
|
||||
assert(data.length == 0, "not all consumed, wtf " ~ to!string(data));
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -1482,6 +1482,7 @@ struct LazyPngFile(LazyPngChunksProvider)
|
|||
}
|
||||
|
||||
// FIXME: doesn't handle interlacing... I think
|
||||
// note it returns the length including the filter byte!!
|
||||
@nogc @safe pure nothrow
|
||||
int bytesPerLineOfPng(ubyte depth, ubyte type, uint width) {
|
||||
immutable bitsPerChannel = depth;
|
||||
|
|
|
@ -81,6 +81,8 @@ class PostgreSql : Database {
|
|||
|
||||
auto res = PQexec(conn, toStringz(sql));
|
||||
int ress = PQresultStatus(res);
|
||||
// https://www.postgresql.org/docs/current/libpq-exec.html
|
||||
// FIXME: PQresultErrorField can get a lot more info in a more structured way
|
||||
if(ress != PGRES_TUPLES_OK
|
||||
&& ress != PGRES_COMMAND_OK)
|
||||
throw new DatabaseException(error());
|
||||
|
|
|
@ -198,6 +198,17 @@ import core.thread;
|
|||
final class AudioPcmOutThread : Thread {
|
||||
///
|
||||
this() {
|
||||
version(linux) {
|
||||
// this thread has no business intercepting signals from the main thread,
|
||||
// so gonna block a couple of them
|
||||
import core.sys.posix.signal;
|
||||
sigset_t sigset;
|
||||
auto err = sigfillset(&sigset);
|
||||
assert(!err);
|
||||
err = sigprocmask(SIG_BLOCK, &sigset, null);
|
||||
assert(!err);
|
||||
}
|
||||
|
||||
super(&run);
|
||||
}
|
||||
|
||||
|
|
|
@ -848,8 +848,6 @@ version(Windows) {
|
|||
|
||||
// http://wiki.dlang.org/Simpledisplay.d
|
||||
|
||||
// FIXME: SIGINT handler is necessary to clean up shared memory handles upon ctrl+c
|
||||
|
||||
// see : http://www.sbin.org/doc/Xlib/chapt_09.html section on Keyboard Preferences re: scroll lock led
|
||||
|
||||
// Cool stuff: I want right alt and scroll lock to do different stuff for personal use. maybe even right ctrl
|
||||
|
@ -1351,6 +1349,14 @@ class SimpleWindow : CapableOfHandlingNativeEvent, CapableOfBeingDrawnUpon {
|
|||
is the drawable space inside; it excludes the title bar, etc.)
|
||||
|
||||
Windows based on images will not be resizable and do not use OpenGL.
|
||||
|
||||
It will draw the image in upon creation, but this will be overwritten
|
||||
upon any draws, including the initial window visible event.
|
||||
|
||||
You probably do not want to use this and it may be removed from
|
||||
the library eventually, or I might change it to be a "permanent"
|
||||
background image; one that is automatically drawn on it before any
|
||||
other drawing event. idk.
|
||||
+/
|
||||
this(Image image, string title = null) {
|
||||
this(image.width, image.height, title);
|
||||
|
@ -2345,6 +2351,7 @@ private:
|
|||
|
||||
version(X11) {
|
||||
__gshared int customEventFD = -1;
|
||||
__gshared int customSignalFD = -1;
|
||||
} else version(Windows) {
|
||||
__gshared HANDLE customEventH = null;
|
||||
}
|
||||
|
@ -2713,6 +2720,12 @@ struct EventLoop {
|
|||
impl.notExited = false;
|
||||
}
|
||||
|
||||
version(linux)
|
||||
ref void delegate(int) signalHandler() {
|
||||
assert(impl !is null);
|
||||
return impl.signalHandler;
|
||||
}
|
||||
|
||||
static EventLoopImpl* impl;
|
||||
}
|
||||
|
||||
|
@ -2741,6 +2754,8 @@ struct EventLoopImpl {
|
|||
static import unix = core.sys.posix.unistd;
|
||||
static import err = core.stdc.errno;
|
||||
import core.sys.linux.timerfd;
|
||||
|
||||
void delegate(int) signalHandler;
|
||||
}
|
||||
|
||||
version(X11) {
|
||||
|
@ -2843,6 +2858,29 @@ struct EventLoopImpl {
|
|||
throw new Exception("can't create eventfd for custom event processing");
|
||||
}
|
||||
}
|
||||
|
||||
if (customSignalFD == -1) {
|
||||
import core.sys.linux.sys.signalfd;
|
||||
|
||||
sigset_t sigset;
|
||||
auto err = sigemptyset(&sigset);
|
||||
assert(!err);
|
||||
err = sigaddset(&sigset, SIGINT);
|
||||
assert(!err);
|
||||
err = sigaddset(&sigset, SIGHUP);
|
||||
assert(!err);
|
||||
err = sigprocmask(SIG_BLOCK, &sigset, null);
|
||||
assert(!err);
|
||||
|
||||
customSignalFD = signalfd(-1, &sigset, SFD_NONBLOCK);
|
||||
assert(customSignalFD != -1);
|
||||
|
||||
ep.epoll_event ev = void;
|
||||
{ import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy
|
||||
ev.events = ep.EPOLLIN;
|
||||
ev.data.fd = customSignalFD;
|
||||
ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, customSignalFD, &ev);
|
||||
}
|
||||
}
|
||||
|
||||
SimpleWindow.processAllCustomEvents(); // process events added before event FD creation
|
||||
|
@ -2909,8 +2947,10 @@ struct EventLoopImpl {
|
|||
dispose();
|
||||
}
|
||||
|
||||
version(X11)
|
||||
version(linux)
|
||||
ref int customEventFD() { return SimpleWindow.customEventFD; }
|
||||
version(linux)
|
||||
ref int customSignalFD() { return SimpleWindow.customSignalFD; }
|
||||
version(Windows)
|
||||
ref auto customEventH() { return SimpleWindow.customEventH; }
|
||||
|
||||
|
@ -2957,7 +2997,22 @@ struct EventLoopImpl {
|
|||
assert(fd != -1); // should never happen cuz the api doesn't do that but better to assert than assume.
|
||||
auto flags = events[idx].events;
|
||||
if(flags & ep.EPOLLIN) {
|
||||
if(fd == display.fd) {
|
||||
if (fd == customSignalFD) {
|
||||
version(linux) {
|
||||
import core.sys.linux.sys.signalfd;
|
||||
import core.sys.posix.unistd : read;
|
||||
signalfd_siginfo info;
|
||||
read(customSignalFD, &info, info.sizeof);
|
||||
|
||||
auto sig = info.ssi_signo;
|
||||
|
||||
if(EventLoop.get.signalHandler !is null) {
|
||||
EventLoop.get.signalHandler()(sig);
|
||||
} else {
|
||||
EventLoop.get.exit();
|
||||
}
|
||||
}
|
||||
} else if(fd == display.fd) {
|
||||
version(sdddd) { import std.stdio; writeln("X EVENT PENDING!"); }
|
||||
this.mtLock();
|
||||
scope(exit) this.mtUnlock();
|
||||
|
@ -4628,8 +4683,8 @@ version(X11) {
|
|||
setX11Selection!"SECONDARY"(window, text);
|
||||
}
|
||||
|
||||
///
|
||||
void setX11Selection(string atomName)(SimpleWindow window, string text) {
|
||||
/// The `after` delegate is called after a client requests the UTF8_STRING thing. it is a mega-hack right now!
|
||||
void setX11Selection(string atomName)(SimpleWindow window, string text, void delegate() after = null) {
|
||||
assert(window !is null);
|
||||
|
||||
auto display = XDisplayConnection.get();
|
||||
|
@ -4673,6 +4728,9 @@ version(X11) {
|
|||
event.target,
|
||||
8 /* bits */, 0 /* PropModeReplace */,
|
||||
text.ptr, cast(int) text.length);
|
||||
|
||||
if(after)
|
||||
after();
|
||||
} else {
|
||||
selectionEvent.property = None; // I don't know how to handle this type...
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue