1 /* coded by Ketmar // Invisible Vector <ketmar@ketmar.no-ip.org>
2 * Understanding is not required. Only obedience.
4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, version 3 of the License ONLY.
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
19 import core
.time
: MonoTime
, Duration
;
29 // ////////////////////////////////////////////////////////////////////////// //
30 enum fontNorm
= "-*-terminus-bold-*-*-*-20-*-*-*-*-*-*-*";
31 enum fontBold
= "-*-terminus-bold-*-*-*-20-*-*-*-*-*-*-*";
33 enum OptPtrBlankTime
= 1500;
34 enum OptPtrBlinkTime
= 700;
37 // ////////////////////////////////////////////////////////////////////////// //
38 class ExitException
: Exception
{ this () { super("exit"); } }
39 class ExitError
: ExitException
{ this () { super(); } }
42 void die(AA
...) (string fmt
, AA args
) {
43 stderr
.write("FATAL: ");
44 stderr
.writeln(fmt
, args
);
45 throw new ExitError();
49 // ////////////////////////////////////////////////////////////////////////// //
50 public bool isUTF8Locale () nothrow @trusted @nogc {
53 static char tolower (char ch
) pure nothrow @safe @nogc { return (ch
>= 'A' && ch
<= 'Z' ?
cast(char)(ch
-'A'+'a') : ch
); }
55 __gshared
bool res
= false;
56 __gshared
bool checked
= false;
57 static shared int waiting
= 0; // 0: not inited; 1: initializing; 2: done
60 if (cas(&waiting
, 0, 1)) {
61 // not inited -> inited
62 import core
.stdc
.locale
;
63 char* lct
= setlocale(LC_CTYPE
, null);
65 //auto lang = getenv("LANG");
66 //if (lang is null) return;
69 if (tolower(lang
[0]) == 'u' && tolower(lang
[1]) == 't' && tolower(lang
[2]) == 'f') { res
= true; break; }
72 //res = (strcasestr(lct, "utf") !is null);
79 if (!cas(&waiting
, 1, 2)) assert(0, "wtf?!");
81 // either inited, or initializing
82 while (atomicLoad(waiting
) != 2) {}
85 if (!checked
) assert(0, "wtf?!");
92 // ////////////////////////////////////////////////////////////////////////// //
93 final class DiWindow
{
95 private import core
.stdc
.config
: c_long
;
98 enum RedrawRateActive
= 40; // each 40 msecs
99 enum RedrawRateInactive
= 100; // each 100 msecs
101 enum DefaultBGColorIdx
= 0;
102 enum DefaultFGColorIdx
= 7;
103 enum ActiveCursorColorBGIdx
= 256; // +1 for blink
104 enum ActiveCursorColorFGIdx
= 258; // same for both blink stages
105 enum InactiveCursorColorFGIdx
= 259; // same for both blink stages
106 enum BoldFGIdx
= 260;
107 enum UnderlineFGIdx
= 260;
109 enum { FONTDEF
, FONTBOLD
}
110 bool mOneFont
; // FONTDEF is the same as FONTBOLD?
112 Window mXEmbed
; // do xembed with this wid
114 int csigfd
= -1; // signals fd
119 Cursor mXTextCursor
; // text cursor
120 Cursor mXDefaultCursor
; // 'default' cursor
121 Cursor mXBlankCursor
;
125 int winWidth
; // window width in pixels
126 int winHeight
; // window height in pixels
127 int charWidth
; // char width
128 int charHeight
; // char height
130 bool mForceRedraw
; // force full terminal redraw
131 bool mForceDirtyRedraw
; // force update of dirty areas
133 char[1024] mLastTitle
= 0;
134 int mLastTitleLength
= int.max
;
135 MonoTime mLastTitleChangeTime
;
138 int mSelLastX
, mSelLastY
; // last mouse position if mDoSelection is true
139 string mCurSelection
;
142 MonoTime mCurLastPhaseChange
;
143 MonoTime mCurLastMove
;
144 int mCurPhase
; // 0/1
146 MonoTime mLastInputTime
; // for pointer blanking
147 bool mPointerVisible
;
149 MonoTime mLastDrawTime
;
155 ubyte mWinState
; // focus, visible
157 @property bool isVisible () const pure @safe nothrow @nogc { pragma(inline
, true); return ((mWinState
&wsVisible
) != 0); }
158 @property bool isFocused () const pure @safe nothrow @nogc { pragma(inline
, true); return ((mWinState
&wsFocused
) != 0); }
167 Atom xaWMDeleteWindow
;
168 Atom xaTerminalMessage
;
175 enum { CLNORMAL
, CLBW
, CLGREEN
}
176 c_ulong
[512][3] clrs
;
178 static struct XFont
{
188 static final class DittyTab
{
191 bool prevcurVis
= false;
192 int prevcurX
, prevcurY
;
193 bool reversed
; // is everything reversed?
195 char[1024] mLastTitle
= 0;
196 int mLastTitleLength
= int.max
;
197 MonoTime mLastTitleChangeTime
;
199 char[1024] mLastProcess
= 0;
200 int mLastProcessLength
;
202 char[1024] mLastFullProcess
= 0;
203 int mLastFullProcessLength
;
205 this (DiWindow axw
, int aw
, int ah
) {
206 import std
.algorithm
: max
, min
;
207 assert(axw
!is null);
209 aw
= max(MinBufferWidth
, min(aw
, MaxBufferWidth
));
210 ah
= max(MinBufferHeight
, min(ah
, MaxBufferHeight
));
211 // create application buffer
212 appbuf
= new VT100Emu(aw
, ah
, isUTF8Locale
);
213 appbuf
.onScrollUp
= &axw
.onScrollUp
;
214 appbuf
.onScrollDown
= &axw
.onScrollDown
;
215 appbuf
.onBell
= &axw
.onBell
;
216 appbuf
.onNewTitleEvent
= &axw
.onNewTitleEvent
;
217 appbuf
.onReverseEvent
= &axw
.onReverseEvent
;
220 @property ScreenBuffer
activebuf () nothrow @safe @nogc { pragma(inline
, true); return appbuf
; }
221 // title is always 0-terminated
222 @property const(char)[] title () nothrow @safe @nogc { pragma(inline
, true); return mLastTitle
[0..mLastTitleLength
]; }
223 @property const(char)[] appname () nothrow @safe @nogc { pragma(inline
, true); return mLastProcess
[0..mLastProcessLength
]; }
224 @property const(char)[] fullappname () nothrow @safe @nogc { pragma(inline
, true); return mLastFullProcess
[0..mLastFullProcessLength
]; }
229 void onResize (int aw
, int ah
) {
230 appbuf
.resize(aw
, ah
);
231 appbuf
.setFullDirty();
234 void keypressEvent (dchar dch
, KeySym ksym
, X11ModState modstate
) {
235 activebuf
.keypressEvent(dch
, ksym
, modstate
);
238 // return `true` if process name was changed
239 bool checkFixProcess () nothrow @nogc {
241 char[1024] buf
= void;
242 auto nm
= appbuf
.getFullProcessName(buf
[]);
243 if (nm
.length
!= mLastFullProcessLength || mLastFullProcess
[0..mLastFullProcessLength
] != nm
[0..$]) {
244 mLastFullProcess
[0..nm
.length
] = nm
[];
245 mLastFullProcessLength
= cast(int)nm
.length
;
247 nm
= appbuf
.getProcessName(buf
[]);
248 if (nm
.length
!= mLastProcessLength || mLastProcess
[0..mLastProcessLength
] != nm
[0..$]) {
250 mLastProcess
[0..nm
.length
] = nm
[];
251 mLastProcessLength
= cast(int)nm
.length
;
257 // return `true` if process name was changed
258 void setTitle (const(char)[] atitle
) nothrow @nogc {
259 if (atitle
.length
> mLastTitle
.length
-1) atitle
= atitle
[0..mLastTitle
.length
-1]; //FIXME
260 mLastTitle
[0..atitle
.length
] = atitle
[];
261 mLastTitleLength
= cast(int)atitle
.length
;
262 mLastTitle
[mLastTitleLength
] = 0;
268 final @property DittyTab
tab () nothrow @safe @nogc { pragma(inline
, true); return mTab
; }
269 final @property VT100Emu
appbuf () nothrow @safe @nogc { pragma(inline
, true); return mTab
.appbuf
; }
270 final @property ScreenBuffer
scrbuf () nothrow @safe @nogc { pragma(inline
, true); return mTab
.activebuf
; }
272 dchar[MaxBufferWidth
+8] mDrawBuf
= void;
274 c_ulong
getColor (usize idx
) const nothrow @trusted @nogc {
275 //if (globalBW && (curterm == NULL || !curterm->blackandwhite)) return dc.clrs[globalBW][idx];
276 //if (curterm != NULL) return dc.clrs[curterm->blackandwhite%3][idx];
277 //return dc.clrs[0][idx];
278 return (idx
< clrs
[0].length ? clrs
[CLNORMAL
][idx
] : clrs
[0][0]);
281 //~this () { close(); } //oops
284 void postMessage (c_long type
, c_long l1
=0, c_long l2
=0, c_long l3
=0, c_long l4
=0) {
285 //{ import iv.writer; writeln("posting message..."); }
287 ev
.type
= ClientMessage
;
288 ev
.xclient
.window
= win
;
289 ev
.xclient
.message_type
= xaTerminalMessage
;
290 ev
.xclient
.format
= 32;
291 ev
.xclient
.data
.l
[0] = type
;
292 ev
.xclient
.data
.l
[1] = l1
;
293 ev
.xclient
.data
.l
[2] = l2
;
294 ev
.xclient
.data
.l
[3] = l3
;
295 ev
.xclient
.data
.l
[4] = l4
;
296 XSendEvent(dpy
, win
, False
, 0, &ev
);
301 xaXEmbed
= XInternAtom(dpy
, "_XEMBED", False
);
302 xaVTSelection
= XInternAtom(dpy
, "_STERM_SELECTION_", 0);
303 xaClipboard
= XInternAtom(dpy
, "CLIPBOARD", 0);
304 xaUTF8
= XInternAtom(dpy
, "UTF8_STRING", 0);
305 xaNetWMName
= XInternAtom(dpy
, "_NET_WM_NAME", 0);
306 xaTargets
= XInternAtom(dpy
, "TARGETS", 0);
307 xaWMProtocols
= XInternAtom(dpy
, "WM_PROTOCOLS", 0);
308 xaWMDeleteWindow
= XInternAtom(dpy
, "WM_DELETE_WINDOW", 0);
309 xaTerminalMessage
= XInternAtom(dpy
, "K8_DITTY_MESSAGE", 0);
314 klass
.res_class
= "DiTTY_SAMPLE_TERMINAL";
315 klass
.res_name
= "DiTTY_SAMPLE_TERMINAL";
317 wm
.flags
= InputHint
;
320 size
.flags
= PMinSize|PMaxSize|PSize|PResizeInc|PBaseSize
;
321 size
.min_width
= MinBufferWidth
*charWidth
;
322 size
.min_height
= (MinBufferHeight
+1)*charHeight
; // one more row for tabs and status info
323 size
.max_width
= MaxBufferWidth
*charWidth
;
324 size
.max_height
= (MaxBufferHeight
+1)*charHeight
;
325 size
.width_inc
= charWidth
;
326 size
.height_inc
= charHeight
;
327 /* if we'll do it this way, FluxBox will assume that minimal window size is equal to base size
328 size.width = winWidth;
329 size.height = winHeight;
330 size.base_width = winWidth;
331 size.base_height = winHeight;
333 size
.width
= size
.min_width
;
334 size
.height
= size
.min_height
;
335 size
.base_width
= size
.min_width
;
336 size
.base_height
= size
.min_height
;
337 XSetWMNormalHints(dpy
, win
, &size
);
338 XSetWMProperties(dpy
, win
, null, null, null, 0, &size
, &wm
, &klass
);
339 XSetWMProtocols(dpy
, win
, &xaWMDeleteWindow
, 1);
340 XResizeWindow(dpy
, win
, winWidth
, winHeight
);
343 XFontSet
xinitfont (string fontstr
) {
344 import std
.string
: toStringz
, fromStringz
;
350 set
= XCreateFontSet(dpy
, cast(char*)(fontstr
.toStringz
), &missing
, &n
, &def
);
352 //!!!:while (n--) errwriteln("diterm: missing fontset: ", missing[n].fromStringz);
353 XFreeStringList(missing
);
358 void xgetfontinfo (XFontSet set
, int *ascent
, int *descent
, short *lbearing
, short *rbearing
, Font
*fid
) {
359 import std
.algorithm
: max
;
360 XFontStruct
**xfonts
; // owned by Xlib
361 char **fontNames
; // owned by Xlib
362 *ascent
= *descent
= *lbearing
= *rbearing
= 0;
363 /*int n =*/ XFontsOfFontSet(set
, &xfonts
, &fontNames
);
364 /*HACK: use only first font in set; this is due to possible extra-wide fonts*/
365 *fid
= (*xfonts
).fid
;
366 *ascent
= max(*ascent
, (*xfonts
).ascent
);
367 *descent
= max(*descent
, (*xfonts
).descent
);
368 *lbearing
= max(*lbearing
, (*xfonts
).min_bounds
.lbearing
);
369 *rbearing
= max(*rbearing
, (*xfonts
).max_bounds
.rbearing
);
372 void initfonts (string fontstr
, string bfontstr
) {
373 import core
.stdc
.stdlib
: malloc
, free
;
374 import core
.stdc
.string
: strdup
;
375 import core
.stdc
.locale
;
377 /* X Core Fonts fix */
378 /* sorry, we HAVE to do this shit here!
379 * braindamaged X11 will not work with utf-8 in Xmb*() correctly if we have, for example,
380 * koi8 as system locale unless we first create font set in system locale. don't even ask
381 * me why X.Org is so fuckin' broken. */
385 olocale
= strdup(setlocale(LC_ALL
, null)); /*FIXME: this can fail if we have no memory; fuck it*/
386 setlocale(LC_ALL
, "");
387 tmp_fs
= XCreateFontSet(dpy
, cast(char*)"-*-*-*-*-*-*-*-*-*-*-*-*-*", &missing
, &missing_count
, null);
388 if (!tmp_fs
) die("FATAL: can't apply workarount for X Core FontSets!");
389 if (missing
) XFreeStringList(missing
);
390 // throw out unused fontset
391 XFreeFontSet(dpy
, tmp_fs
);
392 //setlocale(LC_ALL, "ru_RU.utf-8");
393 //TODO: find suitable utf-8 encoding
394 setlocale(LC_ALL
, "en_US.UTF-8");
395 // create fonts for utf-8 locale
396 if ((font
[0].set
= xinitfont(fontstr
)) is null) {
397 /*if ((font[0].set = xinitfont(FONT)) is null)*/ die("can't load font %s", fontstr
);
399 xgetfontinfo(font
[0].set
, &font
[0].ascent
, &font
[0].descent
, &font
[0].lbearing
, &font
[0].rbearing
, &font
[0].fid
);
400 if ((font
[1].set
= xinitfont(bfontstr
)) is null) {
401 /*if ((font[1].set = xinitfont(FONTBOLD)) is null)*/ die("can't load font %s", bfontstr
);
403 xgetfontinfo(font
[1].set
, &font
[1].ascent
, &font
[1].descent
, &font
[1].lbearing
, &font
[1].rbearing
, &font
[1].fid
);
405 if ((font[2].set = xinitfont(tabfont)) is null) {
406 /*if ((font[2].set = xinitfont(FONTTAB)) is null)*/ die("can't load font %s", tabfont);
408 xgetfontinfo(font[2].set, &font[2].ascent, &font[2].descent, &font[2].lbearing, &font[2].rbearing, &font[2].fid);
411 setlocale(LC_ALL
, olocale
);
413 // same fonts for normal and bold?
414 mOneFont
= (fontstr
== bfontstr
);
417 void xallocbwclr (usize idx
, XColor
*color
) {
418 XQueryColor(dpy
, cmap
, color
);
419 double lumi
= 0.3*(cast(double)color
.red
/65535.0)+0.59*(cast(double)color
.green
/65535.0)+0.11*(cast(double)color
.blue
/65535.0);
420 color
.red
= color
.green
= color
.blue
= cast(ushort)(lumi
*65535.0);
421 if (!XAllocColor(dpy
, cmap
, color
)) {
422 import core
.stdc
.stdio
;
423 stderr
.fprintf("WARNING: could not allocate b/w color #%u\n", cast(uint)idx
);
426 clrs
[CLBW
][idx
] = color
.pixel
;
427 color
.red
= color
.blue
= 0;
428 if (!XAllocColor(dpy
, cmap
, color
)) {
429 import core
.stdc
.stdio
;
430 stderr
.fprintf("WARNING: could not allocate b/w color #%u\n", cast(uint)idx
);
433 clrs
[CLGREEN
][idx
] = color
.pixel
;
436 void xallocnamedclr (usize idx
, string cname
) {
437 import std
.string
: toStringz
;
439 if (!XAllocNamedColor(dpy
, cmap
, cname
.toStringz
, &color
, &color
)) {
440 import core
.stdc
.stdio
;
441 stderr
.fprintf("WARNING: could not allocate color #%u: '%.s'", cast(uint)idx
, cast(uint)cname
.length
, cname
.ptr
);
444 clrs
[CLNORMAL
][idx
] = color
.pixel
;
445 xallocbwclr(idx
, &color
);
449 static immutable string
[16] defclr
= [
471 uint white
= WhitePixel(dpy
, scr
);
476 // load colors [0-15]
477 foreach (immutable idx
; 0..defclr
.length
) {
478 //const char *cname = opt_colornames[f]!=NULL?opt_colornames[f]:defcolornames[f];
479 xallocnamedclr(idx
, defclr
[idx
]);
482 // load colors [256-...]
483 foreach (immutable idx
; 256..clrs
.length
) xallocnamedclr(idx
, "#fff700");
485 // load colors [16-255]; same colors as xterm
487 foreach (immutable r
; 0..6) {
488 foreach (immutable g
; 0..6) {
489 foreach (immutable b
; 0..6) {
490 color
.red
= cast(ushort)(r
== 0 ?
0 : 0x3737+0x2828*r
);
491 color
.green
= cast(ushort)(g
== 0 ?
0 : 0x3737+0x2828*g
);
492 color
.blue
= cast(ushort)(b
== 0 ?
0 : 0x3737+0x2828*b
);
493 if (!XAllocColor(dpy
, cmap
, &color
)) {
494 import core
.stdc
.stdio
;
495 stderr
.fprintf("WARNING: could not allocate color #%u\n", cast(uint)idx
);
497 clrs
[CLNORMAL
][idx
] = color
.pixel
;
498 xallocbwclr(idx
, &color
);
504 foreach (immutable r
; 0..24) {
505 color
.red
= color
.green
= color
.blue
= cast(ushort)(0x0808+0x0a0a*r
);
506 if (!XAllocColor(dpy
, cmap
, &color
)) {
507 import core
.stdc
.stdio
;
508 stderr
.fprintf("WARNING: could not allocate color #%u\n", cast(uint)idx
);
510 clrs
[CLNORMAL
][idx
] = color
.pixel
;
511 xallocbwclr(idx
, &color
);
516 // blinking cursor bg
517 xallocnamedclr(256, "#00ff00");
518 xallocnamedclr(257, "#00cc00");
520 xallocnamedclr(258, "#005500");
522 xallocnamedclr(259, "#009900");
524 xallocnamedclr(260, "#00afaf");
526 xallocnamedclr(261, "#00af00");
529 void changeTitle (const(char)[] s
) nothrow @trusted @nogc {
530 if (s
.length
== 0) s
= "";
531 usize len
= s
.length
;
532 if (len
>= mLastTitle
.length
) len
= mLastTitle
.length
-1;
533 if (mLastTitleLength
!= len || mLastTitle
[0..len
] != s
[0..len
]) {
534 mLastTitle
[0..len
] = s
[0..len
];
536 mLastTitleLength
= len
;
537 XStoreName(dpy
, win
, mLastTitle
.ptr
);
538 XChangeProperty(dpy
, win
, xaNetWMName
, xaUTF8
, 8, PropModeReplace
, cast(ubyte*)mLastTitle
.ptr
, cast(uint)mLastTitleLength
);
539 XFlush(dpy
); // immediate update
543 string
atomName (Atom a
) {
544 import std
.string
: fromStringz
;
545 auto nm
= XGetAtomName(dpy
, a
);
546 auto res
= nm
.fromStringz
.idup
;
551 // ////////////////////////////////////////////////////////////////////// //
552 private void setupSignals () {
554 import core
.sys
.linux
.sys
.signalfd
: signalfd
, SFD_NONBLOCK
, SFD_CLOEXEC
;
555 import core
.sys
.posix
.signal
: sigset_t
, sigemptyset
, sigaddset
, sigprocmask
;
556 import core
.sys
.posix
.signal
: SIG_BLOCK
, SIGTERM
, SIGHUP
, SIGQUIT
, SIGINT
, SIGCHLD
;
559 //sigaddset(&mask, SIGTERM);
560 //sigaddset(&mask, SIGHUP);
561 //sigaddset(&mask, SIGQUIT);
562 //sigaddset(&mask, SIGINT);
563 sigaddset(&mask
, SIGCHLD
);
564 sigprocmask(SIG_BLOCK
, &mask
, null); // we block the signals
565 //pthread_sigmask(SIG_BLOCK, &mask, NULL); // we block the signal
566 csigfd
= signalfd(-1, &mask
, SFD_NONBLOCK|SFD_CLOEXEC
);
567 if (csigfd
< 0) die("can't setup signal handler");
571 void processDeadChildren () {
572 import core
.sys
.linux
.sys
.signalfd
: signalfd_siginfo
;
573 import core
.sys
.posix
.unistd
: read
;
574 signalfd_siginfo si
= void;
575 while (read(csigfd
, &si
, si
.sizeof
) > 0) {} // ignore errors here
577 import core
.sys
.posix
.sys
.wait : waitpid
, WNOHANG
, WIFEXITED
, WEXITSTATUS
;
579 auto pid
= waitpid(-1, &status
, WNOHANG
);
580 if (pid
<= 0) break; // no more dead children
581 if (tab
.appbuf
.checkDeadChild(pid
, WEXITSTATUS(status
))) continue mainloop
;
582 if (onDeadChildren
!is null) onDeadChildren(cast(int)pid
, WEXITSTATUS(status
));
586 import core
.sys
.posix
.sys
.types
: pid_t
;
587 static assert(pid_t
.sizeof
== int.sizeof
);
590 void onScrollUp (ScreenBuffer self
, int y0
, int y1
, int count
, bool wasDirty
) nothrow {
591 if (!isVisible
) return;
592 if (wasDirty
) return;
593 bool restcur
= (tab
.prevcurVis
&& tab
.prevcurX
>= 0 && tab
.prevcurX
< self
.width
&& tab
.prevcurY
>= y0
&& tab
.prevcurY
<= y1
);
594 if (restcur
) undrawCursor();
598 self
.width
, y1
-y0
+1-count
);
599 if (restcur
) drawCursor();
600 // mark scrolled lines non-dirty
601 //{ import core.stdc.stdio; printf("from %d to %d (y0=%d; y1=%d)\n", y0, y1-count+1-1, y0, y1); }
602 foreach (immutable y
; y0
..y1
-count
+1) self
.resetDirtyLine(y
);
605 void onScrollDown (ScreenBuffer self
, int y0
, int y1
, int count
, bool wasDirty
) nothrow {
606 if (!isVisible
) return;
607 if (wasDirty
) return;
609 bool restcur
= (tab
.prevcurVis
&& tab
.prevcurX
>= 0 && tab
.prevcurX
< self
.width
&& tab
.prevcurY
>= y0
&& tab
.prevcurY
<= y1
);
610 if (restcur
) undrawCursor();
614 self
.width
, y1
-y0
+1-count
);
615 if (restcur
) drawCursor();
616 // mark scrolled lines non-dirty
617 foreach (immutable y
; y0
+count
..y1
+1) self
.resetDirtyLine(y
);
621 final void onBell (ScreenBuffer self
) nothrow @trusted @nogc {
625 // new title was set; we don't check if it's the same as old title
626 final void onNewTitleEvent (ScreenBuffer self
, const(char)[] title
) nothrow {
627 if (self
is tab
.appbuf
) {
628 tab
.checkFixProcess();
635 // inverse mode changed; it should be in effect immediately
636 final void onReverseEvent (ScreenBuffer self
) nothrow @trusted @nogc {
637 if (self
is tab
.appbuf
) {
638 tab
.reversed
= !tab
.reversed
;
645 void delegate (int pid
, int exitcode
) onDeadChildren
;
648 // width and height in cells
649 this (int awdt
, int ahgt
) {
650 mTab
= new DittyTab(this, awdt
, ahgt
);
652 initialize(mTab
.appbuf
.width
, mTab
.appbuf
.height
);
655 private void initialize (int awdt
, int ahgt
) {
656 if (win
) assert(0, "already initialized");
658 XSetWindowAttributes attrs
;
660 XColor blackcolor
;// = { 0, 0, 0, 0, 0, 0 };
664 import core
.stdc
.locale
;
665 import core
.stdc
.stdlib
: free
;
666 import core
.stdc
.string
: strdup
;
667 auto olocale
= strdup(setlocale(LC_ALL
, null));
668 //{ import std.string : fromStringz; import iv.writer; writeln("[", olocale.fromStringz, "]"); }
669 if (isUTF8Locale()) {
670 if (!setlocale(LC_ALL
, "")) die("can't set locale");
672 if (!setlocale(LC_ALL
, "en_US.UTF-8")) die("can't set UTF locale");
674 if (!XSupportsLocale()) die("X server doesn't support locale");
675 if (XSetLocaleModifiers("@im=local") is null) die("XSetLocaleModifiers failed");
676 setlocale(LC_ALL
, olocale
);
680 dpy
= XOpenDisplay();
681 if (!dpy
) die("can't open display");
682 scr
= XDefaultScreen(dpy
);
687 initfonts(fontNorm
, fontBold
);
688 /* XXX: Assuming same size for bold font */
689 charWidth
= font
[0].rbearing
-font
[0].lbearing
;
690 charHeight
= font
[0].ascent
+font
[0].descent
;
691 //tch = font[2].ascent+font[2].descent;
694 cmap
= XDefaultColormap(dpy
, scr
);
697 // window - default size
698 wwdt
= awdt
*charWidth
;
699 whgt
= ahgt
*charHeight
;
702 winHeight
= whgt
; // one more row for tabs and status info
704 attrs
.background_pixel
= None
;//getColor(/*defaultBG*/0);
705 attrs
.border_pixel
= None
;//getColor(/*defaultBG*/0);
706 attrs
.bit_gravity
= NorthWestGravity
;
709 KeyPressMask|KeyReleaseMask|KeymapStateMask|
710 ExposureMask|VisibilityChangeMask|StructureNotifyMask|
712 PointerMotionMask|
//TODO: do we need motion reports at all?
713 ButtonPressMask|ButtonReleaseMask|
714 /*EnterWindowMask|LeaveWindowMask|*/
716 attrs
.colormap
= cmap
;
717 parent
= XRootWindow(dpy
, scr
);
718 if (mXEmbed
) parent
= mXEmbed
;
719 win
= XCreateWindow(dpy
, parent
, 0, 0,
720 winWidth
, winHeight
, 0, XDefaultDepth(dpy
, scr
), InputOutput
,
721 XDefaultVisual(dpy
, scr
),
722 CWBackPixel|CWBorderPixel|CWBitGravity|CWEventMask|CWColormap
,
727 // force locale for correct XIM
729 import core
.stdc
.locale
;
730 import core
.stdc
.stdlib
: free
;
731 import core
.stdc
.string
: strdup
;
732 auto olocale
= strdup(setlocale(LC_ALL
, null));
733 //{ import std.string : fromStringz; import iv.writer; writeln("[", olocale.fromStringz, "]"); }
734 if (isUTF8Locale()) {
735 if (!setlocale(LC_ALL
, "")) die("can't set locale");
737 if (!setlocale(LC_ALL
, "en_US.UTF-8")) die("can't set UTF locale");
740 if ((xim
= XOpenIM(dpy
, null, null, null)) is null) {
741 if (XSetLocaleModifiers("@im=local") is null) die("XSetLocaleModifiers failed");
742 if ((xim
= XOpenIM(dpy
, null, null, null)) is null) {
743 if (XSetLocaleModifiers("@im=") is null) die("XSetLocaleModifiers failed");
744 if ((xim
= XOpenIM(dpy
, null, null, null)) is null) die("XOpenIM() failed");
748 XNInputStyle
.ptr
, XIMPreeditNothing|XIMStatusNothing
,
749 XNClientWindow
.ptr
, win
,
750 XNFocusWindow
.ptr
, win
,
752 if (xic
is null) die("XCreateIC failed");
754 setlocale(LC_ALL
, olocale
);
759 gc
= XCreateGC(dpy
, cast(Drawable
)win
, 0, null);
760 XSetGraphicsExposures(dpy
, gc
, True
);
762 mXTextCursor
= XCreateFontCursor(dpy
, XC_xterm
);
763 //mXTextCursor = XCreateFontCursor(dpy, XC_arrow);
764 /* green cursor, black outline */
766 XRecolorCursor(dpy, mXTextCursor,
767 &(XColor){.red = 0x0000, .green = 0xffff, .blue = 0x0000},
768 &(XColor){.red = 0x0000, .green = 0x0000, .blue = 0x0000});
770 mXDefaultCursor
= XCreateFontCursor(dpy
, /*XC_X_cursor*/XC_left_ptr
);
772 XRecolorCursor(dpy, mXDefaultCursor,
773 &(XColor){.red = 0x0000, .green = 0xffff, .blue = 0x0000},
774 &(XColor){.red = 0x0000, .green = 0x0000, .blue = 0x0000});
776 XDefineCursor(dpy
, win
, mXTextCursor
);
777 XStoreName(dpy
, win
, "Ditty");
779 XSetForeground(dpy
, gc
, 0);
781 XMapWindow(dpy
, win
);
783 version(BLANKPTR_USE_GLYPH_CURSOR
) {
784 mXBlankCursor
= XCreateGlyphCursor(dpy
, font
[0].fid
, font
[0].fid
, ' ', ' ', &blackcolor
, &blackcolor
);
788 pm
= XCreateBitmapFromData(dpy
, cast(Drawable
)win
, cmbmp
.ptr
, 1, 1);
789 mXBlankCursor
= XCreatePixmapCursor(dpy
, pm
, pm
, &blackcolor
, &blackcolor
, 0, 0);
790 XFreePixmap(dpy
, pm
);
802 XUnmapWindow(dpy
, win
);
803 XDestroyWindow(dpy
, win
);
812 // ////////////////////////////////////////////////////////////////////// //
813 void blankPointer () {
814 if (mPointerVisible
&& mXBlankCursor
!= None
) {
815 mPointerVisible
= false;
816 XDefineCursor(dpy
, win
, mXBlankCursor
);
821 void unblankPointer () {
822 if (!mPointerVisible
&& mXTextCursor
!= None
) {
823 mPointerVisible
= true;
824 XDefineCursor(dpy
, win
, mXTextCursor
);
827 mLastInputTime
= MonoTime
.currTime
;
830 // ////////////////////////////////////////////////////////////////////// //
831 void drawString (int x
, int y
, const(char)[] str, usize fontset
=FONTDEF
) {
832 if (charHeight
< 1 || charWidth
< 1) return;
833 if (y
< 0 || y
*charHeight
>= winHeight ||
str.length
== 0 || x
/charWidth
>= winWidth
) return;
836 foreach (char ch
; str) {
837 if (dc
.decodeSafe(ch
)) {
838 if (dlen
== mDrawBuf
.length
) break;
839 mDrawBuf
.ptr
[dlen
++] = dc
.codepoint
;
842 //{ import core.stdc.stdio; stderr.fprintf("dlen=%d\n", dlen); }
843 auto xbuf
= mDrawBuf
[0..dlen
];
844 if (dlen
== 0) return;
847 if (x
>= dlen
) return;
850 if (dlen
== 0) return;
852 XFontSet xfontset
= font
[fontset
].set
;
853 int winx
= x
*charWidth
;
854 int winy
= y
*charHeight
+font
[fontset
].ascent
;
855 // XwcDrawString will not fill background
856 XwcDrawImageString(dpy
, cast(Drawable
)win
, xfontset
, gc
, winx
, winy
, xbuf
.ptr
, cast(uint)dlen
);
859 void drawString (int x
, int y
, const(dchar)[] str, usize fontset
=FONTDEF
) {
860 if (charHeight
< 1 || charWidth
< 1) return;
861 if (y
< 0 || y
*charHeight
>= winHeight ||
str.length
== 0 || x
/charWidth
>= winWidth
) return;
864 if (x
>= str.length
) return;
868 XFontSet xfontset
= font
[fontset
].set
;
869 int winx
= x
*charWidth
;
870 int winy
= y
*charHeight
+font
[fontset
].ascent
;
871 // XwcDrawString will not fill background
872 XwcDrawImageString(dpy
, cast(Drawable
)win
, xfontset
, gc
, winx
, winy
, str.ptr
, cast(uint)str.length
);
875 // ////////////////////////////////////////////////////////////////////// //
876 void drawTermChar (int cx
, int cy
, bool asCursor
=false) nothrow @trusted @nogc {
877 if (cx
>= 0 && cy
>= 0 && cx
< scrbuf
.width
&& cy
< scrbuf
.height
) {
878 auto gl
= scrbuf
[cx
, cy
];
879 ushort bg
, fg
, fontidx
;
880 getAttrBGFGFont(gl
.attr
, tab
.reversed
, bg
, fg
, fontidx
);
881 if (asCursor
&& isFocused
) {
882 ushort cbg
= cast(ushort)(ActiveCursorColorBGIdx
+mCurPhase
);
883 ushort cfg
= ActiveCursorColorFGIdx
;
884 if (cbg
< clrs
[0].length
) bg
= cbg
;
885 if (cfg
< clrs
[0].length
) fg
= cfg
;
887 if (scrbuf
.isInSelection(cx
, cy
)) {
893 XSetBackground(dpy
, gc
, getColor(bg
));
894 XSetForeground(dpy
, gc
, getColor(fg
));
895 XFontSet xfontset
= font
[fontidx
].set
;
896 auto ascent
= font
[fontidx
].ascent
;
897 mDrawBuf
.ptr
[0] = gl
.ch
;
898 XwcDrawImageString(dpy
, cast(Drawable
)win
, xfontset
, gc
, cx
*charWidth
, cy
*charHeight
+ascent
, mDrawBuf
.ptr
, 1);
900 if (asCursor
&& !isFocused
&& InactiveCursorColorFGIdx
< clrs
[0].length
) {
901 XSetForeground(dpy
, gc
, getColor(InactiveCursorColorFGIdx
));
902 XDrawRectangle(dpy
, cast(Drawable
)win
, gc
, cx
*charWidth
, cy
*charHeight
, charWidth
-1, charHeight
-1);
904 scrbuf
.setDirtyAt(cx
, cy
, false);
908 // ////////////////////////////////////////////////////////////////////// //
909 void getAttrBGFGFont() (in Attr a
, bool reversed
, out ushort bg
, out ushort fg
, out ushort fontidx
) {
910 fontidx
= (mOneFont ? FONTDEF
: (a
.bold ? FONTBOLD
: FONTDEF
));
911 bg
= (a
.defaultBG ? DefaultBGColorIdx
: a
.bg
);
912 fg
= (a
.defaultFG ? DefaultFGColorIdx
: a
.fg
);
914 if (a
.bold
) fg
= BoldFGIdx
;
915 else if (a
.underline
) fg
= UnderlineFGIdx
;
917 // bold means "intensity" too
918 if (a
.bold
&& fg
< 8) fg
+= 8;
920 if (a
.reversed
!= reversed
) {
927 // ////////////////////////////////////////////////////////////////////// //
928 void drawGlyphsInLine (int x
, int y
, usize len
, bool cursor
=true) nothrow @trusted @nogc {
929 if (y
< 0 || y
>= scrbuf
.height || x
>= scrbuf
.width || len
== 0) return;
930 if (len
> int.max
/1024) len
= int.max
/1024;
933 if (x
>= len
) return;
937 if (x
+len
> scrbuf
.width
) {
938 len
= scrbuf
.width
-x
;
939 if (len
== 0) return;
942 int winx
= x
*charWidth
;
943 immutable winy
= y
*charHeight
;
944 immutable int curx
= scrbuf
.curX
;
945 immutable int cury
= scrbuf
.curY
;
946 immutable bool curvis
= scrbuf
.curVisible
;
947 bool doSelection
= scrbuf
.lineHasSelection(y
);
948 bool cursorLine
= (cursor
&& curvis
&& y
== cury
);
950 // draw cursor char if we hit it
951 if (cursorLine
&& x
== curx
) {
952 drawTermChar(curx
, cury
, /*asCursor:*/true);
958 // get first char attributes
959 ushort bg
, fg
, fontidx
;
960 auto gss
= scrbuf
[x
, y
];
961 getAttrBGFGFont(gss
.attr
, tab
.reversed
, bg
, fg
, fontidx
);
962 XFontSet xfontset
= font
[fontidx
].set
;
963 auto ascent
= font
[fontidx
].ascent
;
964 if (doSelection
&& scrbuf
.isInSelection(x
, y
)) {
969 // now scan until something changed
972 uint ex
= cast(uint)(x
+len
);
973 if (cursor
&& curvis
&& y
== cury
&& x
< curx
&& ex
> curx
) ex
= curx
;
974 while (bpos
< ex
-sx
) {
975 ushort bg1
= void, fg1
= void, fontidx1
= void;
976 auto gg
= scrbuf
[x
, y
];
977 getAttrBGFGFont(gg
.attr
, tab
.reversed
, bg1
, fg1
, fontidx1
);
978 if (doSelection
&& scrbuf
.isInSelection(x
, y
)) {
983 if (bg1
!= bg || fg1
!= fg || fontidx1
!= fontidx
) break;
984 mDrawBuf
.ptr
[bpos
++] = gg
.ch
;
985 //ln.changeDirtyX(x, false);
986 scrbuf
.setDirtyAt(x
, y
, false);
991 XSetBackground(dpy
, gc
, getColor(bg
));
992 XSetForeground(dpy
, gc
, getColor(fg
));
993 XwcDrawImageString(dpy
, cast(Drawable
)win
, xfontset
, gc
, winx
, winy
+ascent
, mDrawBuf
.ptr
, cast(uint)bpos
);
994 // draw underline here
995 winx
+= bpos
*charWidth
;
1000 // ////////////////////////////////////////////////////////////////////// //
1002 void drawTermPixRect (int x
, int y
, int wdt
, int hgt
) nothrow @trusted @nogc {
1003 import std
.algorithm
: max
, min
;
1004 if (wdt
< 1 || hgt
< 1) return;
1005 // the following can overflow; idc
1006 if (x
< 0) { wdt
+= x
; x
= 0; }
1007 if (y
< 0) { hgt
+= y
; y
= 0; }
1009 if (wdt
< 1 || hgt
< 1) return;
1010 // default tty bg color
1011 XSetForeground(dpy
, gc
, getColor(DefaultBGColorIdx
));
1012 // fill right part of the window if necessary
1013 if (x
+wdt
-1 >= scrbuf
.width
*charWidth
) {
1014 XFillRectangle(dpy
, cast(Drawable
)win
, gc
, scrbuf
.width
*charWidth
, y
, x
+wdt
-scrbuf
.width
*charWidth
, hgt
);
1016 // fill bottom part of the window if necessary
1017 if (y
+hgt
-1 >= scrbuf
.height
*charHeight
) {
1018 XFillRectangle(dpy
, cast(Drawable
)win
, gc
, x
, scrbuf
.height
*charWidth
, wdt
, y
+hgt
-scrbuf
.height
*charHeight
);
1020 // should we draw glyphs?
1021 if (x
>= scrbuf
.width
*charWidth || y
>= scrbuf
.height
*charHeight
) return;
1022 int x0
= x
/charWidth
;
1023 int y0
= y
/charHeight
;
1024 int x1
= min((x
+wdt
-1)/charWidth
, scrbuf
.width
-1);
1025 int y1
= min((y
+hgt
-1)/charHeight
, scrbuf
.height
-1);
1026 foreach (immutable yy
; y0
..y1
+1) drawGlyphsInLine(x0
, yy
, x1
-x0
+1);
1029 void drawTermDirty () nothrow @trusted @nogc {
1030 //{ import core.stdc.stdio; printf("REDRAW! dc=%d\n", mScrBuf.mDirtyCount); }
1031 foreach (immutable y
; 0..scrbuf
.height
) {
1032 if (!scrbuf
.isDirtyLine(y
)) continue;
1033 //{ import core.stdc.stdio; printf("REDRAW! y=%d\n", y); }
1035 while (x0
< scrbuf
.width
) {
1036 // find first dirty glyph
1037 while (x0
< scrbuf
.width
&& !scrbuf
.isDirtyAt(x0
, y
)) ++x0
;
1038 if (x0
>= scrbuf
.width
) break;
1039 // find last dirty glyph
1041 while (x1
< scrbuf
.width
&& !scrbuf
.isDirtyAt(x1
, y
)) ++x1
;
1042 drawGlyphsInLine(x0
, y
, x1
-x0
, /*cursor:*/true);
1048 // ////////////////////////////////////////////////////////////////////// //
1049 // operates in character cells
1050 // WARNING! don't pass invalid args!
1051 void xcopytty (int dx
, int dy
, int sx
, int sy
, int wdt
, int hgt
) nothrow @trusted @nogc {
1052 if (wdt
< 1 || hgt
< 1 ||
(sx
== dx
&& sy
== dy
)) return;
1059 XCopyArea(dpy
, cast(Drawable
)win
, cast(Drawable
)win
, gc
,
1066 void undrawCursor () nothrow @trusted @nogc {
1067 if (!tab
.prevcurVis
) return;
1068 drawTermChar(tab
.prevcurX
, tab
.prevcurY
, /*asCursor:*/false);
1071 void drawCursor () nothrow @trusted @nogc {
1072 if (!tab
.prevcurVis
) return;
1073 drawTermChar(tab
.prevcurX
, tab
.prevcurY
, /*asCursor:*/true);
1076 // ////////////////////////////////////////////////////////////////////// //
1077 void doneSelection () {
1079 mDoSelection
= false;
1080 mCurSelection
= scrbuf
.getSelectionText();
1081 if (mCurSelection
.length
== 0) mCurSelection
= null;
1083 XSetSelectionOwner(dpy
, XA_PRIMARY
, win
, CurrentTime
);
1084 XSetSelectionOwner(dpy
, xaClipboard
, win
, CurrentTime
);
1087 scrbuf
.doneSelection();
1090 void xdoMouseReport (ref XEvent e
) {
1091 uint x
= e
.xbutton
.x
/charWidth
;
1092 uint y
= e
.xbutton
.y
/charHeight
;
1093 e
.xbutton
.state
&= (Mod1Mask|Mod4Mask|ControlMask|ShiftMask
);
1096 if (x
>= scrbuf
.width
) x
= scrbuf
.width
-1;
1097 if (y
>= scrbuf
.height
) y
= scrbuf
.height
-1;
1100 scrbuf
.selectionChanged(x
, y
);
1101 if (e
.xbutton
.type
== ButtonRelease
&& e
.xbutton
.button
== 1) doneSelection();
1105 if (e
.xbutton
.state
== ShiftMask
&& e
.xbutton
.type
== ButtonPress
&& e
.xbutton
.button
== 1) {
1107 if (x
>= scrbuf
.width
) x
= scrbuf
.width
-1;
1108 if (y
>= scrbuf
.height
) y
= scrbuf
.height
-1;
1111 scrbuf
.resetSelection();
1112 scrbuf
.selectionChanged(x
, y
);
1113 mDoSelection
= true;
1117 auto button
= cast(ubyte)(e
.xbutton
.button
-Button1
+1);
1119 VT100Emu
.MouseEvent event
;
1121 switch (e
.xbutton
.type
) {
1122 case MotionNotify
: event
= VT100Emu
.MouseEvent
.Motion
; break;
1123 case ButtonPress
: event
= VT100Emu
.MouseEvent
.Down
; break;
1124 case ButtonRelease
: event
= VT100Emu
.MouseEvent
.Up
; break;
1129 (e
.xbutton
.state
&ShiftMask ? VT100Emu
.MouseMods
.Shift
: 0) |
1130 (e
.xbutton
.state
&Mod1Mask ? VT100Emu
.MouseMods
.Meta
: 0) |
1131 (e
.xbutton
.state
&ControlMask ? VT100Emu
.MouseMods
.Ctrl
: 0) |
1132 (e
.xbutton
.state
&Mod4Mask ? VT100Emu
.MouseMods
.Hyper
: 0);
1134 scrbuf
.doMouseReport(x
, y
, event
, button
, mods
);
1137 void processResize (XConfigureEvent
*e
) {
1138 import std
.algorithm
: max
, min
;
1139 if (e
.width
< 1 || e
.height
< 1) return;
1141 winHeight
= e
.height
;
1142 int cols
= max(MinBufferWidth
, min(MaxBufferWidth
, e
.width
/charWidth
));
1143 int rows
= max(MinBufferHeight
, min(MaxBufferHeight
, (e
.height
-charHeight
)/charHeight
));
1144 if (cols
== scrbuf
.width
&& rows
== scrbuf
.height
) return;
1145 // do terminal resize
1146 tab
.onResize(cols
, rows
);
1149 void selRequest (Atom which
) {
1150 if (XGetSelectionOwner(dpy
, which
) != None
) {
1151 XConvertSelection(dpy
, which
, xaTargets
, xaVTSelection
, win
, CurrentTime
);
1155 void preparePaste (XSelectionEvent
* ev
) {
1156 import core
.stdc
.config
;
1157 c_ulong nitems
, ofs
, rem
;
1165 if (ev
.property
!= xaVTSelection
) return;
1166 if (ev
.target
== xaUTF8
) {
1168 } else if (ev
.target
== XA_STRING
) {
1170 } else if (ev
.target
== xaTargets
) {
1171 // list of targets arrived, select appropriate one
1172 Atom rqtype
= cast(Atom
)None
;
1174 if (XGetWindowProperty(dpy
, win
, ev
.property
, 0, 65536, False
, XA_ATOM
, &type
, &format
, &nitems
, &rem
, &data
)) {
1178 for (targ
= cast(Atom
*)data
; nitems
> 0; --nitems
, ++targ
) {
1179 if (*targ
== xaUTF8
) rqtype
= xaUTF8
;
1180 else if (*targ
== XA_STRING
&& rqtype
== None
) rqtype
= XA_STRING
;
1184 if (rqtype
!= None
) XConvertSelection(dpy
, ev
.selection
, rqtype
, xaVTSelection
, win
, CurrentTime
);
1187 { import core
.stdc
.stdio
; auto aname
= atomName(ev
.target
); stderr
.fprintf("unknown selection comes: '%.*s'", cast(uint)aname
.length
, aname
.ptr
); }
1193 enum doconv
= false; //(isutf8 ? !curterm.isUTF8 : curterm.isUTF8);
1194 //{ import iv.writer; writeln("isutf8: ", isutf8, "; doconv: ", doconv); }
1195 bool wasbrp
= false;
1197 if (curterm.bracketPaste && !wasbrp) {
1199 curterm.putData("\x1b[200~");
1205 if (XGetWindowProperty(dpy
, win
, ev
.property
, ofs
, BUFSIZ
/4, False
, AnyPropertyType
, &type
, &format
, &nitems
, &rem
, &data
)) {
1206 //fprintf(stderr, "Clipboard allocation failed\n");
1209 blen
= cast(int)(nitems
*format
/8);
1211 //{ import iv.writer; writeln("data: [", (cast(const(char)*)data)[0..blen], "]"); }
1212 static if (doconv
) {
1213 // we must convert data first
1215 //{ import iv.writer; writeln("utf->koi: data: [", (cast(const(char)*)data)[0..blen], "]"); }
1216 // we receiving utf8 text which must be converted to koi
1218 while (pos
< blen
) {
1220 // we have partial utf char, try to complete it
1222 //{ import iv.writer; writefln!"utf continues (0x%02X); valid cont: %s"(data[pos], isValidUtf8Cont(cast(char)data[pos])); }
1223 if (isValidUtf8Cont(cast(char)data
[pos
])) {
1224 ucbuf
[ucbufused
++] = cast(char)data
[pos
++];
1225 if (utf8Decode(&wch
, ucbuf
[0..ucbufused
])) {
1227 //{ import iv.writer; writeln("good utf, len=", ucbufused); }
1228 char[1] ch
= uni2koi(wch
);
1229 curterm
.putData(ch
);
1231 } else if (ucbufused
== ucbuf
.length
) {
1233 curterm
.putData(cast(char[])ucbuf
[]);
1238 // this is not valid UTF continuation char, flush buffer
1239 curterm
.putData(cast(char[])ucbuf
[0..ucbufused
]);
1243 assert(ucbufused
== 0);
1245 while (end
< blen
&& !isValidUtf8Start(data
[end
])) ++end
;
1247 curterm
.putData((cast(const(char)*)data
)[pos
..end
]);
1249 if (pos
>= blen
) break;
1251 //{ import iv.writer; writeln("utf start at ", pos); }
1252 // this is valid UTF start char
1253 ucbuf
[ucbufused
++] = data
[pos
++];
1256 // we receiving non-utf8 text which must be converted to utf
1258 while (pos
< blen
) {
1260 while (end
< blen
&& data
[end
] < 0x80) ++end
;
1262 curterm
.putData((cast(const(char)*)data
)[pos
..end
]);
1267 wchar ch
= koi2uni(cast(char)data
[pos
++]);
1268 curterm
.putData(utf8Encode(ucbuf
[], ch
));
1272 //!!!curterm.putData((cast(const(char)*)data)[0..blen]);
1276 // number of 32-bit chunks returned
1277 ofs
+= nitems
*format
/32;
1279 //!!!if (ucbufused > 0) curterm.putData(cast(char[])ucbuf[0..ucbufused]);
1280 //{ import iv.writer; writeln("no more data!"); }
1281 //!!!if (wasbrp) curterm.putData("\x1b[201~");
1284 void prepareCopy (XSelectionRequestEvent
* ev
) {
1285 XSelectionEvent xev
;
1286 string text
= mCurSelection
;
1287 if (text
.length
== 0) text
= ""; // let it point to something
1288 xev
.type
= SelectionNotify
;
1289 xev
.requestor
= ev
.requestor
;
1290 xev
.selection
= ev
.selection
;
1291 xev
.target
= ev
.target
;
1294 xev
.property
= cast(Atom
)None
;
1295 if (ev
.target
== xaTargets
) {
1296 // respond with the supported type
1297 Atom
[3] tlist
= [xaUTF8
, XA_STRING
, xaTargets
];
1298 XChangeProperty(ev
.display
, ev
.requestor
, ev
.property
, XA_ATOM
, 32, PropModeReplace
, cast(ubyte*)tlist
.ptr
, 3);
1299 xev
.property
= ev
.property
;
1300 } else if (ev
.target
== xaUTF8 || ev
.target
== XA_STRING
) {
1301 // maybe i should convert from utf for XA_STRING, but i don't care
1302 XChangeProperty(ev
.display
, ev
.requestor
, ev
.property
, ev
.target
, 8, PropModeReplace
,
1303 cast(ubyte*)text
.ptr
, cast(uint)text
.length
);
1304 xev
.property
= ev
.property
;
1306 /* all done, send a notification to the listener */
1307 if (!XSendEvent(ev
.display
, ev
.requestor
, True
, 0, cast(XEvent
*)&xev
)) {
1308 //fprintf(stderr, "Error sending SelectionNotify event\n");
1312 void clearSelection (XSelectionClearEvent
* ev
) {
1316 bool processXEvents () {
1317 void keypressEvent (ref XKeyEvent e
) {
1318 // blank pointer on any keyboard event
1319 if (OptPtrBlankTime
> 0) blankPointer();
1320 KeySym ksym
= NoSymbol
;
1322 //immutable meta = ((e.state&Mod1Mask) != 0);
1323 //immutable shift = ((e.state&ShiftMask) != 0);
1325 auto len
= XwcLookupString(xic
, &e
, &ch
, 1, &ksym
, &status
);
1326 // if "alt" (aka "meta") is pressed, get keysym name with ignored language switches
1327 if (e
.state
&(Mod1Mask|ControlMask
)) ksym
= XLookupKeysym(&e
, 0);
1328 if (len
!= 1) ch
= 0;
1329 // leave only known mods
1330 //e.state &= ~Mod2Mask; // numlock
1331 e
.state
&= (Mod1Mask|Mod4Mask|ControlMask|ShiftMask
);
1333 if (ksym
== XK_Insert
&& (e
.state
&(Mod1Mask|ShiftMask
)) != 0 && (e
.state
&~(Mod1Mask|ShiftMask
)) == 0) {
1336 // alt+shift: secondary
1337 if (e
.state
== ShiftMask
) selRequest(XA_PRIMARY
);
1338 else if (e
.state
== Mod1Mask
) selRequest(xaClipboard
);
1339 else selRequest(XA_SECONDARY
);
1342 tab
.keypressEvent(ch
, ksym
, X11ModState(e
.state
));
1345 // event processing loop
1347 XEvent event
= void;
1348 while (!quit
&& XPending(dpy
)) {
1349 XNextEvent(dpy
, &event
);
1350 if (XFilterEvent(&event
, cast(Window
)0)) continue;
1351 if (event
.type
== KeymapNotify
) {
1352 XRefreshKeyboardMapping(&event
.xmapping
);
1355 if (event
.xany
.window
!= win
) {
1356 //{ import iv.writer; writeln("CRAP!"); }
1360 switch (event
.type
) {
1362 if (event
.xclient
.message_type
== xaWMProtocols
) {
1363 if (cast(Atom
)event
.xclient
.data
.l
[0] == xaWMDeleteWindow
) {
1366 } else if (event
.xclient
.message_type
== xaTerminalMessage
) {
1367 if (event
.xclient
.data
.l
[0] == WinMsgDirty
) {
1373 keypressEvent(event
.xkey
);
1376 if (!mForceRedraw
) {
1377 auto ee
= cast(XExposeEvent
*)&event
;
1378 drawTermPixRect(ee
.x
, ee
.y
, ee
.width
, ee
.height
);
1379 // fix last redraw time if no dirty areas left
1380 if (!scrbuf
.isDirty
) resetRedrawInfo();
1383 case GraphicsExpose
:
1384 if (!mForceRedraw
) {
1385 drawTermPixRect(event
.xgraphicsexpose
.x
, event
.xgraphicsexpose
.y
, event
.xgraphicsexpose
.width
, event
.xgraphicsexpose
.height
);
1386 // fix last redraw time if no dirty areas left
1387 if (!scrbuf
.isDirty
) resetRedrawInfo();
1389 //event.xgraphicsexpose.count
1391 case VisibilityNotify
:
1392 if (event
.xvisibility
.state
== VisibilityFullyObscured
) {
1393 mWinState
&= ~wsVisible
;
1395 mWinState |
= wsVisible
;
1400 if (!(mWinState
&wsFocused
)) {
1401 mWinState |
= wsFocused
;
1402 scrbuf
.onBlurFocus(true);
1408 if (mWinState
&wsFocused
) {
1409 mWinState
&= ~wsFocused
;
1410 scrbuf
.onBlurFocus(false);
1417 xdoMouseReport(event
);
1419 case SelectionNotify
:
1420 // we got our requested selection, and should paste it
1421 preparePaste(&event
.xselection
);
1423 case SelectionRequest
:
1424 prepareCopy(&event
.xselectionrequest
);
1426 case SelectionClear
:
1427 clearSelection(&event
.xselectionclear
);
1429 case ConfigureNotify
:
1430 processResize(&event
.xconfigure
);
1438 // ////////////////////////////////////////////////////////////////////// //
1439 void resetRedrawInfo () nothrow @trusted @nogc {
1440 mLastDrawTime
= MonoTime
.currTime
;
1441 mForceRedraw
= false;
1442 mForceDirtyRedraw
= false;
1445 void fullRedraw () nothrow @trusted @nogc {
1446 drawTermPixRect(0, 0, winWidth
, winHeight
);
1450 // ////////////////////////////////////////////////////////////////////// //
1452 import std
.algorithm
: max
;
1453 import core
.sys
.posix
.sys
.select
;
1454 import core
.sys
.posix
.sys
.time
: timeval
;
1457 bool mPrevFocused
= false;
1459 void addRFD (int fd
) {
1461 if (fd
+1 > maxfd
) maxfd
= fd
+1;
1466 void addWFD (int fd
) {
1468 if (fd
+1 > maxfd
) maxfd
= fd
+1;
1473 bool timeToHidePointer () {
1474 return (OptPtrBlankTime
> 0 ?
(MonoTime
.currTime
-mLastInputTime
).total
!"msecs" >= OptPtrBlankTime
: false);
1478 int msUntilNextCursorBlink () {
1479 if (!isVisible
) return -1;
1480 if (isFocused
!= mPrevFocused
) return 0; // must update cursor NOW!
1481 if (!scrbuf
.curVisible
&& !tab
.prevcurVis
) return -1; // nothing was changed
1482 auto now
= MonoTime
.currTime
;
1485 auto ntt
= (now
-mCurLastPhaseChange
).total
!"msecs";
1486 if (ntt
>= OptPtrBlinkTime
) return 0; // NOW!
1487 if (scrbuf
.curVisible
!= tab
.prevcurVis || scrbuf
.curX
!= tab
.prevcurX || scrbuf
.curY
!= tab
.prevcurY
) {
1488 ntt
= OptPtrBlinkTime
-ntt
;
1489 // check last move time
1490 auto lmt
= (now
-mCurLastMove
).total
!"msecs";
1491 if (lmt
>= 50) return 0; // NOW! -- cursor state changed
1492 if (50-lmt
< ntt
) return cast(int)(50-lmt
);
1494 return cast(int)ntt
;
1496 // not blinking, check last move time
1497 if (scrbuf
.curVisible
== tab
.prevcurVis
&& scrbuf
.curX
== tab
.prevcurX
&& scrbuf
.curY
== tab
.prevcurY
) return -1; // never
1498 auto lmt
= (now
-mCurLastMove
).total
!"msecs";
1499 if (lmt
>= 150) return 0; // NOW! -- cursor state changed
1500 return cast(int)(150-lmt
);
1504 // <0: already hidden, or not required
1505 int msUntilPointerHiding () {
1506 if (!isVisible ||
!isFocused ||
!mPointerVisible || OptPtrBlankTime
< 1) return -1;
1507 auto now
= MonoTime
.currTime
;
1508 auto ntt
= (now
-mLastInputTime
).total
!"msecs";
1509 if (ntt
>= OptPtrBlankTime
) return 0; // NOW!
1510 return cast(int)(OptPtrBlankTime
-ntt
);
1513 @property int redrawDelay () nothrow @trusted @nogc { return (isFocused ? RedrawRateActive
: RedrawRateInactive
); }
1515 // <0: no redraw required
1516 int msUntilRedraw () {
1517 if (!isVisible ||
!scrbuf
.isDirty
) return -1;
1518 auto now
= MonoTime
.currTime
;
1519 auto ntt
= (now
-mLastDrawTime
).total
!"msecs";
1520 if (ntt
>= redrawDelay
) return 0; // NOW!
1521 return cast(int)(redrawDelay
-ntt
);
1524 void doCursorBlink () {
1525 bool doCursorRedraw
= false;
1526 auto now
= MonoTime
.currTime
;
1527 if (scrbuf
.curVisible
!= tab
.prevcurVis || scrbuf
.curX
!= tab
.prevcurX || scrbuf
.curY
!= tab
.prevcurY
) {
1528 if (tab
.prevcurVis
) {
1529 if (!scrbuf
.isDirty
) {
1530 drawTermChar(tab
.prevcurX
, tab
.prevcurY
, /*asCursor:*/false);
1532 scrbuf
.setDirtyAt(tab
.prevcurX
, tab
.prevcurY
, true);
1535 tab
.prevcurVis
= scrbuf
.curVisible
;
1536 if (tab
.prevcurVis
) doCursorRedraw
= true;
1537 tab
.prevcurX
= scrbuf
.curX
;
1538 tab
.prevcurY
= scrbuf
.curY
;
1542 if (!mPrevFocused
) {
1543 // window was not focused, force cursor redraw
1544 doCursorRedraw
= true;
1546 } else if ((now
-mCurLastPhaseChange
).total
!"msecs" >= OptPtrBlinkTime
) {
1547 // window was focused and cursor must blink
1549 doCursorRedraw
= true;
1553 doCursorRedraw
= (scrbuf
.curVisible
&& (mPrevFocused || mCurPhase
!= 0));
1556 if (scrbuf
.curVisible
&& doCursorRedraw
) {
1557 if (isFocused
== mPrevFocused
&& scrbuf
.isDirty || msUntilRedraw
== 0) {
1558 // mark current cursor cell as dirty, redraw will follow
1559 scrbuf
.setDirtyAt(scrbuf
.curX
, scrbuf
.curY
, true);
1562 drawTermChar(scrbuf
.curX
, scrbuf
.curY
, /*asCursor:*/true);
1566 if (doCursorRedraw
) mCurLastPhaseChange
= now
;
1567 mPrevFocused
= isFocused
;
1570 // return true if we should exit
1572 immutable ovs
= isVisible
;
1573 if (processXEvents()) return true;
1574 if (ovs
!= isVisible
) {
1575 // visibility changed
1577 // becomes hidden, cancel any pending redraw
1578 mForceRedraw
= false;
1580 // becomes visible, do full redraw
1581 // don't need that, as Expose will redraw damaged areas
1583 mForceRedraw = true;
1590 // find max possible wait interval (0: don't wait at all; -1: forever)
1591 int calcMaxWaitTime () {
1593 if (isFocused
!= mPrevFocused
) return 0; // now!
1594 int ntt
= msUntilNextCursorBlink();
1595 //{ import core.stdc.stdio; printf("until blink: %d\n", ntt); }
1596 if (ntt
== 0) return 0; // now!
1598 int ntt1
= msUntilPointerHiding();
1599 //{ import core.stdc.stdio; printf("until hide: %d\n", ntt1); }
1600 if (ntt1
== 0) return 0; // now!
1601 if (ntt
< 0 ||
(ntt1
>= 0 && ntt
> ntt1
)) ntt
= ntt1
;
1604 int ntt1
= msUntilRedraw();
1605 //{ import core.stdc.stdio; printf("until redraw: %d\n", ntt1); }
1606 if (ntt1
== 0) return 0; // now!
1607 if (ntt
< 0 ||
(ntt1
>= 0 && ntt
> ntt1
)) ntt
= ntt1
;
1609 //{ import core.stdc.stdio; printf("RES: %d\n", ntt); }
1610 return (ntt
> 0 ? ntt
+1 : ntt
); // add one msec to really trigger the event
1612 // window is invisible, we can ignore any timed events
1613 return -1; // forever
1617 mCurLastPhaseChange
= MonoTime
.currTime
;
1618 mCurLastMove
= mCurLastPhaseChange
;
1620 mPrevFocused
= isFocused
;
1621 if (tab
.checkFixProcess()) changeTitle(tab
.mLastProcess
[0..tab
.mLastProcessLength
]);
1624 mPointerVisible
= false;
1626 int xfd
= XConnectionNumber(dpy
);
1627 main_loop
: for (;;) {
1628 immutable waitTimeMS
= calcMaxWaitTime();
1632 // setup misc sockets
1635 foreach (immutable int fd
; tab
.appbuf
.getReadFDs
) if (fd
>= 0) addRFD(fd
);
1636 foreach (immutable int fd
; tab
.appbuf
.getWriteFDs
) if (fd
>= 0) addWFD(fd
);
1638 if (waitTimeMS
>= 0) {
1640 tv
.tv_sec
= waitTimeMS
/1000;
1641 tv
.tv_usec
= (waitTimeMS
%1000)*1000;
1642 //{ import core.stdc.stdio; printf("wait(0:%d)\n", waitTimeMS); }
1643 sres
= select(maxfd
, &rds
, &wrs
, null, &tv
);
1644 //{ import core.stdc.stdio; printf("wait(1:%d)\n", waitTimeMS); }
1647 //{ import core.stdc.stdio; printf("forever(0)\n"); }
1648 sres
= select(maxfd
, &rds
, &wrs
, null, null);
1649 //{ import core.stdc.stdio; printf("forever(1)\n"); }
1652 import core
.stdc
.errno
;
1653 if (errno
== EINTR
) continue;
1654 die("select() error");
1660 // check if terminal is dead
1661 if (csigfd
>= 0 && FD_ISSET(csigfd
, &rds
)) processDeadChildren();
1665 if (doXEvents()) break;
1666 // send generated data to app; this can't trigger updates
1667 // also, read data from app and write it to terminal
1668 foreach (immutable int fd
; tab
.appbuf
.getWriteFDs
) if (fd
>= 0 && FD_ISSET(fd
, &wrs
)) tab
.appbuf
.canWriteTo(fd
);
1669 foreach (immutable int fd
; tab
.appbuf
.getReadFDs
) if (fd
>= 0 && FD_ISSET(fd
, &rds
)) tab
.appbuf
.canReadFrom(fd
);
1671 auto nowTime
= MonoTime
.currTime
;
1672 bool doFlush
= false;
1673 // do immediate full redraw only if we are focused
1674 if (mForceRedraw
&& !isFocused
) {
1675 mForceRedraw
= false;
1676 scrbuf
.setFullDirty();
1679 //{ import core.stdc.stdio; stderr.fprintf("FORCE REDRAW!\n"); }
1683 if (mForceDirtyRedraw || msUntilRedraw
== 0) {
1684 if (scrbuf
.isDirty
) {
1685 //{ import core.stdc.stdio; stderr.fprintf("DIRTY REDRAW!\n"); }
1694 if (timeToHidePointer
) blankPointer();
1696 if ((nowTime
-mLastTitleChangeTime
).total
!"msecs" >= 500) {
1697 if (tab
.checkFixProcess()) {
1698 changeTitle(tab
.mLastProcess
[0..tab
.mLastProcessLength
]);
1699 doFlush
= false; // flush already done
1701 mLastTitleChangeTime
= nowTime
;
1703 if (doFlush
) XFlush(dpy
);
1705 if (!tab
.appbuf
.isPtyActive
) break;
1712 write("usage: sampleterm [runcmd...]\n");
1713 throw new ExitException();
1717 string
[] processCLIArgs (string
[] args
) {
1718 string
[] res
= [args
[0]];
1719 bool noMoreArgs
= false;
1720 for (usize aidx
= 1; aidx
< args
.length
; ++aidx
) {
1721 auto arg
= args
[aidx
];
1722 if (noMoreArgs || arg
.length
== 0 || arg
[0] != '-') {
1726 if (arg
== "--") { noMoreArgs
= true; continue; }
1727 stderr
.writeln("FATAL: unknown option: ", arg
.quote
);
1728 throw new ExitError();
1734 int main (string
[] args
) {
1736 args
= processCLIArgs(args
);
1737 auto xw
= new DiWindow(80, 24);
1738 xw
.changeTitle("DiTTY sample terminal");
1739 xw
.appbuf
.execPty(args
[1..$]);
1743 } catch (ExitException e
) {
1744 if (cast(ExitError
)e
) return -1;