mirror of https://github.com/adamdruppe/arsd.git
Merge branch 'master' of github.com:adamdruppe/arsd
This commit is contained in:
commit
32a48d4ea0
2
apng.d
2
apng.d
|
@ -728,7 +728,7 @@ ApngAnimation readApng(in ubyte[] data, bool strictApng = false, scope ApngAnima
|
||||||
obj.frames[frameNumber - 1].compressedDatastream ~= chunk.payload[offset .. $];
|
obj.frames[frameNumber - 1].compressedDatastream ~= chunk.payload[offset .. $];
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
obj.handleOtherChunk(chunk);
|
obj.handleOtherChunkWhenLoading(chunk);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
85
cgi.d
85
cgi.d
|
@ -3510,11 +3510,11 @@ struct RequestServer {
|
||||||
foundHost = false;
|
foundHost = false;
|
||||||
}
|
}
|
||||||
if(foundUid) {
|
if(foundUid) {
|
||||||
privDropUserId = to!int(arg);
|
privilegesDropToUid = to!uid_t(arg);
|
||||||
foundUid = false;
|
foundUid = false;
|
||||||
}
|
}
|
||||||
if(foundGid) {
|
if(foundGid) {
|
||||||
privDropGroupId = to!int(arg);
|
privilegesDropToGid = to!gid_t(arg);
|
||||||
foundGid = false;
|
foundGid = false;
|
||||||
}
|
}
|
||||||
if(arg == "--listening-host" || arg == "-h" || arg == "/listening-host")
|
if(arg == "--listening-host" || arg == "-h" || arg == "/listening-host")
|
||||||
|
@ -3528,7 +3528,37 @@ struct RequestServer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME: the privDropUserId/group id need to be set in here instead of global
|
version(Windows) {
|
||||||
|
private alias uid_t = int;
|
||||||
|
private alias gid_t = int;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// user (uid) to drop privileges to
|
||||||
|
/// 0 … do nothing
|
||||||
|
uid_t privilegesDropToUid = 0;
|
||||||
|
/// group (gid) to drop privileges to
|
||||||
|
/// 0 … do nothing
|
||||||
|
gid_t privilegesDropToGid = 0;
|
||||||
|
|
||||||
|
private void dropPrivileges() {
|
||||||
|
version(Posix) {
|
||||||
|
import core.sys.posix.unistd;
|
||||||
|
|
||||||
|
if (privilegesDropToGid != 0 && setgid(privilegesDropToGid) != 0)
|
||||||
|
throw new Exception("Dropping privileges via setgid() failed.");
|
||||||
|
|
||||||
|
if (privilegesDropToUid != 0 && setuid(privilegesDropToUid) != 0)
|
||||||
|
throw new Exception("Dropping privileges via setuid() failed.");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// FIXME: Windows?
|
||||||
|
//pragma(msg, "Dropping privileges is not implemented for this platform");
|
||||||
|
}
|
||||||
|
|
||||||
|
// done, set zero
|
||||||
|
privilegesDropToGid = 0;
|
||||||
|
privilegesDropToUid = 0;
|
||||||
|
}
|
||||||
|
|
||||||
/++
|
/++
|
||||||
Serves a single HTTP request on this thread, with an embedded server, then stops. Designed for cases like embedded oauth responders
|
Serves a single HTTP request on this thread, with an embedded server, then stops. Designed for cases like embedded oauth responders
|
||||||
|
@ -3557,7 +3587,7 @@ struct RequestServer {
|
||||||
|
|
||||||
bool tcp;
|
bool tcp;
|
||||||
void delegate() cleanup;
|
void delegate() cleanup;
|
||||||
auto socket = startListening(listeningHost, listeningPort, tcp, cleanup, 1);
|
auto socket = startListening(listeningHost, listeningPort, tcp, cleanup, 1, &dropPrivileges);
|
||||||
auto connection = socket.accept();
|
auto connection = socket.accept();
|
||||||
doThreadHttpConnectionGuts!(CustomCgi, fun, true)(connection);
|
doThreadHttpConnectionGuts!(CustomCgi, fun, true)(connection);
|
||||||
|
|
||||||
|
@ -3680,28 +3710,6 @@ else
|
||||||
|
|
||||||
private __gshared bool globalStopFlag = false;
|
private __gshared bool globalStopFlag = false;
|
||||||
|
|
||||||
private int privDropUserId;
|
|
||||||
private int privDropGroupId;
|
|
||||||
|
|
||||||
// Added Jan 11, 2021
|
|
||||||
private void dropPrivs() {
|
|
||||||
version(Posix) {
|
|
||||||
import core.sys.posix.unistd;
|
|
||||||
|
|
||||||
auto userId = privDropUserId;
|
|
||||||
auto groupId = privDropGroupId;
|
|
||||||
|
|
||||||
if((userId != 0 || groupId != 0) && getuid() == 0) {
|
|
||||||
if(groupId)
|
|
||||||
setgid(groupId);
|
|
||||||
if(userId)
|
|
||||||
setuid(userId);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
// FIXME: Windows?
|
|
||||||
}
|
|
||||||
|
|
||||||
version(embedded_httpd_processes)
|
version(embedded_httpd_processes)
|
||||||
void serveEmbeddedHttpdProcesses(alias fun, CustomCgi = Cgi)(RequestServer params) {
|
void serveEmbeddedHttpdProcesses(alias fun, CustomCgi = Cgi)(RequestServer params) {
|
||||||
import core.sys.posix.unistd;
|
import core.sys.posix.unistd;
|
||||||
|
@ -3745,7 +3753,7 @@ void serveEmbeddedHttpdProcesses(alias fun, CustomCgi = Cgi)(RequestServer param
|
||||||
close(sock);
|
close(sock);
|
||||||
throw new Exception("listen");
|
throw new Exception("listen");
|
||||||
}
|
}
|
||||||
dropPrivs();
|
params.dropPrivileges();
|
||||||
}
|
}
|
||||||
|
|
||||||
version(embedded_httpd_processes_accept_after_fork) {} else {
|
version(embedded_httpd_processes_accept_after_fork) {} else {
|
||||||
|
@ -5153,9 +5161,13 @@ import core.atomic;
|
||||||
/**
|
/**
|
||||||
To use this thing:
|
To use this thing:
|
||||||
|
|
||||||
|
---
|
||||||
void handler(Socket s) { do something... }
|
void handler(Socket s) { do something... }
|
||||||
auto manager = new ListeningConnectionManager("127.0.0.1", 80, &handler);
|
auto manager = new ListeningConnectionManager("127.0.0.1", 80, &handler, &delegateThatDropsPrivileges);
|
||||||
manager.listen();
|
manager.listen();
|
||||||
|
---
|
||||||
|
|
||||||
|
The 4th parameter is optional.
|
||||||
|
|
||||||
I suggest you use BufferedInputRange(connection) to handle the input. As a packet
|
I suggest you use BufferedInputRange(connection) to handle the input. As a packet
|
||||||
comes in, you will get control. You can just continue; though to fetch more.
|
comes in, you will get control. You can just continue; though to fetch more.
|
||||||
|
@ -5355,15 +5367,15 @@ class ListeningConnectionManager {
|
||||||
private void dg_handler(Socket s) {
|
private void dg_handler(Socket s) {
|
||||||
fhandler(s);
|
fhandler(s);
|
||||||
}
|
}
|
||||||
this(string host, ushort port, void function(Socket) handler) {
|
this(string host, ushort port, void function(Socket) handler, void delegate() dropPrivs = null) {
|
||||||
fhandler = handler;
|
fhandler = handler;
|
||||||
this(host, port, &dg_handler);
|
this(host, port, &dg_handler, dropPrivs);
|
||||||
}
|
}
|
||||||
|
|
||||||
this(string host, ushort port, void delegate(Socket) handler) {
|
this(string host, ushort port, void delegate(Socket) handler, void delegate() dropPrivs = null) {
|
||||||
this.handler = handler;
|
this.handler = handler;
|
||||||
|
|
||||||
listener = startListening(host, port, tcp, cleanup, 128);
|
listener = startListening(host, port, tcp, cleanup, 128, dropPrivs);
|
||||||
|
|
||||||
version(cgi_use_fiber) version(cgi_use_fork)
|
version(cgi_use_fiber) version(cgi_use_fork)
|
||||||
listener.blocking = false;
|
listener.blocking = false;
|
||||||
|
@ -5376,7 +5388,7 @@ class ListeningConnectionManager {
|
||||||
void delegate(Socket) handler;
|
void delegate(Socket) handler;
|
||||||
}
|
}
|
||||||
|
|
||||||
Socket startListening(string host, ushort port, ref bool tcp, ref void delegate() cleanup, int backQueue) {
|
Socket startListening(string host, ushort port, ref bool tcp, ref void delegate() cleanup, int backQueue, void delegate() dropPrivs) {
|
||||||
Socket listener;
|
Socket listener;
|
||||||
if(host.startsWith("unix:")) {
|
if(host.startsWith("unix:")) {
|
||||||
version(Posix) {
|
version(Posix) {
|
||||||
|
@ -5424,7 +5436,8 @@ Socket startListening(string host, ushort port, ref bool tcp, ref void delegate(
|
||||||
|
|
||||||
listener.listen(backQueue);
|
listener.listen(backQueue);
|
||||||
|
|
||||||
dropPrivs();
|
if (dropPrivs !is null) // can be null, backwards compatibility
|
||||||
|
dropPrivs();
|
||||||
|
|
||||||
return listener;
|
return listener;
|
||||||
}
|
}
|
||||||
|
@ -10791,7 +10804,7 @@ auto serveStaticFile(string urlPrefix, string filename = null, string contentTyp
|
||||||
// man 2 sendfile
|
// man 2 sendfile
|
||||||
assert(urlPrefix[0] == '/');
|
assert(urlPrefix[0] == '/');
|
||||||
if(filename is null)
|
if(filename is null)
|
||||||
filename = urlPrefix[1 .. $];
|
filename = decodeComponent(urlPrefix[1 .. $]); // FIXME is this actually correct?
|
||||||
if(contentType is null) {
|
if(contentType is null) {
|
||||||
contentType = contentTypeFromFileExtension(filename);
|
contentType = contentTypeFromFileExtension(filename);
|
||||||
}
|
}
|
||||||
|
@ -10873,7 +10886,7 @@ auto serveStaticFileDirectory(string urlPrefix, string directory = null) {
|
||||||
assert(directory[$-1] == '/');
|
assert(directory[$-1] == '/');
|
||||||
|
|
||||||
static bool internalHandler(string urlPrefix, Cgi cgi, Object presenter, DispatcherDetails details) {
|
static bool internalHandler(string urlPrefix, Cgi cgi, Object presenter, DispatcherDetails details) {
|
||||||
auto file = cgi.pathInfo[urlPrefix.length .. $];
|
auto file = decodeComponent(cgi.pathInfo[urlPrefix.length .. $]); // FIXME: is this actually correct
|
||||||
if(file.indexOf("/") != -1 || file.indexOf("\\") != -1)
|
if(file.indexOf("/") != -1 || file.indexOf("\\") != -1)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
|
|
@ -199,7 +199,9 @@ version(linux) {
|
||||||
break;
|
break;
|
||||||
else assert(0); // , to!string(fd) ~ " " ~ to!string(errno));
|
else assert(0); // , to!string(fd) ~ " " ~ to!string(errno));
|
||||||
}
|
}
|
||||||
assert(r == event.sizeof);
|
if(r != event.sizeof)
|
||||||
|
throw new Exception("Read something weird off the joystick event fd");
|
||||||
|
//import std.stdio; writeln(event);
|
||||||
|
|
||||||
ptrdiff_t player = -1;
|
ptrdiff_t player = -1;
|
||||||
foreach(i, f; joystickFds)
|
foreach(i, f; joystickFds)
|
||||||
|
@ -230,6 +232,7 @@ version(linux) {
|
||||||
}
|
}
|
||||||
if(event.type & JS_EVENT_BUTTON) {
|
if(event.type & JS_EVENT_BUTTON) {
|
||||||
joystickState[player].buttons[event.number] = event.value ? 255 : 0;
|
joystickState[player].buttons[event.number] = event.value ? 255 : 0;
|
||||||
|
//writeln(player, " ", event.number, " ", event.value, " ", joystickState[player].buttons[event.number]);//, " != ", event.value ? 255 : 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
225
minigui.d
225
minigui.d
|
@ -1925,6 +1925,18 @@ class Widget : ReflectableProperties {
|
||||||
return StyleInformation(this);
|
return StyleInformation(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int focusableWidgets(scope int delegate(Widget) dg) {
|
||||||
|
foreach(widget; WidgetStream(this)) {
|
||||||
|
if(widget.tabStop && !widget.hidden) {
|
||||||
|
int result = dg(widget);
|
||||||
|
if (result)
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// FIXME: I kinda want to hide events from implementation widgets
|
// FIXME: I kinda want to hide events from implementation widgets
|
||||||
// so it just catches them all and stops propagation...
|
// so it just catches them all and stops propagation...
|
||||||
// i guess i can do it with a event listener on star.
|
// i guess i can do it with a event listener on star.
|
||||||
|
@ -7084,7 +7096,7 @@ class HorizontalLayout : Layout {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
version(Windows)
|
version(win32_widgets)
|
||||||
private
|
private
|
||||||
extern(Windows)
|
extern(Windows)
|
||||||
LRESULT DoubleBufferWndProc(HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam) nothrow {
|
LRESULT DoubleBufferWndProc(HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam) nothrow {
|
||||||
|
@ -7944,6 +7956,12 @@ class Window : Widget {
|
||||||
Window.newWindowCreated(this);
|
Window.newWindowCreated(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
version(custom_widgets)
|
||||||
|
override void defaultEventHandler_click(ClickEvent event) {
|
||||||
|
if(event.target && event.target.tabStop)
|
||||||
|
event.target.focus();
|
||||||
|
}
|
||||||
|
|
||||||
private static void delegate(Window) newWindowCreated;
|
private static void delegate(Window) newWindowCreated;
|
||||||
|
|
||||||
version(win32_widgets)
|
version(win32_widgets)
|
||||||
|
@ -8082,17 +8100,63 @@ class Window : Widget {
|
||||||
}
|
}
|
||||||
win = new SimpleWindow(width, height, title, OpenGlOptions.no, Resizability.allowResizing, WindowTypes.normal, WindowFlags.dontAutoShow | WindowFlags.managesChildWindowFocus);
|
win = new SimpleWindow(width, height, title, OpenGlOptions.no, Resizability.allowResizing, WindowTypes.normal, WindowFlags.dontAutoShow | WindowFlags.managesChildWindowFocus);
|
||||||
|
|
||||||
|
static if(UsingSimpledisplayX11) {
|
||||||
|
///+
|
||||||
|
// for input proxy
|
||||||
|
auto display = XDisplayConnection.get;
|
||||||
|
auto inputProxy = XCreateSimpleWindow(display, win.window, -1, -1, 1, 1, 0, 0, 0);
|
||||||
|
XSelectInput(display, inputProxy, EventMask.KeyPressMask | EventMask.KeyReleaseMask);
|
||||||
|
XMapWindow(display, inputProxy);
|
||||||
|
//import std.stdio; writefln("input proxy: 0x%0x", inputProxy);
|
||||||
|
this.inputProxy = new SimpleWindow(inputProxy);
|
||||||
|
|
||||||
|
XEvent lastEvent;
|
||||||
|
this.inputProxy.handleNativeEvent = (XEvent ev) {
|
||||||
|
lastEvent = ev;
|
||||||
|
return 1;
|
||||||
|
};
|
||||||
|
this.inputProxy.setEventHandlers(
|
||||||
|
(MouseEvent e) {
|
||||||
|
dispatchMouseEvent(e);
|
||||||
|
},
|
||||||
|
(KeyEvent e) {
|
||||||
|
//import std.stdio;
|
||||||
|
//writefln("%x %s", cast(uint) e.key, e.key);
|
||||||
|
if(dispatchKeyEvent(e)) {
|
||||||
|
// FIXME: i should trap error
|
||||||
|
if(auto nw = cast(NestedChildWindowWidget) focusedWidget) {
|
||||||
|
auto thing = nw.focusableWindow();
|
||||||
|
if(thing && thing.window) {
|
||||||
|
lastEvent.xkey.window = thing.window;
|
||||||
|
// import std.stdio; writeln("sending event ", lastEvent.xkey);
|
||||||
|
trapXErrors( {
|
||||||
|
XSendEvent(XDisplayConnection.get, thing.window, false, 0, &lastEvent);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
(dchar e) {
|
||||||
|
if(e == 13) e = 10; // hack?
|
||||||
|
if(e == 127) return; // linux sends this, windows doesn't. we don't want it.
|
||||||
|
dispatchCharEvent(e);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
// done
|
||||||
|
//+/
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
win.setRequestedInputFocus = &this.setRequestedInputFocus;
|
win.setRequestedInputFocus = &this.setRequestedInputFocus;
|
||||||
|
|
||||||
this(win);
|
this(win);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SimpleWindow inputProxy;
|
||||||
|
|
||||||
private SimpleWindow setRequestedInputFocus() {
|
private SimpleWindow setRequestedInputFocus() {
|
||||||
if(auto fw = cast(NestedChildWindowWidget) focusedWidget) {
|
return inputProxy;
|
||||||
// sdpyPrintDebugString("heaven");
|
|
||||||
return fw.focusableWindow;
|
|
||||||
}
|
|
||||||
return win;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// ditto
|
/// ditto
|
||||||
|
@ -8126,13 +8190,15 @@ class Window : Widget {
|
||||||
event.ctrlKey = (ev.modifierState & ModifierState.ctrl) ? true : false;
|
event.ctrlKey = (ev.modifierState & ModifierState.ctrl) ? true : false;
|
||||||
event.dispatch();
|
event.dispatch();
|
||||||
|
|
||||||
return true;
|
return !event.propagationStopped;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// returns true if propagation should continue into nested things.... prolly not a great thing to do.
|
||||||
bool dispatchCharEvent(dchar ch) {
|
bool dispatchCharEvent(dchar ch) {
|
||||||
if(focusedWidget) {
|
if(focusedWidget) {
|
||||||
auto event = new CharEvent(focusedWidget, ch);
|
auto event = new CharEvent(focusedWidget, ch);
|
||||||
event.dispatch();
|
event.dispatch();
|
||||||
|
return !event.propagationStopped;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -8249,7 +8315,7 @@ class Window : Widget {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true; // FIXME: the event default prevented?
|
||||||
}
|
}
|
||||||
|
|
||||||
/++
|
/++
|
||||||
|
@ -8295,18 +8361,29 @@ class Window : Widget {
|
||||||
}
|
}
|
||||||
|
|
||||||
static Widget getFirstFocusable(Widget start) {
|
static Widget getFirstFocusable(Widget start) {
|
||||||
if(start.tabStop && !start.hidden)
|
if(start is null)
|
||||||
return start;
|
return null;
|
||||||
|
|
||||||
if(!start.hidden)
|
foreach(widget; &start.focusableWidgets) {
|
||||||
foreach(child; start.children) {
|
return widget;
|
||||||
auto f = getFirstFocusable(child);
|
|
||||||
if(f !is null)
|
|
||||||
return f;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static Widget getLastFocusable(Widget start) {
|
||||||
|
if(start is null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
Widget last;
|
||||||
|
foreach(widget; &start.focusableWidgets) {
|
||||||
|
last = widget;
|
||||||
|
}
|
||||||
|
|
||||||
|
return last;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
mixin Emits!ClosingEvent;
|
mixin Emits!ClosingEvent;
|
||||||
mixin Emits!ClosedEvent;
|
mixin Emits!ClosedEvent;
|
||||||
}
|
}
|
||||||
|
@ -10409,9 +10486,10 @@ class Menu : Window {
|
||||||
if(!menuParent.parentWindow.win.closed) {
|
if(!menuParent.parentWindow.win.closed) {
|
||||||
if(auto maw = cast(MouseActivatedWidget) menuParent) {
|
if(auto maw = cast(MouseActivatedWidget) menuParent) {
|
||||||
maw.setDynamicState(DynamicState.depressed, false);
|
maw.setDynamicState(DynamicState.depressed, false);
|
||||||
|
maw.setDynamicState(DynamicState.hover, false);
|
||||||
maw.redraw();
|
maw.redraw();
|
||||||
}
|
}
|
||||||
menuParent.parentWindow.win.focus();
|
// menuParent.parentWindow.win.focus();
|
||||||
}
|
}
|
||||||
clickListener.disconnect();
|
clickListener.disconnect();
|
||||||
}
|
}
|
||||||
|
@ -11285,7 +11363,7 @@ class ImageBox : Widget {
|
||||||
if(this.parentWindow && this.parentWindow.win) {
|
if(this.parentWindow && this.parentWindow.win) {
|
||||||
if(sprite)
|
if(sprite)
|
||||||
sprite.dispose();
|
sprite.dispose();
|
||||||
sprite = new Sprite(this.parentWindow.win, Image.fromMemoryImage(image_));
|
sprite = new Sprite(this.parentWindow.win, Image.fromMemoryImage(image_, true));
|
||||||
}
|
}
|
||||||
redraw();
|
redraw();
|
||||||
}
|
}
|
||||||
|
@ -11319,7 +11397,7 @@ class ImageBox : Widget {
|
||||||
|
|
||||||
private void updateSprite() {
|
private void updateSprite() {
|
||||||
if(sprite is null && this.parentWindow && this.parentWindow.win) {
|
if(sprite is null && this.parentWindow && this.parentWindow.win) {
|
||||||
sprite = new Sprite(this.parentWindow.win, Image.fromMemoryImage(image_));
|
sprite = new Sprite(this.parentWindow.win, Image.fromMemoryImage(image_, true));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -14177,6 +14255,117 @@ interface ReflectableProperties {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private struct Stack(T) {
|
||||||
|
this(int maxSize) {
|
||||||
|
internalLength = 0;
|
||||||
|
arr = initialBuffer[];
|
||||||
|
}
|
||||||
|
|
||||||
|
///.
|
||||||
|
void push(T t) {
|
||||||
|
if(internalLength >= arr.length) {
|
||||||
|
auto oldarr = arr;
|
||||||
|
if(arr.length < 4096)
|
||||||
|
arr = new T[arr.length * 2];
|
||||||
|
else
|
||||||
|
arr = new T[arr.length + 4096];
|
||||||
|
arr[0 .. oldarr.length] = oldarr[];
|
||||||
|
}
|
||||||
|
|
||||||
|
arr[internalLength] = t;
|
||||||
|
internalLength++;
|
||||||
|
}
|
||||||
|
|
||||||
|
///.
|
||||||
|
T pop() {
|
||||||
|
assert(internalLength);
|
||||||
|
internalLength--;
|
||||||
|
return arr[internalLength];
|
||||||
|
}
|
||||||
|
|
||||||
|
///.
|
||||||
|
T peek() {
|
||||||
|
assert(internalLength);
|
||||||
|
return arr[internalLength - 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
///.
|
||||||
|
@property bool empty() {
|
||||||
|
return internalLength ? false : true;
|
||||||
|
}
|
||||||
|
|
||||||
|
///.
|
||||||
|
private T[] arr;
|
||||||
|
private size_t internalLength;
|
||||||
|
private T[64] initialBuffer;
|
||||||
|
// the static array is allocated with this object, so if we have a small stack (which we prolly do; dom trees usually aren't insanely deep),
|
||||||
|
// using this saves us a bunch of trips to the GC. In my last profiling, I got about a 50x improvement in the push()
|
||||||
|
// function thanks to this, and push() was actually one of the slowest individual functions in the code!
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This is the lazy range that walks the tree for you. It tries to go in the lexical order of the source: node, then children from first to last, each recursively.
|
||||||
|
private struct WidgetStream {
|
||||||
|
|
||||||
|
///.
|
||||||
|
@property Widget front() {
|
||||||
|
return current.widget;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Use Widget.tree instead.
|
||||||
|
this(Widget start) {
|
||||||
|
current.widget = start;
|
||||||
|
current.childPosition = -1;
|
||||||
|
isEmpty = false;
|
||||||
|
stack = typeof(stack)(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Handle it
|
||||||
|
handle its children
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
///.
|
||||||
|
void popFront() {
|
||||||
|
more:
|
||||||
|
if(isEmpty) return;
|
||||||
|
|
||||||
|
// FIXME: the profiler says this function is somewhat slow (noticeable because it can be called a lot of times)
|
||||||
|
|
||||||
|
current.childPosition++;
|
||||||
|
if(current.childPosition >= current.widget.children.length) {
|
||||||
|
if(stack.empty())
|
||||||
|
isEmpty = true;
|
||||||
|
else {
|
||||||
|
current = stack.pop();
|
||||||
|
goto more;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
stack.push(current);
|
||||||
|
current.widget = current.widget.children[current.childPosition];
|
||||||
|
current.childPosition = -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
///.
|
||||||
|
@property bool empty() {
|
||||||
|
return isEmpty;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
struct Current {
|
||||||
|
Widget widget;
|
||||||
|
int childPosition;
|
||||||
|
}
|
||||||
|
|
||||||
|
Current current;
|
||||||
|
|
||||||
|
Stack!(Current) stack;
|
||||||
|
|
||||||
|
bool isEmpty;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/+
|
/+
|
||||||
|
|
||||||
|
|
|
@ -360,7 +360,7 @@ class WebViewWidget_CEF : WebViewWidgetBase {
|
||||||
//semaphore = new Semaphore;
|
//semaphore = new Semaphore;
|
||||||
assert(CefApp.active);
|
assert(CefApp.active);
|
||||||
|
|
||||||
this(new MiniguiCefClient(openNewWindow), parent);
|
this(new MiniguiCefClient(openNewWindow), parent, false);
|
||||||
|
|
||||||
cef_window_info_t window_info;
|
cef_window_info_t window_info;
|
||||||
window_info.parent_window = containerWindow.nativeWindowHandle;
|
window_info.parent_window = containerWindow.nativeWindowHandle;
|
||||||
|
@ -398,7 +398,7 @@ class WebViewWidget_CEF : WebViewWidgetBase {
|
||||||
.destroy(this); // but this is ok to do some memory management cleanup
|
.destroy(this); // but this is ok to do some memory management cleanup
|
||||||
}
|
}
|
||||||
|
|
||||||
private this(MiniguiCefClient client, Widget parent) {
|
private this(MiniguiCefClient client, Widget parent, bool isDevTools) {
|
||||||
super(parent);
|
super(parent);
|
||||||
|
|
||||||
this.client = client;
|
this.client = client;
|
||||||
|
@ -407,22 +407,56 @@ class WebViewWidget_CEF : WebViewWidgetBase {
|
||||||
|
|
||||||
mapping[containerWindow.nativeWindowHandle()] = this;
|
mapping[containerWindow.nativeWindowHandle()] = this;
|
||||||
|
|
||||||
|
this.addEventListener(delegate(KeyDownEvent ke) {
|
||||||
this.parentWindow.addEventListener((FocusEvent fe) {
|
if(ke.key == Key.Tab)
|
||||||
if(!browserHandle) return;
|
ke.preventDefault();
|
||||||
//browserHandle.get_host.set_focus(true);
|
|
||||||
|
|
||||||
executeJavascript("if(window.__arsdPreviouslyFocusedNode) window.__arsdPreviouslyFocusedNode.focus(); window.dispatchEvent(new FocusEvent(\"focus\"));");
|
|
||||||
});
|
});
|
||||||
this.parentWindow.addEventListener((BlurEvent be) {
|
|
||||||
|
this.addEventListener((FocusEvent fe) {
|
||||||
if(!browserHandle) return;
|
if(!browserHandle) return;
|
||||||
|
|
||||||
executeJavascript("if(document.activeElement) { window.__arsdPreviouslyFocusedNode = document.activeElement; document.activeElement.blur(); } window.dispatchEvent(new FocusEvent(\"blur\"));");
|
XFocusChangeEvent ev;
|
||||||
|
ev.type = arsd.simpledisplay.EventType.FocusIn;
|
||||||
|
ev.display = XDisplayConnection.get;
|
||||||
|
ev.window = ozone;
|
||||||
|
ev.mode = NotifyModes.NotifyNormal;
|
||||||
|
ev.detail = NotifyDetail.NotifyVirtual;
|
||||||
|
|
||||||
|
trapXErrors( {
|
||||||
|
XSendEvent(XDisplayConnection.get, ozone, false, 0, cast(XEvent*) &ev);
|
||||||
|
});
|
||||||
|
|
||||||
|
// this also works if the message is buggy and it avoids weirdness from raising window etc
|
||||||
|
//executeJavascript("if(window.__arsdPreviouslyFocusedNode) window.__arsdPreviouslyFocusedNode.focus(); window.dispatchEvent(new FocusEvent(\"focus\"));");
|
||||||
|
});
|
||||||
|
this.addEventListener((BlurEvent be) {
|
||||||
|
if(!browserHandle) return;
|
||||||
|
|
||||||
|
XFocusChangeEvent ev;
|
||||||
|
ev.type = arsd.simpledisplay.EventType.FocusOut;
|
||||||
|
ev.display = XDisplayConnection.get;
|
||||||
|
ev.window = ozone;
|
||||||
|
ev.mode = NotifyModes.NotifyNormal;
|
||||||
|
ev.detail = NotifyDetail.NotifyNonlinearVirtual;
|
||||||
|
|
||||||
|
trapXErrors( {
|
||||||
|
XSendEvent(XDisplayConnection.get, ozone, false, 0, cast(XEvent*) &ev);
|
||||||
|
});
|
||||||
|
|
||||||
|
//executeJavascript("if(document.activeElement) { window.__arsdPreviouslyFocusedNode = document.activeElement; document.activeElement.blur(); } window.dispatchEvent(new FocusEvent(\"blur\"));");
|
||||||
});
|
});
|
||||||
|
|
||||||
bool closeAttempted = false;
|
bool closeAttempted = false;
|
||||||
|
|
||||||
|
if(isDevTools)
|
||||||
this.parentWindow.addEventListener((scope ClosingEvent ce) {
|
this.parentWindow.addEventListener((scope ClosingEvent ce) {
|
||||||
|
this.parentWindow.hide();
|
||||||
|
ce.preventDefault();
|
||||||
|
});
|
||||||
|
else
|
||||||
|
this.parentWindow.addEventListener((scope ClosingEvent ce) {
|
||||||
|
if(devTools)
|
||||||
|
devTools.close();
|
||||||
if(!closeAttempted && browserHandle) {
|
if(!closeAttempted && browserHandle) {
|
||||||
browserHandle.get_host.close_browser(true);
|
browserHandle.get_host.close_browser(true);
|
||||||
ce.preventDefault();
|
ce.preventDefault();
|
||||||
|
@ -450,11 +484,67 @@ class WebViewWidget_CEF : WebViewWidgetBase {
|
||||||
}
|
}
|
||||||
|
|
||||||
private NativeWindowHandle browserWindow;
|
private NativeWindowHandle browserWindow;
|
||||||
|
private NativeWindowHandle ozone;
|
||||||
private RC!cef_browser_t browserHandle;
|
private RC!cef_browser_t browserHandle;
|
||||||
|
|
||||||
private static WebViewWidget[NativeWindowHandle] mapping;
|
private static WebViewWidget[NativeWindowHandle] mapping;
|
||||||
private static WebViewWidget[NativeWindowHandle] browserMapping;
|
private static WebViewWidget[NativeWindowHandle] browserMapping;
|
||||||
|
|
||||||
|
private {
|
||||||
|
int findingIdent;
|
||||||
|
string findingText;
|
||||||
|
bool findingCase;
|
||||||
|
}
|
||||||
|
|
||||||
|
// might not be stable, webview does this fully integrated
|
||||||
|
void findText(string text, bool forward = true, bool matchCase = false, bool findNext = false) {
|
||||||
|
if(browserHandle) {
|
||||||
|
auto host = browserHandle.get_host();
|
||||||
|
|
||||||
|
static ident = 0;
|
||||||
|
auto txt = cef_string_t(text);
|
||||||
|
host.find(++ident, &txt, forward, matchCase, findNext);
|
||||||
|
|
||||||
|
findingIdent = ident;
|
||||||
|
findingText = text;
|
||||||
|
findingCase = matchCase;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ditto
|
||||||
|
void findPrevious() {
|
||||||
|
if(findingIdent == 0)
|
||||||
|
return;
|
||||||
|
if(!browserHandle)
|
||||||
|
return;
|
||||||
|
auto host = browserHandle.get_host();
|
||||||
|
auto txt = cef_string_t(findingText);
|
||||||
|
host.find(findingIdent, &txt, 0, findingCase, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ditto
|
||||||
|
void findNext() {
|
||||||
|
if(findingIdent == 0)
|
||||||
|
return;
|
||||||
|
if(!browserHandle)
|
||||||
|
return;
|
||||||
|
auto host = browserHandle.get_host();
|
||||||
|
auto txt = cef_string_t(findingText);
|
||||||
|
host.find(findingIdent, &txt, 1, findingCase, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ditto
|
||||||
|
void stopFind() {
|
||||||
|
if(findingIdent == 0)
|
||||||
|
return;
|
||||||
|
if(!browserHandle)
|
||||||
|
return;
|
||||||
|
auto host = browserHandle.get_host();
|
||||||
|
host.stop_finding(1);
|
||||||
|
|
||||||
|
findingIdent = 0;
|
||||||
|
}
|
||||||
|
|
||||||
override void refresh() { if(browserHandle) browserHandle.reload(); }
|
override void refresh() { if(browserHandle) browserHandle.reload(); }
|
||||||
override void back() { if(browserHandle) browserHandle.go_back(); }
|
override void back() { if(browserHandle) browserHandle.go_back(); }
|
||||||
override void forward() { if(browserHandle) browserHandle.go_forward(); }
|
override void forward() { if(browserHandle) browserHandle.go_forward(); }
|
||||||
|
@ -475,9 +565,37 @@ class WebViewWidget_CEF : WebViewWidgetBase {
|
||||||
browserHandle.get_main_frame.execute_java_script(&c, &u, line);
|
browserHandle.get_main_frame.execute_java_script(&c, &u, line);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Window devTools;
|
||||||
override void showDevTools() {
|
override void showDevTools() {
|
||||||
if(!browserHandle) return;
|
if(!browserHandle) return;
|
||||||
browserHandle.get_host.show_dev_tools(null /* window info */, client.passable, null /* settings */, null /* inspect element at coordinates */);
|
|
||||||
|
if(devTools is null) {
|
||||||
|
auto host = browserHandle.get_host;
|
||||||
|
|
||||||
|
if(host.has_dev_tools()) {
|
||||||
|
host.close_dev_tools();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
cef_window_info_t windowinfo;
|
||||||
|
version(linux) {
|
||||||
|
auto sw = new Window("DevTools");
|
||||||
|
//sw.win.beingOpenKeepsAppOpen = false;
|
||||||
|
devTools = sw;
|
||||||
|
|
||||||
|
auto wv = new WebViewWidget_CEF(client, sw, true);
|
||||||
|
|
||||||
|
sw.show();
|
||||||
|
|
||||||
|
windowinfo.parent_window = wv.containerWindow.nativeWindowHandle;
|
||||||
|
}
|
||||||
|
host.show_dev_tools(&windowinfo, client.passable, null /* settings */, null /* inspect element at coordinates */);
|
||||||
|
} else {
|
||||||
|
if(devTools.hidden)
|
||||||
|
devTools.show();
|
||||||
|
else
|
||||||
|
devTools.hide();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// FYI the cef browser host also allows things like custom spelling dictionaries and getting navigation entries.
|
// FYI the cef browser host also allows things like custom spelling dictionaries and getting navigation entries.
|
||||||
|
@ -534,7 +652,7 @@ version(cef) {
|
||||||
cef_dictionary_value_t** extra_info,
|
cef_dictionary_value_t** extra_info,
|
||||||
int* no_javascript_access
|
int* no_javascript_access
|
||||||
) {
|
) {
|
||||||
|
sdpyPrintDebugString("on_before_popup");
|
||||||
if(this.client.openNewWindow is null)
|
if(this.client.openNewWindow is null)
|
||||||
return 1; // new windows disabled
|
return 1; // new windows disabled
|
||||||
|
|
||||||
|
@ -548,10 +666,10 @@ version(cef) {
|
||||||
|
|
||||||
runInGuiThread({
|
runInGuiThread({
|
||||||
ret = 1;
|
ret = 1;
|
||||||
scope WebViewWidget delegate(Widget, BrowserSettings) o = (parent, passed_settings) {
|
scope WebViewWidget delegate(Widget, BrowserSettings) accept = (parent, passed_settings) {
|
||||||
ret = 0;
|
ret = 0;
|
||||||
if(parent !is null) {
|
if(parent !is null) {
|
||||||
auto widget = new WebViewWidget_CEF(this.client, parent);
|
auto widget = new WebViewWidget_CEF(this.client, parent, false);
|
||||||
(*windowInfo).parent_window = widget.containerWindow.nativeWindowHandle;
|
(*windowInfo).parent_window = widget.containerWindow.nativeWindowHandle;
|
||||||
|
|
||||||
passed_settings.set(browser_settings);
|
passed_settings.set(browser_settings);
|
||||||
|
@ -560,7 +678,7 @@ version(cef) {
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
this.client.openNewWindow(OpenNewWindowParams(target_url.toGC, o));
|
this.client.openNewWindow(OpenNewWindowParams(target_url.toGC, accept));
|
||||||
return;
|
return;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -589,10 +707,13 @@ version(cef) {
|
||||||
import arsd.simpledisplay : Window;
|
import arsd.simpledisplay : Window;
|
||||||
Window root;
|
Window root;
|
||||||
Window parent;
|
Window parent;
|
||||||
|
Window ozone;
|
||||||
uint c = 0;
|
uint c = 0;
|
||||||
auto display = XDisplayConnection.get;
|
auto display = XDisplayConnection.get;
|
||||||
Window* children;
|
Window* children;
|
||||||
XQueryTree(display, handle, &root, &parent, &children, &c);
|
XQueryTree(display, handle, &root, &parent, &children, &c);
|
||||||
|
if(c == 1)
|
||||||
|
ozone = children[0];
|
||||||
XFree(children);
|
XFree(children);
|
||||||
} else static assert(0);
|
} else static assert(0);
|
||||||
|
|
||||||
|
@ -600,8 +721,9 @@ version(cef) {
|
||||||
auto wv = *wvp;
|
auto wv = *wvp;
|
||||||
wv.browserWindow = handle;
|
wv.browserWindow = handle;
|
||||||
wv.browserHandle = RC!cef_browser_t(ptr);
|
wv.browserHandle = RC!cef_browser_t(ptr);
|
||||||
|
wv.ozone = ozone ? ozone : handle;
|
||||||
|
|
||||||
wv.browserWindowWrapped = new SimpleWindow(wv.browserWindow);
|
wv.browserWindowWrapped = new SimpleWindow(wv.ozone);
|
||||||
/+
|
/+
|
||||||
XSelectInput(XDisplayConnection.get, handle, EventMask.FocusChangeMask);
|
XSelectInput(XDisplayConnection.get, handle, EventMask.FocusChangeMask);
|
||||||
|
|
||||||
|
@ -831,15 +953,70 @@ version(cef) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class MiniguiRequestHandler : CEF!cef_request_handler_t {
|
||||||
|
override int on_before_browse(RC!(cef_browser_t), RC!(cef_frame_t), RC!(cef_request_t), int, int) nothrow {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
override int on_open_urlfrom_tab(RC!(cef_browser_t), RC!(cef_frame_t), const(cef_string_utf16_t)*, cef_window_open_disposition_t, int) nothrow {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
override cef_resource_request_handler_t* get_resource_request_handler(RC!(cef_browser_t), RC!(cef_frame_t), RC!(cef_request_t), int, int, const(cef_string_utf16_t)*, int*) nothrow {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
override int get_auth_credentials(RC!(cef_browser_t), const(cef_string_utf16_t)*, int, const(cef_string_utf16_t)*, int, const(cef_string_utf16_t)*, const(cef_string_utf16_t)*, RC!(cef_auth_callback_t)) nothrow {
|
||||||
|
// this is for http basic auth popup.....
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
override int on_quota_request(RC!(cef_browser_t), const(cef_string_utf16_t)*, long, RC!(cef_callback_t)) nothrow {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
override int on_certificate_error(RC!(cef_browser_t), cef_errorcode_t, const(cef_string_utf16_t)*, RC!(cef_sslinfo_t), RC!(cef_callback_t)) nothrow {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
override int on_select_client_certificate(RC!(cef_browser_t), int, const(cef_string_utf16_t)*, int, ulong, cef_x509certificate_t**, RC!(cef_select_client_certificate_callback_t)) nothrow {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
override void on_plugin_crashed(RC!(cef_browser_t), const(cef_string_utf16_t)*) nothrow {
|
||||||
|
|
||||||
|
}
|
||||||
|
override void on_render_view_ready(RC!(cef_browser_t) p) nothrow {
|
||||||
|
|
||||||
|
}
|
||||||
|
override void on_render_process_terminated(RC!(cef_browser_t), cef_termination_status_t) nothrow {
|
||||||
|
|
||||||
|
}
|
||||||
|
override void on_document_available_in_main_frame(RC!(cef_browser_t) browser) nothrow {
|
||||||
|
browser.runOnWebView(delegate(wv) {
|
||||||
|
wv.executeJavascript("console.log('here');");
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class MiniguiFocusHandler : CEF!cef_focus_handler_t {
|
class MiniguiFocusHandler : CEF!cef_focus_handler_t {
|
||||||
override void on_take_focus(RC!(cef_browser_t) browser, int next) nothrow {
|
override void on_take_focus(RC!(cef_browser_t) browser, int next) nothrow {
|
||||||
// sdpyPrintDebugString("take");
|
browser.runOnWebView(delegate(wv) {
|
||||||
|
Widget f;
|
||||||
|
if(next) {
|
||||||
|
f = Window.getFirstFocusable(wv.parentWindow);
|
||||||
|
} else {
|
||||||
|
foreach(w; &wv.parentWindow.focusableWidgets) {
|
||||||
|
if(w is wv)
|
||||||
|
break;
|
||||||
|
f = w;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(f)
|
||||||
|
f.focus();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
override int on_set_focus(RC!(cef_browser_t) browser, cef_focus_source_t source) nothrow {
|
override int on_set_focus(RC!(cef_browser_t) browser, cef_focus_source_t source) nothrow {
|
||||||
//browser.runOnWebView((ev) {
|
/+
|
||||||
|
browser.runOnWebView((ev) {
|
||||||
|
ev.focus(); // even this can steal focus from other parts of my application!
|
||||||
|
});
|
||||||
|
+/
|
||||||
//sdpyPrintDebugString("setting");
|
//sdpyPrintDebugString("setting");
|
||||||
//ev.parentWindow.focusedWidget = ev;
|
|
||||||
//});
|
|
||||||
|
|
||||||
return 1; // otherwise, cancel because this bullshit tends to steal focus from other applications and i never, ever, ever want that to happen.
|
return 1; // otherwise, cancel because this bullshit tends to steal focus from other applications and i never, ever, ever want that to happen.
|
||||||
// seems to happen because of race condition in it getting a focus event and then stealing the focus from the parent
|
// seems to happen because of race condition in it getting a focus event and then stealing the focus from the parent
|
||||||
|
@ -848,7 +1025,12 @@ version(cef) {
|
||||||
// it also breaks its own pop up menus and drop down boxes to allow this! wtf
|
// it also breaks its own pop up menus and drop down boxes to allow this! wtf
|
||||||
}
|
}
|
||||||
override void on_got_focus(RC!(cef_browser_t) browser) nothrow {
|
override void on_got_focus(RC!(cef_browser_t) browser) nothrow {
|
||||||
// sdpyPrintDebugString("got");
|
browser.runOnWebView((ev) {
|
||||||
|
// this sometimes steals from the app too but it is relatively acceptable
|
||||||
|
// steals when i mouse in from the side of the window quickly, but still
|
||||||
|
// i want the minigui state to match so i'll allow it
|
||||||
|
ev.focus();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -863,6 +1045,7 @@ version(cef) {
|
||||||
MiniguiDownloadHandler downloadHandler;
|
MiniguiDownloadHandler downloadHandler;
|
||||||
MiniguiKeyboardHandler keyboardHandler;
|
MiniguiKeyboardHandler keyboardHandler;
|
||||||
MiniguiFocusHandler focusHandler;
|
MiniguiFocusHandler focusHandler;
|
||||||
|
MiniguiRequestHandler requestHandler;
|
||||||
this(void delegate(scope OpenNewWindowParams) openNewWindow) {
|
this(void delegate(scope OpenNewWindowParams) openNewWindow) {
|
||||||
this.openNewWindow = openNewWindow;
|
this.openNewWindow = openNewWindow;
|
||||||
lsh = new MiniguiCefLifeSpanHandler(this);
|
lsh = new MiniguiCefLifeSpanHandler(this);
|
||||||
|
@ -872,6 +1055,7 @@ version(cef) {
|
||||||
downloadHandler = new MiniguiDownloadHandler();
|
downloadHandler = new MiniguiDownloadHandler();
|
||||||
keyboardHandler = new MiniguiKeyboardHandler();
|
keyboardHandler = new MiniguiKeyboardHandler();
|
||||||
focusHandler = new MiniguiFocusHandler();
|
focusHandler = new MiniguiFocusHandler();
|
||||||
|
requestHandler = new MiniguiRequestHandler();
|
||||||
}
|
}
|
||||||
|
|
||||||
override cef_audio_handler_t* get_audio_handler() {
|
override cef_audio_handler_t* get_audio_handler() {
|
||||||
|
@ -915,10 +1099,12 @@ version(cef) {
|
||||||
override cef_render_handler_t* get_render_handler() {
|
override cef_render_handler_t* get_render_handler() {
|
||||||
// this thing might work for an off-screen thing
|
// this thing might work for an off-screen thing
|
||||||
// like to an image or to a video stream maybe
|
// like to an image or to a video stream maybe
|
||||||
|
//
|
||||||
|
// might be useful to have it render here then send it over too for remote X sharing a process
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
override cef_request_handler_t* get_request_handler() {
|
override cef_request_handler_t* get_request_handler() {
|
||||||
return null;
|
return requestHandler.returnable;
|
||||||
}
|
}
|
||||||
override int on_process_message_received(RC!cef_browser_t, RC!cef_frame_t, cef_process_id_t, RC!cef_process_message_t) {
|
override int on_process_message_received(RC!cef_browser_t, RC!cef_frame_t, cef_process_id_t, RC!cef_process_message_t) {
|
||||||
return 0; // return 1 if you can actually handle the message
|
return 0; // return 1 if you can actually handle the message
|
||||||
|
|
14
postgres.d
14
postgres.d
|
@ -184,6 +184,19 @@ class PostgresResult : ResultSet {
|
||||||
return row;
|
return row;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int affectedRows() {
|
||||||
|
auto g = PQcmdTuples(res);
|
||||||
|
if(g is null)
|
||||||
|
return 0;
|
||||||
|
int num;
|
||||||
|
while(*g) {
|
||||||
|
num *= 10;
|
||||||
|
num += *g - '0';
|
||||||
|
g++;
|
||||||
|
}
|
||||||
|
return num;
|
||||||
|
}
|
||||||
|
|
||||||
void popFront() {
|
void popFront() {
|
||||||
position++;
|
position++;
|
||||||
if(position < numRows)
|
if(position < numRows)
|
||||||
|
@ -308,6 +321,7 @@ extern(C) {
|
||||||
int row_number,
|
int row_number,
|
||||||
int column_number);
|
int column_number);
|
||||||
|
|
||||||
|
char* PQcmdTuples(PGresult *res);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -12296,6 +12296,13 @@ version(X11) {
|
||||||
if(width == 0 || height == 0) {
|
if(width == 0 || height == 0) {
|
||||||
XSetClipMask(display, gc, None);
|
XSetClipMask(display, gc, None);
|
||||||
|
|
||||||
|
if(xrenderPicturePainter) {
|
||||||
|
|
||||||
|
XRectangle[1] rects;
|
||||||
|
rects[0] = XRectangle(short.min, short.min, short.max, short.max);
|
||||||
|
XRenderSetPictureClipRectangles(display, xrenderPicturePainter, 0, 0, rects.ptr, cast(int) rects.length);
|
||||||
|
}
|
||||||
|
|
||||||
version(with_xft) {
|
version(with_xft) {
|
||||||
if(xftFont is null || xftDraw is null)
|
if(xftFont is null || xftDraw is null)
|
||||||
return;
|
return;
|
||||||
|
@ -12306,6 +12313,9 @@ version(X11) {
|
||||||
rects[0] = XRectangle(cast(short)(x), cast(short)(y), cast(short) width, cast(short) height);
|
rects[0] = XRectangle(cast(short)(x), cast(short)(y), cast(short) width, cast(short) height);
|
||||||
XSetClipRectangles(XDisplayConnection.get, gc, 0, 0, rects.ptr, 1, 0);
|
XSetClipRectangles(XDisplayConnection.get, gc, 0, 0, rects.ptr, 1, 0);
|
||||||
|
|
||||||
|
if(xrenderPicturePainter)
|
||||||
|
XRenderSetPictureClipRectangles(display, xrenderPicturePainter, 0, 0, rects.ptr, cast(int) rects.length);
|
||||||
|
|
||||||
version(with_xft) {
|
version(with_xft) {
|
||||||
if(xftFont is null || xftDraw is null)
|
if(xftFont is null || xftDraw is null)
|
||||||
return;
|
return;
|
||||||
|
@ -12572,6 +12582,12 @@ version(X11) {
|
||||||
XRenderPictureAttributes attrs;
|
XRenderPictureAttributes attrs;
|
||||||
// FIXME: I can prolly reuse this as long as the pixmap itself is valid.
|
// FIXME: I can prolly reuse this as long as the pixmap itself is valid.
|
||||||
xrenderPicturePainter = XRenderCreatePicture(display, d, Sprite.RGB24, 0, &attrs);
|
xrenderPicturePainter = XRenderCreatePicture(display, d, Sprite.RGB24, 0, &attrs);
|
||||||
|
|
||||||
|
// need to initialize the clip
|
||||||
|
XRectangle[1] rects;
|
||||||
|
rects[0] = XRectangle(cast(short)(_clipRectangle.left), cast(short)(_clipRectangle.top), cast(short) _clipRectangle.width, cast(short) _clipRectangle.height);
|
||||||
|
|
||||||
|
XRenderSetPictureClipRectangles(display, xrenderPicturePainter, 0, 0, rects.ptr, cast(int) rects.length);
|
||||||
}
|
}
|
||||||
|
|
||||||
XRenderComposite(
|
XRenderComposite(
|
||||||
|
@ -13613,6 +13629,35 @@ mixin DynamicLoad!(XRandr, "Xrandr", 2, XRandrLibrarySuccessfullyLoaded) XRandrL
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/++
|
||||||
|
Platform-specific for X11. Traps errors for the duration of `dg`. Avoid calling this from inside a call to this.
|
||||||
|
|
||||||
|
Please note that it returns
|
||||||
|
+/
|
||||||
|
XErrorEvent[] trapXErrors(scope void delegate() dg) {
|
||||||
|
|
||||||
|
static XErrorEvent[] errorBuffer;
|
||||||
|
|
||||||
|
static extern(C) int handler (Display* dpy, XErrorEvent* evt) nothrow {
|
||||||
|
errorBuffer ~= *evt;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto savedErrorHandler = XSetErrorHandler(&handler);
|
||||||
|
|
||||||
|
try {
|
||||||
|
dg();
|
||||||
|
} finally {
|
||||||
|
XSync(XDisplayConnection.get, 0/*False*/);
|
||||||
|
XSetErrorHandler(savedErrorHandler);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto bfr = errorBuffer;
|
||||||
|
errorBuffer = null;
|
||||||
|
|
||||||
|
return bfr;
|
||||||
|
}
|
||||||
|
|
||||||
/// Platform-specific for X11. A singleton class (well, all its methods are actually static... so more like a namespace) wrapping a `Display*`.
|
/// Platform-specific for X11. A singleton class (well, all its methods are actually static... so more like a namespace) wrapping a `Display*`.
|
||||||
class XDisplayConnection {
|
class XDisplayConnection {
|
||||||
private __gshared Display* display;
|
private __gshared Display* display;
|
||||||
|
@ -16986,6 +17031,9 @@ extern(C) {
|
||||||
extern(C) alias XIOErrorHandler = int function (Display* display);
|
extern(C) alias XIOErrorHandler = int function (Display* display);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extern(C) nothrow
|
||||||
|
alias XErrorHandler = int function(Display*, XErrorEvent*);
|
||||||
|
|
||||||
extern(C) nothrow @nogc {
|
extern(C) nothrow @nogc {
|
||||||
struct Screen{
|
struct Screen{
|
||||||
XExtData *ext_data; /* hook for extension to hang data */
|
XExtData *ext_data; /* hook for extension to hang data */
|
||||||
|
@ -17190,8 +17238,6 @@ struct Visual
|
||||||
byte pad;
|
byte pad;
|
||||||
}
|
}
|
||||||
|
|
||||||
alias XErrorHandler = int function(Display*, XErrorEvent*);
|
|
||||||
|
|
||||||
struct XRectangle {
|
struct XRectangle {
|
||||||
short x;
|
short x;
|
||||||
short y;
|
short y;
|
||||||
|
|
31
terminal.d
31
terminal.d
|
@ -4875,7 +4875,7 @@ class LineGetter {
|
||||||
it to `write` or `writeln`, you should return `["write", "writeln"]`.
|
it to `write` or `writeln`, you should return `["write", "writeln"]`.
|
||||||
|
|
||||||
If you offer different tab complete in different places, you still
|
If you offer different tab complete in different places, you still
|
||||||
need to return the whole string. For example, a file competition of
|
need to return the whole string. For example, a file completion of
|
||||||
a second argument, when the user writes `terminal.d term<tab>` and you
|
a second argument, when the user writes `terminal.d term<tab>` and you
|
||||||
want it to complete to an additional `terminal.d`, you should return
|
want it to complete to an additional `terminal.d`, you should return
|
||||||
`["terminal.d terminal.d"]`; in other words, `candidate ~ completion`
|
`["terminal.d terminal.d"]`; in other words, `candidate ~ completion`
|
||||||
|
@ -6409,6 +6409,25 @@ class LineGetter {
|
||||||
maybePositionCursor();
|
maybePositionCursor();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool isSearchingHistory() {
|
||||||
|
return supplementalGetter !is null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/++
|
||||||
|
Cancels an in-progress history search immediately, discarding the result, returning
|
||||||
|
to the normal prompt.
|
||||||
|
|
||||||
|
If the user is not currently searching history (see [isSearchingHistory]), this
|
||||||
|
function does nothing.
|
||||||
|
+/
|
||||||
|
void cancelHistorySearch() {
|
||||||
|
if(isSearchingHistory()) {
|
||||||
|
lastDrawLength = maximumDrawWidth - 1;
|
||||||
|
supplementalGetter = null;
|
||||||
|
redraw();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/++
|
/++
|
||||||
for integrating into another event loop
|
for integrating into another event loop
|
||||||
you can pass individual events to this and
|
you can pass individual events to this and
|
||||||
|
@ -7468,6 +7487,16 @@ struct ScrollbackBuffer {
|
||||||
scrollbackPosition_++;
|
scrollbackPosition_++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/++
|
||||||
|
Adds a line by components without affecting scrollback.
|
||||||
|
|
||||||
|
History:
|
||||||
|
Added May 17, 2022
|
||||||
|
+/
|
||||||
|
void addLine(LineComponent[] components...) {
|
||||||
|
lines ~= Line(components.dup);
|
||||||
|
}
|
||||||
|
|
||||||
/++
|
/++
|
||||||
Scrolling controls.
|
Scrolling controls.
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue