tons of stuff

This commit is contained in:
Adam D. Ruppe 2020-03-30 12:37:44 -04:00
parent c5cec0c805
commit 7873768fd1
2 changed files with 226 additions and 50 deletions

View File

@ -4359,9 +4359,13 @@ class MainWindow : Window {
} }
void setMenuAndToolbarFromAnnotatedCode_internal(T)(ref T t) { void setMenuAndToolbarFromAnnotatedCode_internal(T)(ref T t) {
Action[] toolbarActions; Action[] toolbarActions;
auto menuBar = new MenuBar(); auto menuBar = this.menuBar is null ? new MenuBar() : this.menuBar;
Menu[string] mcs; Menu[string] mcs;
foreach(menu; menuBar.subMenus) {
mcs[menu.label] = menu;
}
void delegate() triggering; void delegate() triggering;
foreach(memberName; __traits(derivedMembers, T)) { foreach(memberName; __traits(derivedMembers, T)) {
@ -4482,6 +4486,12 @@ class MainWindow : Window {
MenuBar menuBar() { return _menu; } MenuBar menuBar() { return _menu; }
/// ///
MenuBar menuBar(MenuBar m) { MenuBar menuBar(MenuBar m) {
if(m is _menu) {
version(custom_widgets)
recomputeChildLayout();
return m;
}
if(_menu !is null) { if(_menu !is null) {
// make sure it is sanely removed // make sure it is sanely removed
// FIXME // FIXME
@ -4758,6 +4768,7 @@ class ToolButton : Button {
/// ///
class MenuBar : Widget { class MenuBar : Widget {
MenuItem[] items; MenuItem[] items;
Menu[] subMenus;
version(win32_widgets) { version(win32_widgets) {
HMENU handle; HMENU handle;
@ -4796,7 +4807,10 @@ class MenuBar : Widget {
/// ///
Menu addItem(Menu item) { Menu addItem(Menu item) {
auto mbItem = new MenuItem(item.label, this.parentWindow);
subMenus ~= item;
auto mbItem = new MenuItem(item.label, null);// this.parentWindow); // I'ma add the child down below so hopefully this isn't too insane
addChild(mbItem); addChild(mbItem);
items ~= mbItem; items ~= mbItem;
@ -5335,8 +5349,9 @@ class MenuItem : MouseActivatedWidget {
override int minHeight() { return Window.lineHeight + 4; } override int minHeight() { return Window.lineHeight + 4; }
override int minWidth() { return Window.lineHeight * cast(int) label.length + 8; } override int minWidth() { return Window.lineHeight * cast(int) label.length + 8; }
override int maxWidth() { override int maxWidth() {
if(cast(MenuBar) parent) if(cast(MenuBar) parent) {
return Window.lineHeight / 2 * cast(int) label.length + 8; return Window.lineHeight / 2 * cast(int) label.length + 8;
}
return int.max; return int.max;
} }
/// ///

View File

@ -138,11 +138,36 @@ version(demos) unittest {
// FIXME: http://msdn.microsoft.com/en-us/library/windows/desktop/ms686016%28v=vs.85%29.aspx // FIXME: http://msdn.microsoft.com/en-us/library/windows/desktop/ms686016%28v=vs.85%29.aspx
/++
A function the sigint handler will call (if overridden - which is the
case when [RealTimeConsoleInput] is active on Posix or if you compile with
`TerminalDirectToEmulator` version on any platform at this time) in addition
to the library's default handling, which is to set a flag for the event loop
to inform you.
Remember, this is called from a signal handler and/or from a separate thread,
so you are not allowed to do much with it and need care when setting TLS variables.
I suggest you only set a `__gshared bool` flag as many other operations will risk
undefined behavior.
$(WARNING
This function is never called on the default Windows console
configuration in the current implementation. You can use
`-version=TerminalDirectToEmulator` to guarantee it is called there
too by causing the library to pop up a gui window for your application.
)
History:
Added March 30, 2020. Included in release v7.1.0.
+/
__gshared void delegate() nothrow @nogc sigIntExtension;
version(TerminalDirectToEmulator) { version(TerminalDirectToEmulator) {
__gshared bool windowSizeChanged = false; version=WithEncapsulatedSignals;
__gshared bool interrupted = false; /// you might periodically check this in a long operation and abort if it is set. Remember it is volatile. It is also sent through the input event loop via RealTimeConsoleInput
__gshared bool hangedUp = false; /// similar to interrupted.
version=WithSignals;
} else version(Posix) { } else version(Posix) {
enum SIGWINCH = 28; enum SIGWINCH = 28;
__gshared bool windowSizeChanged = false; __gshared bool windowSizeChanged = false;
@ -172,6 +197,9 @@ version(TerminalDirectToEmulator) {
send(SignalFired()); send(SignalFired());
catch(Exception) {} catch(Exception) {}
} }
if(sigIntExtension)
sigIntExtension();
} }
extern(C) extern(C)
void hangupSignalHandler(int sigNumber) nothrow { void hangupSignalHandler(int sigNumber) nothrow {
@ -183,7 +211,6 @@ version(TerminalDirectToEmulator) {
catch(Exception) {} catch(Exception) {}
} }
} }
} }
// parts of this were taken from Robik's ConsoleD // parts of this were taken from Robik's ConsoleD
@ -475,6 +502,12 @@ struct Terminal {
@disable this(this); @disable this(this);
private ConsoleOutputType type; private ConsoleOutputType type;
version(TerminalDirectToEmulator) {
private bool windowSizeChanged = false;
private bool interrupted = false; /// you might periodically check this in a long operation and abort if it is set. Remember it is volatile. It is also sent through the input event loop via RealTimeConsoleInput
private bool hangedUp = false; /// similar to interrupted.
}
private TerminalCursor currentCursor_; private TerminalCursor currentCursor_;
version(Windows) private CONSOLE_CURSOR_INFO originalCursorInfo; version(Windows) private CONSOLE_CURSOR_INFO originalCursorInfo;
@ -1029,6 +1062,20 @@ struct Terminal {
version(TerminalDirectToEmulator) { version(TerminalDirectToEmulator) {
TerminalEmulatorWidget tew; TerminalEmulatorWidget tew;
private __gshared Window mainWindow;
import core.thread;
version(Posix)
ThreadID threadId;
else version(Windows)
HANDLE threadId;
private __gshared Thread guiThread;
private static class NewTerminalEvent {
Terminal* t;
this(Terminal* t) {
this.t = t;
}
}
} }
version(TerminalDirectToEmulator) version(TerminalDirectToEmulator)
@ -1043,17 +1090,39 @@ struct Terminal {
return; return;
} }
tcaps = uint.max; // all capabilities tcaps = uint.max; // all capabilities
import core.thread; import core.thread;
auto thread = new Thread( { version(Posix)
auto window = new TerminalEmulatorWindow(&this); threadId = Thread.getThis.id;
tew = window.tew; else version(Windows)
window.loop(); threadId = GetCurrentThread();
});
if(guiThread is null) {
guiThread = new Thread( {
auto window = new TerminalEmulatorWindow(&this);
mainWindow = window;
mainWindow.win.addEventListener((NewTerminalEvent t) {
auto nw = new TerminalEmulatorWindow(t.t);
t.t.tew = nw.tew;
t.t = null;
nw.show();
});
tew = window.tew;
window.loop();
});
guiThread.start();
guiThread.priority = Thread.PRIORITY_MAX; // gui thread needs responsiveness
} else {
// FIXME: 64 bit builds on linux segfault with multiple terminals
// so that isn't really supported as of yet.
while(cast(shared) mainWindow is null) {
import core.thread;
Thread.sleep(5.msecs);
}
mainWindow.win.postEvent(new NewTerminalEvent(&this));
}
thread.start();
// need to wait until it is properly initialized // need to wait until it is properly initialized
while(cast(shared) tew is null) { while(cast(shared) tew is null) {
import core.thread; import core.thread;
@ -1202,6 +1271,7 @@ struct Terminal {
version(TerminalDirectToEmulator) { version(TerminalDirectToEmulator) {
writeln("\n\n<exited>"); writeln("\n\n<exited>");
setTitle(tew.terminalEmulator.currentTitle ~ " <exited>"); setTitle(tew.terminalEmulator.currentTitle ~ " <exited>");
tew.term = null;
} else } else
if(terminalInFamily("xterm", "rxvt", "screen", "tmux")) { 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
@ -1229,7 +1299,6 @@ struct Terminal {
SetConsoleActiveScreenBuffer(stdo); SetConsoleActiveScreenBuffer(stdo);
if(hConsole !is stdo) if(hConsole !is stdo)
CloseHandle(hConsole); CloseHandle(hConsole);
} }
} }
@ -2250,6 +2319,9 @@ struct RealTimeConsoleInput {
bool timedCheckForInput(int milliseconds) { bool timedCheckForInput(int milliseconds) {
if(inputQueue.length || timedCheckForInput_bypassingBuffer(milliseconds)) if(inputQueue.length || timedCheckForInput_bypassingBuffer(milliseconds))
return true; return true;
version(WithEncapsulatedSignals)
if(terminal.interrupted || terminal.windowSizeChanged || terminal.hangedUp)
return true;
version(WithSignals) version(WithSignals)
if(interrupted || windowSizeChanged || hangedUp) if(interrupted || windowSizeChanged || hangedUp)
return true; return true;
@ -2269,7 +2341,7 @@ struct RealTimeConsoleInput {
// it was notified, but it could be left over from stuff we // it was notified, but it could be left over from stuff we
// already processed... so gonna check the blocking conditions here too // already processed... so gonna check the blocking conditions here too
// (FIXME: this sucks and is surely a race condition of pain) // (FIXME: this sucks and is surely a race condition of pain)
return terminal.tew.terminalEmulator.pendingForApplication.length || interrupted || windowSizeChanged || hangedUp; return terminal.tew.terminalEmulator.pendingForApplication.length || terminal.interrupted || terminal.windowSizeChanged || terminal.hangedUp;
else else
return false; return false;
} else version(Win32Console) { } else version(Win32Console) {
@ -2432,7 +2504,9 @@ struct RealTimeConsoleInput {
auto oldHeight = terminal.height; auto oldHeight = terminal.height;
terminal.updateSize(); terminal.updateSize();
version(WithSignals) version(WithSignals)
windowSizeChanged = false; windowSizeChanged = false;
version(WithEncapsulatedSignals)
terminal.windowSizeChanged = false;
return InputEvent(SizeChangedEvent(oldWidth, oldHeight, terminal.width, terminal.height), terminal); return InputEvent(SizeChangedEvent(oldWidth, oldHeight, terminal.width, terminal.height), terminal);
} }
@ -2452,21 +2526,36 @@ struct RealTimeConsoleInput {
terminal.flush(); terminal.flush();
wait_for_more: wait_for_more:
version(WithSignals) version(WithSignals) {
if(interrupted) { if(interrupted) {
interrupted = false; interrupted = false;
return InputEvent(UserInterruptionEvent(), terminal); return InputEvent(UserInterruptionEvent(), terminal);
}
if(hangedUp) {
hangedUp = false;
return InputEvent(HangupEvent(), terminal);
}
if(windowSizeChanged) {
return checkWindowSizeChanged();
}
} }
version(WithSignals) version(WithEncapsulatedSignals) {
if(hangedUp) { if(terminal.interrupted) {
hangedUp = false; terminal.interrupted = false;
return InputEvent(HangupEvent(), terminal); return InputEvent(UserInterruptionEvent(), terminal);
} }
version(WithSignals) if(terminal.hangedUp) {
if(windowSizeChanged) { terminal.hangedUp = false;
return checkWindowSizeChanged(); return InputEvent(HangupEvent(), terminal);
}
if(terminal.windowSizeChanged) {
return checkWindowSizeChanged();
}
} }
if(inputQueue.length) { if(inputQueue.length) {
@ -5707,6 +5796,8 @@ version(TerminalDirectToEmulator) {
Test for its presence before using with `static if(arsd.terminal.IntegratedEmulator)`. Test for its presence before using with `static if(arsd.terminal.IntegratedEmulator)`.
All settings here must be set BEFORE you construct any [Terminal] instances.
History: History:
Added March 7, 2020. Added March 7, 2020.
+/ +/
@ -5735,6 +5826,37 @@ version(TerminalDirectToEmulator) {
int initialWidth = 80; int initialWidth = 80;
/// ditto /// ditto
int initialHeight = 40; int initialHeight = 40;
/++
Gives you a chance to modify the window as it is constructed. Intended
to let you add custom menu options.
---
import arsd.terminal;
integratedTerminalEmulatorConfiguration.menuExtensionsConstructor = (TerminalEmulatorWindow window) {
import arsd.minigui; // for the menu related UDAs
class Commands {
@menu("Help") {
void Topics() {
auto window = new Window(); // make a help window of some sort
window.show();
}
@separator
void About() {
messageBox("My Application v 1.0");
}
}
}
window.setMenuAndToolbarFromAnnotatedCode(new Commands());
};
---
History:
Added March 29, 2020. Included in release v7.1.0.
+/
void delegate(TerminalEmulatorWindow) menuExtensionsConstructor;
} }
/+ /+
@ -5748,9 +5870,19 @@ version(TerminalDirectToEmulator) {
import arsd.terminalemulator; import arsd.terminalemulator;
import arsd.minigui; import arsd.minigui;
private class TerminalEmulatorWindow : MainWindow { /++
Represents the window that the library pops up for you.
+/
final class TerminalEmulatorWindow : MainWindow {
this(Terminal* term) { /++
Gives access to the underlying terminal emulation object.
+/
TerminalEmulator terminalEmulator() {
return tew.terminalEmulator;
}
private this(Terminal* term) {
super("Terminal Application", integratedTerminalEmulatorConfiguration.initialWidth * integratedTerminalEmulatorConfiguration.fontSize / 2, integratedTerminalEmulatorConfiguration.initialHeight * integratedTerminalEmulatorConfiguration.fontSize); super("Terminal Application", integratedTerminalEmulatorConfiguration.initialWidth * integratedTerminalEmulatorConfiguration.fontSize / 2, integratedTerminalEmulatorConfiguration.initialHeight * integratedTerminalEmulatorConfiguration.fontSize);
smw = new ScrollMessageWidget(this); smw = new ScrollMessageWidget(this);
@ -5765,10 +5897,12 @@ version(TerminalDirectToEmulator) {
smw.setTotalArea(1, 1); smw.setTotalArea(1, 1);
setMenuAndToolbarFromAnnotatedCode(this); setMenuAndToolbarFromAnnotatedCode(this);
if(integratedTerminalEmulatorConfiguration.menuExtensionsConstructor)
integratedTerminalEmulatorConfiguration.menuExtensionsConstructor(this);
} }
TerminalEmulatorWidget tew; private TerminalEmulatorWidget tew;
ScrollMessageWidget smw; private ScrollMessageWidget smw;
@menu("&History") { @menu("&History") {
@tip("Saves the currently visible content to a file") @tip("Saves the currently visible content to a file")
@ -5838,12 +5972,10 @@ version(TerminalDirectToEmulator) {
@menu("&Edit") { @menu("&Edit") {
void Copy() { void Copy() {
tew.terminalEmulator.copyToClipboard(tew.terminalEmulator.getSelectedText()); tew.terminalEmulator.copyToClipboard(tew.terminalEmulator.getSelectedText());
//messageBox("copy", tew.terminalEmulator.getSelectedText());
} }
void Paste() { void Paste() {
tew.terminalEmulator.pasteFromClipboard(&tew.terminalEmulator.sendPasteData); tew.terminalEmulator.pasteFromClipboard(&tew.terminalEmulator.sendPasteData);
//messageBox("paste", "idk");
} }
} }
} }
@ -5862,15 +5994,15 @@ version(TerminalDirectToEmulator) {
override Menu contextMenu(int x, int y) { override Menu contextMenu(int x, int y) {
if(ctx is null) { if(ctx is null) {
ctx = new Menu(""); ctx = new Menu("");
auto i = ctx.addItem(new MenuItem("Copy")); ctx.addItem(new MenuItem(new Action("Copy", 0, {
i.addEventListener("triggered", {
terminalEmulator.copyToClipboard(terminalEmulator.getSelectedText()); terminalEmulator.copyToClipboard(terminalEmulator.getSelectedText());
})));
}); ctx.addItem(new MenuItem(new Action("Paste", 0, {
i = ctx.addItem(new MenuItem("Paste"));
i.addEventListener("triggered", {
terminalEmulator.pasteFromClipboard(&terminalEmulator.sendPasteData); terminalEmulator.pasteFromClipboard(&terminalEmulator.sendPasteData);
}); })));
ctx.addItem(new MenuItem(new Action("Toggle Scroll Lock", 0, {
terminalEmulator.toggleScrollLock();
})));
} }
return ctx; return ctx;
} }
@ -5882,7 +6014,12 @@ version(TerminalDirectToEmulator) {
super(parent); super(parent);
this.parentWindow.win.onClosing = { this.parentWindow.win.onClosing = {
if(term) if(term)
hangedUp = true; term.hangedUp = true;
// try to get it to terminate slightly more forcibly too, if possible
if(sigIntExtension)
sigIntExtension();
terminalEmulator.outgoingSignal.notify(); terminalEmulator.outgoingSignal.notify();
terminalEmulator.incomingSignal.notify(); terminalEmulator.incomingSignal.notify();
}; };
@ -5947,7 +6084,7 @@ version(TerminalDirectToEmulator) {
} }
clearScreenRequested = true; clearScreenRequested = true;
if(widget && widget.term) if(widget && widget.term)
windowSizeChanged = true; widget.term.windowSizeChanged = true;
outgoingSignal.notify(); outgoingSignal.notify();
redraw(); redraw();
} }
@ -6036,6 +6173,8 @@ version(TerminalDirectToEmulator) {
override void sendRawInput(in ubyte[] data) { override void sendRawInput(in ubyte[] data) {
void send(in ubyte[] data) { void send(in ubyte[] data) {
if(data.length == 0)
return;
super.sendRawInput(data); super.sendRawInput(data);
if(echo) if(echo)
sendToApplication(data); sendToApplication(data);
@ -6049,9 +6188,6 @@ version(TerminalDirectToEmulator) {
send(data[last .. idx]); send(data[last .. idx]);
send(crlf[]); send(crlf[]);
last = idx + 1; last = idx + 1;
} else if(ch == 3) {
if(widget && widget.term)
interrupted = true;
} }
} }
@ -6192,9 +6328,34 @@ version(TerminalDirectToEmulator) {
if(c == '\n') c = '\r'; // terminal seem to expect enter to send 13 instead of 10 if(c == '\n') c = '\r'; // terminal seem to expect enter to send 13 instead of 10
auto data = str[0 .. encode(str, c)]; auto data = str[0 .. encode(str, c)];
// on X11, the delete key can send a 127 character too, but that shouldn't be sent to the terminal since xterm shoots \033[3~ instead, which we handle in the KeyEvent handler.
if(c != 127) if(c == 0x1c) /* ctrl+\, force quit */ {
version(Posix) {
import core.sys.posix.signal;
pthread_kill(widget.term.threadId, SIGQUIT); // or SIGKILL even?
assert(0);
//import core.sys.posix.pthread;
//pthread_cancel(widget.term.threadId);
//widget.term = null;
} else version(Windows) {
import core.sys.windows.windows;
auto hnd = OpenProcess(SYNCHRONIZE | PROCESS_TERMINATE, TRUE, GetCurrentProcessId());
TerminateProcess(hnd, -1);
assert(0);
}
} else if(c == 3) /* ctrl+c, interrupt */ {
if(sigIntExtension)
sigIntExtension();
if(widget && widget.term) {
widget.term.interrupted = true;
outgoingSignal.notify();
}
} else if(c != 127) {
// on X11, the delete key can send a 127 character too, but that shouldn't be sent to the terminal since xterm shoots \033[3~ instead, which we handle in the KeyEvent handler.
sendToApplication(data); sendToApplication(data);
}
}); });
} }