mirror of https://github.com/adamdruppe/arsd.git
tons of stuff
This commit is contained in:
parent
c5cec0c805
commit
7873768fd1
21
minigui.d
21
minigui.d
|
@ -4359,9 +4359,13 @@ class MainWindow : Window {
|
|||
}
|
||||
void setMenuAndToolbarFromAnnotatedCode_internal(T)(ref T t) {
|
||||
Action[] toolbarActions;
|
||||
auto menuBar = new MenuBar();
|
||||
auto menuBar = this.menuBar is null ? new MenuBar() : this.menuBar;
|
||||
Menu[string] mcs;
|
||||
|
||||
foreach(menu; menuBar.subMenus) {
|
||||
mcs[menu.label] = menu;
|
||||
}
|
||||
|
||||
void delegate() triggering;
|
||||
|
||||
foreach(memberName; __traits(derivedMembers, T)) {
|
||||
|
@ -4482,6 +4486,12 @@ class MainWindow : Window {
|
|||
MenuBar menuBar() { return _menu; }
|
||||
///
|
||||
MenuBar menuBar(MenuBar m) {
|
||||
if(m is _menu) {
|
||||
version(custom_widgets)
|
||||
recomputeChildLayout();
|
||||
return m;
|
||||
}
|
||||
|
||||
if(_menu !is null) {
|
||||
// make sure it is sanely removed
|
||||
// FIXME
|
||||
|
@ -4758,6 +4768,7 @@ class ToolButton : Button {
|
|||
///
|
||||
class MenuBar : Widget {
|
||||
MenuItem[] items;
|
||||
Menu[] subMenus;
|
||||
|
||||
version(win32_widgets) {
|
||||
HMENU handle;
|
||||
|
@ -4796,7 +4807,10 @@ class MenuBar : Widget {
|
|||
|
||||
///
|
||||
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);
|
||||
items ~= mbItem;
|
||||
|
@ -5335,8 +5349,9 @@ class MenuItem : MouseActivatedWidget {
|
|||
override int minHeight() { return Window.lineHeight + 4; }
|
||||
override int minWidth() { return Window.lineHeight * cast(int) label.length + 8; }
|
||||
override int maxWidth() {
|
||||
if(cast(MenuBar) parent)
|
||||
if(cast(MenuBar) parent) {
|
||||
return Window.lineHeight / 2 * cast(int) label.length + 8;
|
||||
}
|
||||
return int.max;
|
||||
}
|
||||
///
|
||||
|
|
255
terminal.d
255
terminal.d
|
@ -138,11 +138,36 @@ version(demos) unittest {
|
|||
|
||||
// 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) {
|
||||
__gshared bool windowSizeChanged = false;
|
||||
__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;
|
||||
version=WithEncapsulatedSignals;
|
||||
} else version(Posix) {
|
||||
enum SIGWINCH = 28;
|
||||
__gshared bool windowSizeChanged = false;
|
||||
|
@ -172,6 +197,9 @@ version(TerminalDirectToEmulator) {
|
|||
send(SignalFired());
|
||||
catch(Exception) {}
|
||||
}
|
||||
|
||||
if(sigIntExtension)
|
||||
sigIntExtension();
|
||||
}
|
||||
extern(C)
|
||||
void hangupSignalHandler(int sigNumber) nothrow {
|
||||
|
@ -183,7 +211,6 @@ version(TerminalDirectToEmulator) {
|
|||
catch(Exception) {}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// parts of this were taken from Robik's ConsoleD
|
||||
|
@ -475,6 +502,12 @@ struct Terminal {
|
|||
@disable this(this);
|
||||
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_;
|
||||
version(Windows) private CONSOLE_CURSOR_INFO originalCursorInfo;
|
||||
|
||||
|
@ -1029,6 +1062,20 @@ struct Terminal {
|
|||
|
||||
version(TerminalDirectToEmulator) {
|
||||
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)
|
||||
|
@ -1043,17 +1090,39 @@ struct Terminal {
|
|||
return;
|
||||
}
|
||||
|
||||
|
||||
tcaps = uint.max; // all capabilities
|
||||
import core.thread;
|
||||
|
||||
auto thread = new Thread( {
|
||||
auto window = new TerminalEmulatorWindow(&this);
|
||||
tew = window.tew;
|
||||
window.loop();
|
||||
});
|
||||
version(Posix)
|
||||
threadId = Thread.getThis.id;
|
||||
else version(Windows)
|
||||
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
|
||||
while(cast(shared) tew is null) {
|
||||
import core.thread;
|
||||
|
@ -1202,6 +1271,7 @@ struct Terminal {
|
|||
version(TerminalDirectToEmulator) {
|
||||
writeln("\n\n<exited>");
|
||||
setTitle(tew.terminalEmulator.currentTitle ~ " <exited>");
|
||||
tew.term = null;
|
||||
} else
|
||||
if(terminalInFamily("xterm", "rxvt", "screen", "tmux")) {
|
||||
writeStringRaw("\033[23;0t"); // restore window title from the stack
|
||||
|
@ -1229,7 +1299,6 @@ struct Terminal {
|
|||
SetConsoleActiveScreenBuffer(stdo);
|
||||
if(hConsole !is stdo)
|
||||
CloseHandle(hConsole);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2250,6 +2319,9 @@ struct RealTimeConsoleInput {
|
|||
bool timedCheckForInput(int milliseconds) {
|
||||
if(inputQueue.length || timedCheckForInput_bypassingBuffer(milliseconds))
|
||||
return true;
|
||||
version(WithEncapsulatedSignals)
|
||||
if(terminal.interrupted || terminal.windowSizeChanged || terminal.hangedUp)
|
||||
return true;
|
||||
version(WithSignals)
|
||||
if(interrupted || windowSizeChanged || hangedUp)
|
||||
return true;
|
||||
|
@ -2269,7 +2341,7 @@ struct RealTimeConsoleInput {
|
|||
// it was notified, but it could be left over from stuff we
|
||||
// already processed... so gonna check the blocking conditions here too
|
||||
// (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
|
||||
return false;
|
||||
} else version(Win32Console) {
|
||||
|
@ -2432,7 +2504,9 @@ struct RealTimeConsoleInput {
|
|||
auto oldHeight = terminal.height;
|
||||
terminal.updateSize();
|
||||
version(WithSignals)
|
||||
windowSizeChanged = false;
|
||||
windowSizeChanged = false;
|
||||
version(WithEncapsulatedSignals)
|
||||
terminal.windowSizeChanged = false;
|
||||
return InputEvent(SizeChangedEvent(oldWidth, oldHeight, terminal.width, terminal.height), terminal);
|
||||
}
|
||||
|
||||
|
@ -2452,21 +2526,36 @@ struct RealTimeConsoleInput {
|
|||
terminal.flush();
|
||||
|
||||
wait_for_more:
|
||||
version(WithSignals)
|
||||
if(interrupted) {
|
||||
interrupted = false;
|
||||
return InputEvent(UserInterruptionEvent(), terminal);
|
||||
version(WithSignals) {
|
||||
if(interrupted) {
|
||||
interrupted = false;
|
||||
return InputEvent(UserInterruptionEvent(), terminal);
|
||||
}
|
||||
|
||||
if(hangedUp) {
|
||||
hangedUp = false;
|
||||
return InputEvent(HangupEvent(), terminal);
|
||||
}
|
||||
|
||||
if(windowSizeChanged) {
|
||||
return checkWindowSizeChanged();
|
||||
}
|
||||
}
|
||||
|
||||
version(WithSignals)
|
||||
if(hangedUp) {
|
||||
hangedUp = false;
|
||||
return InputEvent(HangupEvent(), terminal);
|
||||
}
|
||||
version(WithEncapsulatedSignals) {
|
||||
if(terminal.interrupted) {
|
||||
terminal.interrupted = false;
|
||||
return InputEvent(UserInterruptionEvent(), terminal);
|
||||
}
|
||||
|
||||
version(WithSignals)
|
||||
if(windowSizeChanged) {
|
||||
return checkWindowSizeChanged();
|
||||
if(terminal.hangedUp) {
|
||||
terminal.hangedUp = false;
|
||||
return InputEvent(HangupEvent(), terminal);
|
||||
}
|
||||
|
||||
if(terminal.windowSizeChanged) {
|
||||
return checkWindowSizeChanged();
|
||||
}
|
||||
}
|
||||
|
||||
if(inputQueue.length) {
|
||||
|
@ -5707,6 +5796,8 @@ version(TerminalDirectToEmulator) {
|
|||
|
||||
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:
|
||||
Added March 7, 2020.
|
||||
+/
|
||||
|
@ -5735,6 +5826,37 @@ version(TerminalDirectToEmulator) {
|
|||
int initialWidth = 80;
|
||||
/// ditto
|
||||
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.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);
|
||||
|
||||
smw = new ScrollMessageWidget(this);
|
||||
|
@ -5765,10 +5897,12 @@ version(TerminalDirectToEmulator) {
|
|||
smw.setTotalArea(1, 1);
|
||||
|
||||
setMenuAndToolbarFromAnnotatedCode(this);
|
||||
if(integratedTerminalEmulatorConfiguration.menuExtensionsConstructor)
|
||||
integratedTerminalEmulatorConfiguration.menuExtensionsConstructor(this);
|
||||
}
|
||||
|
||||
TerminalEmulatorWidget tew;
|
||||
ScrollMessageWidget smw;
|
||||
private TerminalEmulatorWidget tew;
|
||||
private ScrollMessageWidget smw;
|
||||
|
||||
@menu("&History") {
|
||||
@tip("Saves the currently visible content to a file")
|
||||
|
@ -5838,12 +5972,10 @@ version(TerminalDirectToEmulator) {
|
|||
@menu("&Edit") {
|
||||
void Copy() {
|
||||
tew.terminalEmulator.copyToClipboard(tew.terminalEmulator.getSelectedText());
|
||||
//messageBox("copy", tew.terminalEmulator.getSelectedText());
|
||||
}
|
||||
|
||||
void Paste() {
|
||||
tew.terminalEmulator.pasteFromClipboard(&tew.terminalEmulator.sendPasteData);
|
||||
//messageBox("paste", "idk");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -5862,15 +5994,15 @@ version(TerminalDirectToEmulator) {
|
|||
override Menu contextMenu(int x, int y) {
|
||||
if(ctx is null) {
|
||||
ctx = new Menu("");
|
||||
auto i = ctx.addItem(new MenuItem("Copy"));
|
||||
i.addEventListener("triggered", {
|
||||
ctx.addItem(new MenuItem(new Action("Copy", 0, {
|
||||
terminalEmulator.copyToClipboard(terminalEmulator.getSelectedText());
|
||||
|
||||
});
|
||||
i = ctx.addItem(new MenuItem("Paste"));
|
||||
i.addEventListener("triggered", {
|
||||
})));
|
||||
ctx.addItem(new MenuItem(new Action("Paste", 0, {
|
||||
terminalEmulator.pasteFromClipboard(&terminalEmulator.sendPasteData);
|
||||
});
|
||||
})));
|
||||
ctx.addItem(new MenuItem(new Action("Toggle Scroll Lock", 0, {
|
||||
terminalEmulator.toggleScrollLock();
|
||||
})));
|
||||
}
|
||||
return ctx;
|
||||
}
|
||||
|
@ -5882,7 +6014,12 @@ version(TerminalDirectToEmulator) {
|
|||
super(parent);
|
||||
this.parentWindow.win.onClosing = {
|
||||
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.incomingSignal.notify();
|
||||
};
|
||||
|
@ -5947,7 +6084,7 @@ version(TerminalDirectToEmulator) {
|
|||
}
|
||||
clearScreenRequested = true;
|
||||
if(widget && widget.term)
|
||||
windowSizeChanged = true;
|
||||
widget.term.windowSizeChanged = true;
|
||||
outgoingSignal.notify();
|
||||
redraw();
|
||||
}
|
||||
|
@ -6036,6 +6173,8 @@ version(TerminalDirectToEmulator) {
|
|||
|
||||
override void sendRawInput(in ubyte[] data) {
|
||||
void send(in ubyte[] data) {
|
||||
if(data.length == 0)
|
||||
return;
|
||||
super.sendRawInput(data);
|
||||
if(echo)
|
||||
sendToApplication(data);
|
||||
|
@ -6049,9 +6188,6 @@ version(TerminalDirectToEmulator) {
|
|||
send(data[last .. idx]);
|
||||
send(crlf[]);
|
||||
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
|
||||
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);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue