using new event api
[knntp.git] / nntpreader.d
blob840436b8afd16a41c1065d3f13f5bb89dd812de7
1 /* DigitalMars NNTP reader
2 * coded by Ketmar // Invisible Vector <ketmar@ketmar.no-ip.org>
3 * Understanding is not required. Only obedience.
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18 module nntpreader is aliced;
20 import core.atomic;
21 import core.time;
22 import std.concurrency;
24 import arsd.simpledisplay;
26 import iv.bclamp;
27 import iv.encoding;
28 import iv.cmdcon;
29 import iv.cmdcongl;
30 import iv.strex;
31 import iv.vfs;
33 import egfx;
34 import nntp;
35 import editor;
36 import twitlist;
37 import group;
38 import egui;
41 // ////////////////////////////////////////////////////////////////////////// //
42 __gshared int oldMouseX, oldMouseY;
43 __gshared ubyte oldMouseButtons;
46 // ////////////////////////////////////////////////////////////////////////// //
47 __gshared Tid updateThreadId;
50 // ////////////////////////////////////////////////////////////////////////// //
51 enum UpThreadCommand {
52 Ping,
53 StartUpdate, // start updating now
54 Quit,
57 void updateThread (Tid ownerTid) {
58 bool doQuit = false;
59 bool doUpdates = false;
60 try {
61 while (!doQuit) {
62 receive(
63 (UpThreadCommand cmd) {
64 final switch (cmd) {
65 case UpThreadCommand.Ping: break;
66 case UpThreadCommand.StartUpdate: doUpdates = true; break;
67 case UpThreadCommand.Quit: doQuit = true; break;
71 if (doQuit) break;
72 if (doUpdates) {
73 doUpdates = false;
74 foreach (immutable gidx, Group g; groups) {
75 if (g.needUpdate) {
76 if (vbwin !is null) vbwin.postEvent(new UpdatingGroupEvent(cast(uint)gidx));
77 g.doUpdate();
78 if (vbwin !is null) vbwin.postEvent(new UpdatingGroupCompleteEvent(cast(uint)gidx));
79 //conwriteln("group '", g.mbase.groupname, "' update complete");
82 //conwriteln("all groups updated");
83 if (vbwin !is null) {
84 vbwin.postEvent(new UpdatingCompleteEvent());
85 //conwriteln("complete event sent");
89 } catch (Throwable e) {
90 // here, we are dead and fucked (the exact order doesn't matter)
91 import core.stdc.stdlib : abort;
92 import core.stdc.stdio : fprintf, stderr;
93 import core.memory : GC;
94 import core.thread : thread_suspendAll;
95 GC.disable(); // yeah
96 thread_suspendAll(); // stop right here, you criminal scum!
97 auto s = e.toString();
98 fprintf(stderr, "\n=== FATAL ===\n%.*s\n", cast(uint)s.length, s.ptr);
99 abort(); // die, you bitch!
104 // ////////////////////////////////////////////////////////////////////////// //
105 void initConsole () {
106 conRegVar!VBufScale(1, 4, "v_scale", "window scale: [1..3]");
108 conRegVar!bool("v_vsync", "sync to video refresh rate?",
109 (ConVarBase self) => vbufVSync,
110 (ConVarBase self, bool nv) {
111 if (vbufVSync != nv) {
112 vbufVSync = nv;
113 postScreenRepaint();
118 conRegFunc!(() {
119 if (groups.length == 0) return;
120 auto gidx = getActiveGroupIndex();
121 if (gidx == 0) return;
122 if (gidx >= 0) groups[gidx].deactivate();
123 gidx = (gidx < 0 ? 0 : gidx-1);
124 assert(gidx >= 0 && gidx < groups.length);
125 groups[gidx].activate();
126 postScreenReady();
127 })("group_prev", "go to previous group");
129 conRegFunc!(() {
130 if (groups.length == 0) return;
131 auto gidx = getActiveGroupIndex();
132 if (gidx == groups.length-1) return;
133 if (gidx >= 0) groups[gidx].deactivate();
134 gidx = (gidx < 0 ? 0 : gidx+1);
135 assert(gidx >= 0 && gidx < groups.length);
136 groups[gidx].activate();
137 postScreenReady();
138 })("group_next", "go to next group");
140 conRegFunc!(() {
141 foreach (immutable gidx, Group g; groups) {
142 if (g.active) {
143 if (g.markAsUnread) postScreenReady();
144 return;
147 })("mark_unread", "mark current message as unread");
149 conRegFunc!(() {
150 foreach (immutable gidx, Group g; groups) {
151 if (g.active) {
152 if (g.markAsRead) postScreenReady();
153 return;
156 })("mark_read", "mark current message as read");
158 conRegFunc!((bool allowNextGroup=false) {
159 foreach (immutable _; 0..groups.length) {
160 uint actgidx = uint.max;
161 foreach (immutable gidx, Group g; groups) {
162 if (!g.active) continue;
163 actgidx = cast(uint)gidx;
164 if (g.moveToNextUnread) {
165 postScreenReady();
166 return;
169 if (!allowNextGroup) break;
170 // move to next group
171 if (actgidx == uint.max) {
172 actgidx = 0;
173 } else {
174 groups[actgidx].releaseContent();
175 groups[actgidx].active = false;
176 actgidx = (actgidx+1)%groups.length;
178 groups[actgidx].active = true;
180 })("next_unread", "move to next unread message; bool arg: can skip to next group?");
182 conRegFunc!(() {
183 if (articleTextTopLine > 0) {
184 articleTextTopLine -= (VBufHeight-guiThreadListHeight-3*10-4)/8;
185 if (articleTextTopLine < 0) articleTextTopLine = 0;
186 postScreenReady();
188 })("artext_page_up", "do pageup on article text");
190 conRegFunc!(() {
191 articleTextTopLine += (VBufHeight-guiThreadListHeight-3*10-4)/8;
192 postScreenReady();
193 })("artext_page_down", "do pagedown on article text");
195 conRegFunc!(() {
196 if (auto g = getActiveGroup) {
197 if (g.moveUp) postScreenReady();
199 })("article_prev", "go to previous article");
201 conRegFunc!(() {
202 if (auto g = getActiveGroup) {
203 if (g.moveDown) postScreenReady();
205 })("article_next", "go to next article");
207 conRegFunc!(() {
208 if (auto g = getActiveGroup) {
209 if (g.movePageUp) postScreenReady();
211 })("article_pgup", "artiles list: page up");
213 conRegFunc!(() {
214 if (auto g = getActiveGroup) {
215 if (g.movePageDown) postScreenReady();
217 })("article_pgdown", "artiles list: page down");
219 conRegFunc!(() {
220 if (auto g = getActiveGroup) {
221 if (g.scrollUp) postScreenReady();
223 })("article_scroll_up", "scroll article list up");
225 conRegFunc!(() {
226 if (auto g = getActiveGroup) {
227 if (g.scrollDown) postScreenReady();
229 })("article_scroll_down", "scroll article list up");
231 conRegFunc!(() {
232 if (auto g = getActiveGroup) {
233 if (g.moveToFirst) postScreenReady();
235 })("article_to_first", "go to first article");
237 conRegFunc!(() {
238 if (auto g = getActiveGroup) {
239 if (g.moveToLast) postScreenReady();
241 })("article_to_last", "go to last article");
243 conRegFunc!(() {
244 foreach (Group g; groups) g.forceUpdating();
245 })("update_all", "mark all groups for updating");
248 // //////////////////////////////////////////////////////////////////// //
249 // debug
250 conRegFunc!(() {
251 if (auto g = getActiveGroup) {
252 static import iv.vfs.io;
254 g.withBase(delegate (abase) {
255 auto sk = new SocketNNTP("news.digitalmars.com");
256 scope(exit) {
257 if (sk.active) sk.quit();
258 sk.close();
261 sk.selectGroup(abase.groupname);
263 if (sk.emptyGroup) return;
265 uint stnum = abase.maxnum+1;
267 if (abase.alist.length == 0) stnum = (sk.hiwater > 1023 ? sk.hiwater-1023 : 0);
268 if (stnum > sk.hiwater) { conwriteln("no new articles"); return; }
270 conwriteln(sk.hiwater+1-stnum, " new articles");
272 // download new articles
273 foreach (immutable uint anum; stnum..sk.hiwater+1) {
274 import std.conv : to;
275 iv.vfs.io.write("\r[", anum, "/", sk.hiwater, "] ... \x1b[K");
276 auto art = sk.getArticle(anum);
277 if (!art.valid) { iv.vfs.io.writeln("SKIP"); continue; }
278 iv.vfs.io.write("OK");
279 art.flags |= Article.Flag.Unread;
280 abase.insert(art);
281 //abase.selfCheck();
283 iv.vfs.io.writeln;
285 abase.selfCheck();
286 abase.writeUpdates();
288 g.buildVisibleList();
291 postScreenReady();
293 })("group_update", "update current group");
295 conRegFunc!(() {
296 if (auto g = getActiveGroup) {
297 if (g.curidxValid) {
298 g.withBase(delegate (abase) {
299 auto art = abase[g.baseidx(g.curidx)];
300 if (art !is null) {
301 conwriteln("============================");
303 conwriteln("replyto: ", art.replyto, "|");
304 if (art.replyto.length) {
305 auto arr = g.mbase[art.replyto];
306 if (arr !is null) conwriteln("*** ", arr.from, " ***");
309 foreach (string s; art.headers) conwriteln(" ", s);
314 })("article_dump_headers", "dump article headers");
316 conRegFunc!(() {
317 if (auto g = getActiveGroup) {
318 g.withBase(delegate (abase) {
319 foreach (immutable idx; 0..g.length) {
320 auto art = abase[g.baseidx(idx)];
321 if (art !is null) {
322 if (art.from.indexOf("ketmar") >= 0) {
323 g.curidx = cast(int)idx;
324 postScreenReady();
325 return;
331 })("find_mine", "find mine article");
335 // ////////////////////////////////////////////////////////////////////////// //
336 void glUpdateTexture () {
337 import iv.glbinds;
339 zxtexbuf[] = 0;
340 clipReset();
342 gxFillRect(0, 0, guiGroupListWidth, VBufHeight, gxRGB!(20, 20, 20));
343 gxVLine(guiGroupListWidth, 0, VBufHeight, gxRGB!(255, 255, 255));
345 gxFillRect(guiGroupListWidth+1, 0, VBufWidth, guiThreadListHeight, gxRGB!(15, 15, 15));
346 gxHLine(guiGroupListWidth+1, guiThreadListHeight, VBufWidth, gxRGB!(255, 255, 255));
348 void drawArticle (Group g, in ref Article art, string title) {
349 import std.format : format;
350 import std.datetime;
352 if (!art.valid || !art.contentLoaded) {
353 lastArticleText.clear();
354 articleTextTopLine = 0;
357 if (!lastArticleText.equal(g, art)) {
358 lastArticleText.set(g, art);
359 articleTextTopLine = 0;
362 clipX0 = guiGroupListWidth+2;
363 clipX1 = VBufWidth-1;
364 clipY0 = guiThreadListHeight+1;
365 clipY1 = VBufHeight-1;
367 gxFillRect(clipX0, clipY0, clipX1-clipX0+1, 3*8+2, gxRGB!(30, 30, 30));
368 gxDrawTextUtf(clipX0+1, clipY0+0*8+1, "From: %s".format(art.fromDC), gxRGB!(0, 128, 128));
369 gxDrawTextUtf(clipX0+1, clipY0+1*8+1, "Subject: %s".format(art.subjDC), gxRGB!(0, 128, 128));
370 auto t = SysTime.fromUnixTime(art.time);
371 string s = "Date: %04d/%02d/%02d %02d:%02d:%02d".format(t.year, t.month, t.day, t.hour, t.minute, t.second);
372 gxDrawTextUtf(clipX0+1, clipY0+2*8+1, s, gxRGB!(0, 128, 128));
374 //TODO: text reflow
375 int y = clipY0+3*8+2;
376 immutable sty = y;
378 int lines = (clipY1-y)/8;
379 if (lines < 1 || art.text.length <= lines) {
380 articleTextTopLine = 0;
381 } else {
382 if (articleTextTopLine+lines > art.text.length) {
383 articleTextTopLine = cast(int)art.text.length-lines;
384 if (articleTextTopLine < 0) articleTextTopLine = 0;
388 uint idx = articleTextTopLine;
389 while (idx < art.text.length && y < VBufHeight) {
390 int qlevel = 0;
391 s = art.text[idx];
392 uint clr = gxRGB!(0, 128, 0);
394 foreach (char ch; s) {
395 if (ch <= ' ') continue;
396 if (ch != '>') break;
397 ++qlevel;
400 clr = gxRGB!( 0, 128+40, 0);
401 if (qlevel) {
402 final switch (qlevel%2) {
403 case 0: clr = gxRGB!(128, 128, 0); break;
404 case 1: clr = gxRGB!( 0, 128, 128); break;
408 /*if (qlevel == 0 && s.length && s[0] <= ' ') {
409 gxDrawText(clipX0+1, y, s, clr);
410 } else*/ {
411 gxDrawTextUtf(clipX0+1, y, s, clr);
414 if (clipY1-y < 8 && art.text.length-idx > 0) {
415 // draw "down" indicator
416 gxDrawTextOutP(clipX1-gxTextWidthP("\x1f")-3, clipY1-7, "\x1f", gxRGB!(255, 255, 255), gxRGB!(0, 69, 69));
419 ++idx;
420 y += 8;
423 if (articleTextTopLine > 0) {
424 gxDrawTextOutP(clipX1-gxTextWidthP("\x1e")-3, sty, "\x1e", gxRGB!(255, 255, 255), gxRGB!(0, 69, 69));
427 if (title.length) {
428 foreach (immutable dy; clipY0+3*8+2..clipY1+1) {
429 foreach (immutable dx; clipX0..clipX1+1) {
430 if ((dx^dy)&1) gxPutPixel(dx, dy, gxRGB!(0, 0, 80));
434 int tx = clipX0+(clipWidth-gxTextWidthScaledUtf(3, title))/2-1;
435 int ty = clipY0+(clipHeight-3*8)/2-1;
436 foreach (immutable dy; -1..1) {
437 foreach (immutable dx; -1..1) {
438 gxDrawTextScaledUtf(3, tx+dx, ty+dy, title, 0);
441 gxDrawTextScaledUtf(3, tx, ty, title, gxRGB!(255, 0, 0));
445 void drawThreadList (Group g) {
446 g.withBase(delegate (abase) {
447 g.makeCurrentVisible();
449 clipX0 = guiGroupListWidth+2;
450 clipX1 = VBufWidth-1-4;
451 clipY0 = 0;
452 clipY1 = guiThreadListHeight-1;
453 immutable uint origX0 = clipX0;
454 immutable uint origX1 = clipX1;
455 immutable uint origY0 = clipY0;
456 immutable uint origY1 = clipY1;
457 int y = -g.ytopofs;
458 //conwriteln(g.msgtop, " : ", g.list.length);
459 uint idx = g.msgtop;
460 while (idx < g.length && y < guiThreadListHeight) {
461 import std.format : format;
462 import std.datetime;
464 if (y >= guiThreadListHeight) break;
465 if (idx >= g.length) break;
467 clipX0 = origX0;
468 clipX1 = origX1;
470 //conwriteln(idx, " : ", g.list.length);
471 if (idx == g.curidx) gxFillRect(clipX0, y, clipX1-clipX0+1, 8, gxRGB!(0, 127, 127));
472 ++clipX0;
473 --clipX1;
475 auto art = abase[g.baseidx(idx)];
477 //uint clr = (idx != g.curidx ? gxRGB!(255, 127, 0) : gxRGB!(255, 255, 255));
478 uint clr = (art.unread ? gxRGB!(255, 255, 255) : gxRGB!(255-60, 127-60, 0));
480 if (g.twited(idx).length) clr = gxRGB!(60, 0, 0);
481 if (art.from.indexOf("ketmar@ketmar.no-ip.org") >= 0) clr = gxRGB!(0, 190, 0);
483 auto t = SysTime.fromUnixTime(art.time);
484 //string s = "%04d/%02d/%02d %02d:%02d".format(t.year, t.month, t.day, t.hour, t.minute);
485 string s = "%02d/%02d %02d:%02d".format(t.month, t.day, t.hour, t.minute);
486 gxDrawTextUtf(clipX1-gxTextWidthUtf(s), y, s, clr);
488 clipX1 -= 12*6+2;
489 gxDrawTextUtf(clipX1-20*6, y, art.fromDC, clr);
491 clipX1 -= 21*6;
492 gxDrawTextUtf(clipX0+art.depth*3, y, art.subjDC, clr);
494 ++idx;
495 y += 8;
498 // draw progressbar
500 //if (idx > g.list.length) idx = cast(uint)g.list.length;
501 clipX0 = origX0;
502 clipX1 = origX1+4;
503 clipY0 = origY0;
504 clipY1 = origY1;
505 int hgt = clipY1-clipY0+1-4;
506 int pix = cast(int)(cast(long)hgt*idx/g.length);
507 if (pix > hgt) pix = hgt;
508 gxVLine(clipX1-2, clipY0+2, pix, gxRGB!(160, 160, 160));
509 // frame
510 gxVLine(clipX1-3, clipY0+2, hgt, gxRGB!(220, 220, 220));
511 gxVLine(clipX1-1, clipY0+2, hgt, gxRGB!(220, 220, 220));
512 gxHLine(clipX1-2, clipY0+1, 1, gxRGB!(220, 220, 220));
513 gxHLine(clipX1-2, clipY1-1, 1, gxRGB!(220, 220, 220));
516 if (g.curidx < g.length) {
517 abase.loadContent(g.baseidx(g.curidx));
518 drawArticle(g, *abase[g.baseidx(g.curidx)], g.twited(g.curidx));
523 foreach (immutable idx, Group g; groups) {
524 int ofsx = 2;
525 int ofsy = 1+cast(int)idx*10;
526 clipReset();
527 if (g.active) gxFillRect(0, ofsy-1, guiGroupListWidth, 10, gxRGB!(0, 127, 127));
528 clipX0 = ofsx-1;
529 clipY0 = ofsy;
530 clipX1 = guiGroupListWidth-3;
531 uint clr = (g.unreadCount ? gxRGB!(255, 255, 0) : gxRGB!(255, 127, 0));
532 if (g.uiFlagUpdating) clr = gxRGB!(0, 255, 255);
533 gxDrawTextOutP(ofsx, ofsy, g.groupname, clr, gxRGB!(0, 0, 0));
534 if (g.active) {
535 drawThreadList(g);
539 foreach (immutable idx, SubWindow w; subwins) {
540 if (idx == subwins.length-1 && w.immuneToLock) break;
541 w.onPaint();
544 if (vbwinLocked) {
545 clipReset();
546 foreach (immutable y; 0..VBufHeight) {
547 foreach (immutable x; 0..VBufWidth) {
548 if ((x^y)&1) gxPutPixel(x, y, gxRGB!(0, 0, 99));
553 if (subwins.length && subwins[$-1].immuneToLock) subwins[$-1].onPaint();
555 if (zxtexid) {
556 glBindTexture(GL_TEXTURE_2D, zxtexid);
557 glTexSubImage2D(GL_TEXTURE_2D, 0, 0/*x*/, 0/*y*/, VBufWidth, VBufHeight, GLTexType, GL_UNSIGNED_BYTE, zxtexbuf.ptr);
558 //glBindTexture(GL_TEXTURE_2D, 0);
563 // ////////////////////////////////////////////////////////////////////////// //
564 class TitlePrompt : SubWindow {
565 string fullname;
566 string name;
567 string msgid;
568 LineEdit etitle;
570 @property string title () const pure nothrow @safe @nogc { return etitle.str; }
572 static __gshared int titleCount = 0;
574 this (string afullname, string aname, string amsgid, string atitle=null) {
575 super(260, 28);
576 if (titleCount != 0) return;
577 fullname = afullname;
578 name = aname;
579 msgid = amsgid;
580 etitle = new LineEdit();
581 etitle.str = atitle;
582 etitle.active = true;
583 immuneToLock = true;
584 add();
587 override void close () {
588 if (titleCount > 0) --titleCount;
589 super.close();
592 override void onPaint () {
593 setupClip();
594 gxDrawWindow(name, gxRGB!(255, 255, 255), gxRGB!(0, 0, 0), gxRGB!(0, 0, 180));
596 setupClientClip();
597 clipX0 += 4;
598 clipX1 -= 4;
599 clipY0 += 2;
600 etitle.onPaint();
603 override void onKey (KeyEvent event) {
604 if (!event.pressed) return;
605 if (event == "Escape") { close(); return; }
606 if (event == "Enter") {
607 twits.update(name, msgid, title, fullname);
608 if (!vbwinLocked) {
609 if (auto g = getActiveGroup) {
610 g.withBase(delegate (abase) {
611 if (g.curidx < g.length && abase[g.baseidx(g.curidx)].depth == 0) {
612 g.moveToNextThread();
614 g.buildVisibleList();
618 close();
619 return;
621 etitle.onKey(event);
624 override void onMouse (MouseEvent event) {
627 override void onChar (dchar ch) {
628 etitle.onChar(ch);
633 // ////////////////////////////////////////////////////////////////////////// //
634 class PostEditor : SubWindow {
635 static __gshared int editorCount = 0;
637 string groupname;
638 string replyid; // replyto
639 Editor editor;
640 int topline;
641 LineEdit esubj;
642 bool subjActive;
644 @property string subj () const pure nothrow @safe @nogc { return esubj.str; }
646 bool doSend () {
647 if (subj.length == 0) return false;
649 try {
650 import std.digest.sha;
651 import std.datetime;
653 conwriteln("sending to '", groupname, "'");
655 SHA256 hash;
656 hash.put(cast(const(ubyte)[])Clock.currTime().toString);
657 hash.put(cast(const(ubyte)[])groupname);
658 hash.put(cast(const(ubyte)[])replyid);
659 hash.put(cast(const(ubyte)[])subj);
660 foreach (immutable idx; 0..editor.lineCount) hash.put(cast(const(ubyte)[])editor[idx]);
661 auto hashres = hash.finish();
662 string hashstr = toHexString!(LetterCase.lower)(hashres);
663 hashstr = "<"~hashstr~"@knews>";
664 conwriteln(" message id: ", hashstr);
665 conwriteln(" reply to : ", replyid);
667 SocketNNTP sk;
668 try {
669 sk = new SocketNNTP("news.digitalmars.com");
670 } catch (Exception e) {
671 conwriteln("connection error: ", e.msg);
672 return false;
674 scope(exit) {
675 if (sk.active) sk.quit();
676 sk.close();
679 sk.selectGroup(groupname);
681 sk.doSend("%s", "POST");
682 sk.doSend("%s", "From: ketmar <ketmar@ketmar.no-ip.org>");
683 sk.doSend("Newsgroups: %s", groupname);
684 sk.doSend("Subject: %s", subj);
685 sk.doSend("Message-ID: %s", hashstr);
686 sk.doSend("%s", "Mime-Version: 1.0");
687 sk.doSend("%s", "Content-Type: text/plain; charset=utf-8; format=flowed; delsp=yes");
688 sk.doSend("%s", "Content-Transfer-Encoding: 8bit");
689 sk.doSend("%s", "User-Agent: knews");
690 if (replyid.length) sk.doSend("In-Reply-To: %s", replyid);
691 sk.doSend("%s", "");
692 foreach (immutable idx; 0..editor.lineCount) {
693 string s = editor[idx];
694 if (s.length > 0 && s[0] == '.') s = "."~s;
695 sk.doSend("%s", s);
697 sk.doSend("%s", ".");
699 auto ln = sk.readLine;
700 conwriteln(ln); // 340 Ok, recommended message-ID <o7dq4o$mpm$1@digitalmars.com>
702 if (ln.length == 0 || ln[0] != '3') throw new Exception(ln.idup);
704 foreach (Group g; groups) {
705 if (g.groupname == groupname) g.forceUpdating();
708 return true;
709 } catch (Exception e) {
710 conwriteln("SENDING ERROR: ", e.msg);
713 return false;
716 this() (string agroupname, in auto ref Article art) {
717 super(506, 253);
718 if (editorCount != 0) return;
719 if (agroupname.length == 0) return;
720 editor = new Editor();
721 esubj = new LineEdit();
722 if (art.valid) {
723 esubj.str = "Re: "~art.subj;
724 replyid = art.msgid;
726 if (art.valid && art.contentLoaded) {
727 string name = TwitList.getName(art.from);
728 if (name.length == 0) name = art.from;
729 editor.addLine(name~" wrote:");
730 editor.addLine("");
731 foreach (string s; art.text) {
732 editor.addLine(">"~s);
734 editor.reformat();
736 if (editor.lineCount == 0) editor.addLine("");
737 immuneToLock = true;
738 groupname = agroupname;
739 add();
742 override void close () {
743 if (editorCount > 0) --editorCount;
744 super.close();
747 final void textClip () {
748 setupClientClip();
749 clipX0 += 1;
750 clipX1 -= 1+4;
751 clipY0 += 12+6;
752 clipY1 -= 1;
755 final void drawSubj () {
756 setupClientClip(); // the easiest way again
757 //gxFillRect(clipX0, clipY0, clipWidth, 14, gxRGB!(0, 0, 120));
758 clipY0 += 1;
759 gxFillRect(clipX0+2, clipY0, clipWidth-4, 10, (subjActive ? gxRGB!(0, 0, 0) : gxRGB!(20, 20, 20)));
760 clipX0 += 4;
761 clipX1 -= 4;
762 clipY0 += 1;
763 esubj.active = subjActive;
764 esubj.onPaint();
767 final void makeCurVisible () {
768 textClip(); // the easiest way to get the size
769 int lvis = clipHeight/8;
770 if (lvis < 1) lvis = 1; // just in case
771 int ltop = topline;
772 int lbot = topline+lvis-1;
773 int cy = editor.cury;
774 if (cy < ltop) {
775 topline = cy;
776 } else if (cy > lbot) {
777 topline = cy-lvis+1;
778 if (topline < 0) topline = 0;
782 final void drawScrollBar () {
783 textClip(); // the easiest way again
784 clipX1 += 4;
785 // frame
786 gxVLine(clipX1-2, clipY0+1, clipHeight-2, gxRGB!(220, 220, 220));
787 gxVLine(clipX1-0, clipY0+1, clipHeight-2, gxRGB!(220, 220, 220));
788 gxHLine(clipX1-1, clipY0, 1, gxRGB!(220, 220, 220));
789 gxHLine(clipX1-1, clipY1, 1, gxRGB!(220, 220, 220));
790 int pix = (clipHeight-2)*editor.cury/editor.lineCount;
791 if (pix > clipHeight-2) pix = clipHeight-2;
792 gxVLine(clipX1-1, clipY0+1, pix, gxRGB!(160, 160, 160));
795 override void onPaint () {
796 setupClip();
798 gxDrawWindow(groupname, gxRGB!(255, 255, 255), gxRGB!(0, 0, 0), gxRGB!(0, 0, 180));
800 textClip();
802 makeCurVisible();
804 int lidx = topline;
805 int y = clipY0;
806 while (lidx < editor.lineCount && y <= clipY1) {
807 string s = editor[lidx];
808 int qlevel = editor.quoteLevel(lidx);
809 uint clr = gxRGB!(220, 220, 0);
810 if (qlevel) {
811 final switch (qlevel%2) {
812 case 0: clr = gxRGB!(128, 128, 0); break;
813 case 1: clr = gxRGB!( 0, 128, 128); break;
816 gxDrawTextUtf(clipX0, y, s, clr);
817 if (!subjActive && lidx == editor.cury) {
818 int xpos = gxTextWidthUtf(s.utfleft(editor.curx));
819 gxVLine(clipX0+xpos, y, 8, gxRGB!(255, 255, 255));
821 ++lidx;
822 y += 8;
824 if (!subjActive && editor.cury >= editor.lineCount) gxVLine(clipX0, y, 8, gxRGB!(255, 255, 255));
825 drawScrollBar();
826 drawSubj();
829 override void onKey (KeyEvent event) {
830 if (!event.pressed) return;
831 if (event == "Escape") { close(); return; }
832 if (event == "C-Enter") {
833 if (!vbwinLocked) {
834 if (doSend()) close();
836 return;
838 if (event == "Tab") { subjActive = !subjActive; return; }
839 if (!subjActive) {
840 if (event == "Enter" || event == "PadEnter") { editor.putChar(editor.SpecCh.Enter); return; }
841 if (event == "Left" || event == "Pad4") { editor.putChar(editor.SpecCh.Left); return; }
842 if (event == "Right" || event == "Pad6") { editor.putChar(editor.SpecCh.Right); return; }
843 if (event == "Up" || event == "Pad8") { editor.putChar(editor.SpecCh.Up); return; }
844 if (event == "Down" || event == "Pad2") { editor.putChar(editor.SpecCh.Down); return; }
845 if (event == "Delete" || event == "PadDot") { editor.putChar(editor.SpecCh.Delete); return; }
846 if (event == "Home" || event == "Pad7") { editor.putChar(editor.SpecCh.Home); return; }
847 if (event == "End" || event == "Pad1") { editor.putChar(editor.SpecCh.End); return; }
848 if (event == "C-Y") { editor.putChar(editor.SpecCh.KillLine); return; }
849 if (event == "S-Insert") {
850 getClipboardText(vbwin, delegate (in char[] text) {
851 if (!closed) editor.putUtf(text[]);
854 } else {
855 esubj.onKey(event);
859 override void onMouse (MouseEvent event) {
862 override void onChar (dchar ch) {
863 if (!subjActive) {
864 if (ch == 8) { editor.putChar(ch); return; }
865 if (ch < ' ' || ch == 127) return;
866 editor.putChar(ch);
867 } else {
868 esubj.onChar(ch);
874 // ////////////////////////////////////////////////////////////////////////// //
875 void main (string[] args) {
876 sdpyWindowClass = "NNTPReader";
878 initConsole();
880 twits = new TwitList();
881 twits.load();
883 threadtwits = new ThreadTwitList();
884 threadtwits.load();
886 //concmdf!"exec \"%q/zxemut.rc\" tan"(configDir);
887 concmd("exec nntp.rc tan");
888 conProcessQueue(); // load config
889 conProcessArgs!true(args);
891 vbufEffScale = VBufScale;
892 vbufEffVSync = vbufVSync;
894 lastWinWidth = winWidthScaled;
895 lastWinHeight = winHeightScaled;
897 vbwin = new SimpleWindow(lastWinWidth, lastWinHeight, "NNTP Reader", OpenGlOptions.yes, Resizablity.allowResizing);
898 vbwin.hideCursor();
900 vbwin.onFocusChange = delegate (bool focused) {
901 if (!focused) {
902 oldMouseButtons = 0;
904 vbfocused = focused;
907 vbwin.windowResized = delegate (int wdt, int hgt) {
908 // TODO: fix gui sizes
910 if (lastWinWidth == wdt && lastWinHeight == hgt) return;
911 glconResize(wdt, hgt);
913 double glwFrac = cast(double)guiGroupListWidth/VBufWidth;
914 double tlhFrac = cast(double)guiThreadListHeight/VBufHeight;
916 vbufEffScale = VBufScale;
917 if (wdt < VBufScale*32) wdt = VBufScale;
918 if (hgt < VBufScale*32) hgt = VBufScale;
919 VBufWidth = (wdt+VBufScale-1)/VBufScale;
920 VBufHeight = (hgt+VBufScale-1)/VBufScale;
921 zxtexbuf.length = VBufWidth*VBufHeight+4;
923 guiGroupListWidth = cast(int)(glwFrac*VBufWidth+0.5);
924 guiThreadListHeight = cast(int)(tlhFrac*VBufHeight+0.5);
926 if (guiGroupListWidth < 12) guiGroupListWidth = 12;
927 if (guiThreadListHeight < 16) guiThreadListHeight = 16;
929 lastWinWidth = wdt;
930 lastWinHeight = hgt;
932 // reinitialize OpenGL texture
934 import iv.glbinds;
936 enum wrapOpt = GL_REPEAT;
937 enum filterOpt = GL_NEAREST; //GL_LINEAR;
938 enum ttype = GL_UNSIGNED_BYTE;
940 if (zxtexid) glDeleteTextures(1, &zxtexid);
941 zxtexid = 0;
942 glGenTextures(1, &zxtexid);
943 if (zxtexid == 0) assert(0, "can't create OpenGL texture");
945 //GLint gltextbinding;
946 //glGetIntegerv(GL_TEXTURE_BINDING_2D, &gltextbinding);
947 //scope(exit) glBindTexture(GL_TEXTURE_2D, gltextbinding);
949 glBindTexture(GL_TEXTURE_2D, zxtexid);
950 glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, wrapOpt);
951 glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, wrapOpt);
952 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filterOpt);
953 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filterOpt);
954 //glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
955 //glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
957 GLfloat[4] bclr = 0.0;
958 glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, bclr.ptr);
960 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, VBufWidth, VBufHeight, 0, GLTexType, GL_UNSIGNED_BYTE, zxtexbuf.ptr);
963 mouseMoved();
965 glUpdateTexture();
966 vbwin.redrawOpenGlSceneNow();
969 vbwin.addEventListener((DoConsoleCommandsEvent evt) {
970 bool sendAnother = false;
971 if (!vbwinLocked) {
972 consoleLock();
973 scope(exit) consoleUnlock();
974 //auto ccwasempty = conQueueEmpty();
975 conProcessQueue();
976 sendAnother = !conQueueEmpty();
977 } else {
978 consoleLock();
979 scope(exit) consoleUnlock();
980 sendAnother = !conQueueEmpty();
982 if (sendAnother) postDoConCommands();
985 vbwin.addEventListener((HideMouseEvent evt) {
986 if (isQuitRequested) { vbwin.close(); return; }
987 if (vbwin.closed) return;
988 if (!repostHideMouse) vbwin.redrawOpenGlSceneNow(); // this will hide the mouse
991 vbwin.addEventListener((ScreenReadyEvent evt) {
992 //conwriteln("screen ready! ", *cast(void**)&evt);
993 if (isQuitRequested) { vbwin.close(); return; }
994 if (vbwin.closed) return;
995 glUpdateTexture();
996 vbwin.redrawOpenGlSceneNow();
999 vbwin.addEventListener((ScreenRepaintEvent evt) {
1000 //conwriteln("screen repaint! ", *cast(void**)&evt);
1001 if (isQuitRequested) { vbwin.close(); return; }
1002 if (vbwin.closed) return;
1003 vbwin.redrawOpenGlSceneNow();
1006 vbwin.addEventListener((QuitEvent evt) {
1007 //conwriteln("quit! ", *cast(void**)&evt);
1008 if (isQuitRequested) { vbwin.close(); return; }
1009 if (vbwin.closed) return;
1010 vbwin.close();
1013 vbwin.addEventListener((UpdatingGroupEvent evt) {
1014 //conwriteln("updating group '", groups[evt.gidx].mbase.groupname);
1015 groups[evt.gidx].uiFlagUpdating = true;
1016 postScreenReady();
1019 vbwin.addEventListener((UpdatingGroupCompleteEvent evt) {
1020 //conwriteln("updating group '", groups[evt.gidx].mbase.groupname);
1021 groups[evt.gidx].uiFlagUpdating = false;
1022 groups[evt.gidx].buildVisibleList();
1023 postScreenReady();
1026 vbwin.addEventListener((UpdatingCompleteEvent evt) {
1027 //conwriteln("UPDATE COMPLETE!");
1028 if (vbwinLocked) {
1029 vbwinLocked = false;
1031 glUpdateTexture();
1032 vbwin.redrawOpenGlSceneNow();
1035 vbwin.redrawOpenGlScene = delegate () {
1036 bool resizeWin = false;
1037 bool rebuildTexture = false;
1040 consoleLock();
1041 scope(exit) consoleUnlock();
1043 if (!conQueueEmpty()) postDoConCommands();
1045 if (VBufScale != vbufEffScale) {
1046 // window scale changed
1047 vbufEffScale = VBufScale;
1048 resizeWin = true;
1050 if (vbufEffVSync != vbufVSync) {
1051 vbufEffVSync = vbufVSync;
1052 vbwin.vsync = vbufEffVSync;
1056 if (resizeWin) {
1057 vbwin.resize(winWidthScaled, winHeightScaled);
1058 glconResize(winWidthScaled, winHeightScaled);
1059 rebuildTexture = true;
1062 if (rebuildTexture) glUpdateTexture();
1064 glMatrixMode(GL_PROJECTION); // for ortho camera
1065 glLoadIdentity();
1066 // left, right, bottom, top, near, far
1067 //glViewport(0, 0, w*vbufEffScale, h*vbufEffScale);
1068 //glOrtho(0, w, h, 0, -1, 1); // top-to-bottom
1069 glViewport(0, 0, VBufWidth*vbufEffScale, VBufHeight*vbufEffScale);
1070 glOrtho(0, VBufWidth, VBufHeight, 0, -1, 1); // top-to-bottom
1071 glMatrixMode(GL_MODELVIEW);
1072 glLoadIdentity();
1074 glEnable(GL_TEXTURE_2D);
1075 glDisable(GL_LIGHTING);
1076 glDisable(GL_DITHER);
1077 //glDisable(GL_BLEND);
1078 glDisable(GL_DEPTH_TEST);
1079 //glEnable(GL_BLEND);
1080 //glBlendFunc(GL_SRC_ALPHA, GL_ONE);
1081 //glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
1082 glDisable(GL_BLEND);
1083 //glDisable(GL_STENCIL_TEST);
1085 if (zxtexid) {
1086 immutable w = VBufWidth;
1087 immutable h = VBufHeight;
1089 glColor4f(1, 1, 1, 1);
1090 glBindTexture(GL_TEXTURE_2D, zxtexid);
1091 //scope(exit) glBindTexture(GL_TEXTURE_2D, 0);
1092 glBegin(GL_QUADS);
1093 glTexCoord2f(0.0f, 0.0f); glVertex2i(0, 0); // top-left
1094 glTexCoord2f(1.0f, 0.0f); glVertex2i(w, 0); // top-right
1095 glTexCoord2f(1.0f, 1.0f); glVertex2i(w, h); // bottom-right
1096 glTexCoord2f(0.0f, 1.0f); glVertex2i(0, h); // bottom-left
1097 glEnd();
1100 if (vArrowTextureId) {
1101 if (isMouseVisible) {
1102 int px = oldMouseX/vbufEffScale;
1103 int py = oldMouseY/vbufEffScale;
1104 glEnable(GL_BLEND);
1105 //glBlendFunc(GL_SRC_ALPHA, GL_ONE);
1106 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
1107 glColor4f(1, 1, 1, 1);
1108 glBindTexture(GL_TEXTURE_2D, vArrowTextureId);
1109 //scope(exit) glBindTexture(GL_TEXTURE_2D, 0);
1110 glBegin(GL_QUADS);
1111 glTexCoord2f(0.0f, 0.0f); glVertex2i(px, py); // top-left
1112 glTexCoord2f(1.0f, 0.0f); glVertex2i(px+16, py); // top-right
1113 glTexCoord2f(1.0f, 1.0f); glVertex2i(px+16, py+8); // bottom-right
1114 glTexCoord2f(0.0f, 1.0f); glVertex2i(px, py+8); // bottom-left
1115 glEnd();
1119 glconDraw();
1121 if (isQuitRequested()) vbwin.postEvent(new QuitEvent());
1124 static if (is(typeof(&vbwin.closeQuery))) {
1125 vbwin.closeQuery = delegate () { concmd("quit"); };
1128 vbwin.visibleForTheFirstTime = delegate () {
1129 import iv.glbinds;
1130 vbwin.setAsCurrentOpenGlContext();
1131 vbufEffVSync = vbufVSync;
1132 vbwin.vsync = vbufEffVSync;
1134 // initialize OpenGL texture
1136 enum wrapOpt = GL_REPEAT;
1137 enum filterOpt = GL_NEAREST; //GL_LINEAR;
1138 enum ttype = GL_UNSIGNED_BYTE;
1140 glGenTextures(1, &zxtexid);
1141 if (zxtexid == 0) assert(0, "can't create OpenGL texture");
1143 //GLint gltextbinding;
1144 //glGetIntegerv(GL_TEXTURE_BINDING_2D, &gltextbinding);
1145 //scope(exit) glBindTexture(GL_TEXTURE_2D, gltextbinding);
1147 glBindTexture(GL_TEXTURE_2D, zxtexid);
1148 glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, wrapOpt);
1149 glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, wrapOpt);
1150 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filterOpt);
1151 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filterOpt);
1152 //glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
1153 //glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
1155 GLfloat[4] bclr = 0.0;
1156 glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, bclr.ptr);
1158 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, VBufWidth, VBufHeight, 0, GLTexType, GL_UNSIGNED_BYTE, zxtexbuf.ptr);
1161 createArrowTexture();
1163 glconInit(winWidthScaled, winHeightScaled);
1164 vbwin.redrawOpenGlSceneNow();
1166 updateThreadId = spawn(&updateThread, thisTid);
1169 postScreenReady();
1170 repostHideMouse();
1172 vbwin.eventLoop(1000*10,
1173 delegate () {
1174 if (vbwin.closed) return;
1175 if (isQuitRequested) { vbwin.close(); return; }
1176 bool needUpdate = false;
1177 foreach (Group g; groups) if (g.needUpdate()) { needUpdate = true; break; }
1178 if (needUpdate) {
1179 //vbwinLocked = true;
1180 updateThreadId.send(UpThreadCommand.StartUpdate);
1181 //glUpdateTexture();
1182 //vbwin.redrawOpenGlSceneNow();
1184 if (!conQueueEmpty()) postDoConCommands();
1186 delegate (KeyEvent event) {
1187 if (vbwin.closed) return;
1188 if (isQuitRequested) { vbwin.close(); return; }
1189 scope(exit) {
1190 if (!conQueueEmpty()) postDoConCommands();
1192 if (glconKeyEvent(event)) {
1193 //evScrReady.send(EventId.ScreenRepaint);
1194 postScreenRepaint();
1195 return;
1197 if (event.pressed && event == "C-Q") { concmd("quit"); return; }
1198 if (subwins.length) {
1199 if (!vbwinLocked || subwins[$-1].immuneToLock) {
1200 if (event.pressed) ignoreSubWinChar = false;
1201 subwins[$-1].onKey(event);
1202 postScreenReady();
1203 } else {
1204 ignoreSubWinChar = false;
1206 return;
1208 if (event.pressed) {
1209 //conwriteln("key: ", event.toStr, ": ", event.modifierState&ModifierState.windows);
1210 if (event == "N") { concmd("next_unread ona"); return; }
1211 if (event == "S-N") { concmd("next_unread tan"); return; }
1212 if (event == "U") { concmd("mark_unread"); return; }
1213 if (event == "C-R") { concmd("mark_read"); return; }
1214 if (event == "Space") { concmd("artext_page_down"); return; }
1215 if (event == "S-Space") { concmd("artext_page_up"); return; }
1216 if (event == "Up" || event == "Pad8") { concmd("article_prev"); return; }
1217 if (event == "Down" || event == "Pad2") { concmd("article_next"); return; }
1218 if (event == "PageUp" || event == "Pad9") { concmd("article_pgup"); return; }
1219 if (event == "PageDown" || event == "Pad3") { concmd("article_pgdown"); return; }
1220 if (event == "Home" || event == "Pad7") { concmd("article_to_first"); return; }
1221 if (event == "End" || event == "Pad1") { concmd("article_to_last"); return; }
1222 if (event == "C-Up" || event == "C-Pad8") { concmd("article_scroll_up"); return; }
1223 if (event == "C-Down" || event == "C-Pad2") { concmd("article_scroll_down"); return; }
1224 if (event == "C-PageUp" || event == "C-Pad9") { concmd("group_prev"); return; }
1225 if (event == "C-PageDown" || event == "C-Pad3") { concmd("group_next"); return; }
1226 if (event == "C-M-U") { concmd("group_update"); return; }
1227 if (event == "C-H") { concmd("article_dump_headers"); return; }
1228 if (event == "C-S-I") { concmd("update_all"); return; }
1229 if (event == "C-Insert") { concmd("find_mine"); return; }
1230 if (event == "T") {
1231 if (auto g = getActiveGroup) {
1232 g.withBase(delegate (abase) {
1233 if (g.curidx < g.length) {
1234 if (auto art = abase[g.baseidx(g.curidx)]) {
1235 auto t = twits.check(*art);
1236 if (t) {
1237 new TitlePrompt(art.from, t.name, art.msgid, t.title);
1238 } else {
1239 new TitlePrompt(art.from, art.from, art.msgid, "");
1245 return;
1247 if (event == "C-M-K") {
1248 if (auto g = getActiveGroup) {
1249 g.withBase(delegate (abase) {
1250 if (g.curidx < g.length) {
1251 if (auto art = abase[g.baseidx(g.curidx)]) {
1252 if (art.rootidx != uint.max) {
1253 art = abase[art.rootidx];
1254 assert(art !is null);
1256 threadtwits.add(art.msgid);
1257 g.moveToNextThread();
1258 g.buildVisibleList();
1263 return;
1265 if (event == "R") {
1266 if (auto g = getActiveGroup) {
1267 g.withBase((abase) {
1268 if (g.curidx < g.length) {
1269 abase.loadContent(g.baseidx(g.curidx));
1270 if (auto art = abase[g.baseidx(g.curidx)]) {
1271 new PostEditor(g.groupname, *art);
1276 return;
1278 if (event == "S-P") {
1279 if (auto g = getActiveGroup) {
1280 g.withBase((abase) {
1281 new PostEditor(g.groupname, Article());
1284 return;
1286 if (event == "S-Enter") {
1287 if (auto g = getActiveGroup) {
1288 g.withBase((abase) {
1289 if (g.curidx < g.length) {
1290 if (auto art = abase[g.baseidx(g.curidx)]) {
1291 import std.stdio : File;
1292 import std.process;
1293 //http://forum.dlang.org/post/hhyqqpoatbpwkprbvfpj@forum.dlang.org
1294 string id = art.msgid;
1295 if (id.length > 2 && id[0] == '<' && id[$-1] == '>') id = id[1..$-1];
1296 spawnProcess(
1297 ["opera", "http://forum.dlang.org/post/"~id],
1298 File("/dev/zero"), File("/dev/null"), File("/dev/null"),
1304 return;
1307 postScreenRepaint();
1309 delegate (MouseEvent event) {
1310 if (vbwin.closed) return;
1311 scope(exit) {
1312 if (!conQueueEmpty()) postDoConCommands();
1314 oldMouseX = event.x;
1315 oldMouseY = event.y;
1316 mouseMoved();
1317 if (subwins.length) {
1318 if (!vbwinLocked || subwins[$-1].immuneToLock) {
1319 subwins[$-1].onMouse(event);
1320 postScreenReady();
1322 return;
1325 if (event.type == MouseEventType.motion) {
1326 import std.math : abs;
1327 int dx = event.dx;
1328 int dy = -event.dy;
1329 if (vbufEffScale > 1 && abs(dx) > 1) dx /= 2;//vbufEffScale;
1330 if (vbufEffScale > 1 && abs(dy) > 1) dy /= 2;//vbufEffScale;
1331 concmdf!"kmouse_move %s %s"(dx, dy);
1332 if (mouseGrabbed && vbfocused) vbwin.warpMouse(winWidthScaled/2, winHeightScaled/2);
1333 return;
1335 if (event.type == MouseEventType.buttonPressed) {
1336 if (event.button == MouseButton.left) concmd(!Config.kempstonMSwapB ? "kmouse_down 0" : "kmouse_down 1");
1337 if (event.button == MouseButton.right) concmd(!Config.kempstonMSwapB ? "kmouse_down 1" : "kmouse_down 0");
1338 if (event.button == MouseButton.middle) concmd("kmouse_down 2");
1339 if (event.button == MouseButton.wheelUp) concmd("kmouse_wheel -1");
1340 if (event.button == MouseButton.wheelDown) concmd("kmouse_wheel 1");
1341 return;
1343 if (event.type == MouseEventType.buttonReleased) {
1344 if (event.button == MouseButton.left) concmd(!Config.kempstonMSwapB ? "kmouse_up 0" : "kmouse_up 1");
1345 if (event.button == MouseButton.right) concmd(!Config.kempstonMSwapB ? "kmouse_up 1" : "kmouse_up 0");
1346 if (event.button == MouseButton.middle) concmd("kmouse_up 2");
1347 return;
1350 postScreenRepaint();
1352 delegate (dchar ch) {
1353 if (vbwin.closed) return;
1354 scope(exit) {
1355 if (!conQueueEmpty()) postDoConCommands();
1357 if (glconCharEvent(ch)) {
1358 //evScrReady.send(EventId.ScreenRepaint);
1359 postScreenRepaint();
1360 return;
1362 if (subwins.length) {
1363 if (!vbwinLocked || subwins[$-1].immuneToLock) {
1364 if (!ignoreSubWinChar) subwins[$-1].onChar(ch);
1365 ignoreSubWinChar = false;
1366 postScreenReady();
1367 } else {
1368 ignoreSubWinChar = false;
1370 return;
1374 updateThreadId.send(UpThreadCommand.Quit);