fixed but with invalid text in ticker
[amper.git] / amper.d
blob5b992390d49d27234341a775419eb4b80f69f7b4
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, either version 3 of the License, or
7 * (at your option) any later version.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 module amper;
19 import std.concurrency;
21 import arsd.color;
22 import arsd.image;
23 import arsd.simpledisplay;
25 import iv.cmdcon;
26 import iv.cmdcongl;
27 import iv.sdpyutil;
28 import iv.strex;
29 import iv.vfs;
31 import aplayer;
33 import egfx;
35 import amperrpcsrv;
36 import amperskin;
37 import amperopts;
40 // ////////////////////////////////////////////////////////////////////////// //
41 public GxRect getWorkArea () {
42 GxRect rc;
43 getWorkAreaRect(rc.x0, rc.y0, rc.width, rc.height);
44 return rc;
48 // ////////////////////////////////////////////////////////////////////////// //
49 class GlobalHotkeyEx : GlobalHotkey {
50 string cmd;
51 override void doHandle () { concmd(cmd); }
53 this (ConString kname, ConString acmd) {
54 super(kname);
55 cmd = acmd.idup;
59 __gshared GlobalHotkeyEx[] ghbindings; // key is key, value is command
62 void removeAllBindings () {
63 char[128] knbuf;
64 foreach (GlobalHotkeyEx b; ghbindings) {
65 try { GlobalHotkeyManager.unregister(b.key.toStrBuf(knbuf[])); } catch (Exception e) {}
67 ghbindings.length = 0;
68 ghbindings.assumeSafeAppend;
72 void addBinding (ConString key, ConString cmd) {
73 cmd = cmd.xstrip;
74 key = key.xstrip;
75 if (cmd.length == 0) {
76 GlobalHotkeyManager.unregister(key);
77 foreach (immutable idx, GlobalHotkeyEx bind; ghbindings) {
78 if (bind.key == key) {
79 foreach (immutable cc; idx+1..ghbindings.length) ghbindings[cc-1] = ghbindings[cc];
80 ghbindings[$-1] = null;
81 ghbindings.length -= 1;
82 ghbindings.assumeSafeAppend;
83 return;
86 return;
88 try {
89 char[128] knbuf;
90 auto bind = new GlobalHotkeyEx(key, cmd);
91 GlobalHotkeyManager.unregister(bind.key.toStrBuf(knbuf[]));
92 GlobalHotkeyManager.register(bind);
93 foreach (immutable idx, GlobalHotkeyEx b; ghbindings) {
94 if (b.key == key) {
95 ghbindings[idx] = bind;
96 return;
99 auto optr = ghbindings.ptr;
100 ghbindings ~= bind;
101 if (optr !is ghbindings.ptr) {
102 import core.memory : GC;
103 if (ghbindings.ptr is GC.addrOf(ghbindings.ptr)) {
104 GC.setAttr(ghbindings.ptr, GC.BlkAttr.NO_INTERIOR);
107 } catch (Exception e) {
108 conwriteln("ERROR registering hotkey: '", key, "'");
113 // ////////////////////////////////////////////////////////////////////////// //
114 __gshared SimpleWindow sdhint;
115 __gshared string hinttext = "not playing";
116 __gshared Image hintbackbuf;
117 __gshared uint* hintvbuf;
118 __gshared Timer hintHideTimer;
119 __gshared int hintX, hintY;
122 // ////////////////////////////////////////////////////////////////////////// //
123 void setHint(T:const(char)[]) (T str) {
124 static if (is(T == typeof(null))) {
125 setHint("");
126 } else {
127 if (hinttext == str) return;
128 static if (is(T == string)) hinttext = str; else hinttext = str.idup;
129 if (sdhint is null || sdhint.closed) return;
130 if (sdhint.hidden) return;
131 createHintWindow (hintX, hintY);
132 //repaintHintWindow(false);
137 // ////////////////////////////////////////////////////////////////////////// //
138 GxPoint hintWindowTextOffset () { return GxPoint(3, 2); }
140 GxSize hintWindowSize () {
141 int textWidth = gxTextWidthUtf(hinttext)+6;
142 if (textWidth < 8) textWidth = 8;
143 return GxSize(textWidth, gxTextHeightUtf+4);
147 void createHintWindow (int x, int y) {
148 if (sdhint !is null) {
149 sdhint.close();
150 sdhint = null;
153 sdpyWindowClass = "AMPER_HINT_WINDOW";
154 auto wsz = hintWindowSize();
156 auto wrc = getWorkArea();
157 int nx = x;
158 int ny = y;
159 if (nx+wsz.width > wrc.x1) nx = wrc.x1-wsz.width+1;
160 if (nx < wrc.x0) nx = wrc.x0;
161 if (ny+wsz.height > wrc.y1) ny = wrc.y1-wsz.height+1;
162 if (ny < wrc.y0) ny = wrc.y0;
163 hintX = nx;
164 hintY = ny;
166 sdhint = new SimpleWindow(wsz.width, wsz.height, "AmperHint", OpenGlOptions.no, Resizability.fixedSize, WindowTypes.undecorated);
167 sdhint.setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_DOCK"(sdhint.display)); // sorry for this hack
168 sdhint.moveResize(hintX, hintY, wsz.width, wsz.height);
169 repaintHintWindow(true);
173 void repaintHintWindow (bool forced) {
174 if (sdhint is null || sdhint.closed) return;
175 if (!forced && sdhint.hidden) return;
177 auto wsz = hintWindowSize();
179 if (hintbackbuf is null || hintbackbuf.width != wsz.width || hintbackbuf.height != wsz.height) {
180 import core.stdc.stdlib : realloc;
181 hintbackbuf = new Image(wsz.width, wsz.height);
182 hintvbuf = cast(uint*)realloc(hintvbuf, hintbackbuf.width*hintbackbuf.height*hintvbuf[0].sizeof);
183 if (hintvbuf is null) assert(0, "out of memory");
186 if (hintbackbuf !is null) {
187 auto VBufWidthSave = VBufWidth;
188 auto VBufHeightSave = VBufHeight;
189 auto vglTexBufSave = vglTexBuf;
190 VBufWidth = hintbackbuf.width;
191 VBufHeight = hintbackbuf.height;
192 vglTexBuf = hintvbuf;
193 scope(exit) {
194 VBufWidth = VBufWidthSave;
195 VBufHeight = VBufHeightSave;
196 vglTexBuf = vglTexBufSave;
198 gxClipReset();
199 gxClearScreen(gxRGB!(255, 255, 0));
200 gxDrawRect(0, 0, VBufWidth, VBufHeight, gxRGB!(0, 0, 0));
201 auto tofs = hintWindowTextOffset;
202 gxClipRect.shrinkBy(tofs.x, tofs.y);
203 gxDrawTextUtf(gxClipRect.x0, gxClipRect.y0, hinttext, gxRGB!(0, 0, 0));
204 //copyVBufToImage();
206 import core.stdc.string : memcpy;
207 memcpy(hintbackbuf.getDataPointer, hintvbuf, hintbackbuf.width*hintbackbuf.height*4);
210 auto painter = sdhint.draw();
211 painter.drawImage(Point(0, 0), hintbackbuf);
214 flushGui();
218 // ////////////////////////////////////////////////////////////////////////// //
219 __gshared NotificationAreaIcon trayicon;
220 __gshared Image trayimage;
221 __gshared MemoryImage icon; // 0: normal
224 void hideShowWindows () {
225 if (sdampwin is null || sdampwin.closed) return;
226 if (sdampwin.hidden) {
227 sdampwin.show();
228 if (plVisible) {
229 if (sdplwin is null || sdplwin.closed) {
230 if (dgCreatePListWindow !is null) dgCreatePListWindow();
231 } else {
232 sdplwin.show();
235 if (eqVisible) {
236 if (sdeqwin is null || sdeqwin.closed) {
237 if (dgCreateEqWindow !is null) dgCreateEqWindow();
238 } else {
239 sdeqwin.show();
242 switchToWindow(sdampwin);
243 flushGui();
244 } else {
245 if (sdeqwin !is null && !sdeqwin.closed) sdeqwin.hide();
246 if (sdplwin !is null && !sdplwin.closed) sdplwin.hide();
247 sdampwin.hide();
252 void prepareTrayIcon () {
253 static immutable ubyte[] nticonpng = cast(immutable(ubyte)[])import("skins/notifyicon.png");
254 //icon = readPng("skins/notifyicon.png");
255 icon = imageFromPng(readPng(nticonpng));
256 trayimage = Image.fromMemoryImage(icon);
257 trayicon = new NotificationAreaIcon("Amper", trayimage, (int x, int y, MouseButton button, ModifierState mods) {
258 //conwritefln!"x=%d; y=%d; button=%u; mods=0x%04x"(x, y, button, mods);
259 if (button == MouseButton.middle) {
260 //trayicon.close();
261 //trayicon = null;
262 concmd("quit");
263 return;
265 if (button == MouseButton.left) {
266 concmd("win_toggle");
267 return;
270 trayicon.onEnter = delegate (int x, int y, ModifierState mods) {
271 //conwritefln!"icon enter: x=%d; y=%d; mods=0x%04x"(x, y, mods);
273 conwritefln!"icon enter: x=%d; y=%d; mods=0x%04x"(x, y, mods);
274 int wx, wy, wdt, hgt;
275 trayicon.getWindowRect(wx, wy, wdt, hgt);
276 conwriteln("window rect: wx=", wx, "; wy=", wy, "; wdt=", wdt, "; hgt=", hgt);
278 if (sdhint is null || sdhint.hidden) {
279 createHintWindow(x+18, y+2);
280 if (hintHideTimer !is null) hintHideTimer.destroy();
281 hintHideTimer = new Timer(3000, delegate () {
282 if (hintHideTimer !is null) {
283 hintHideTimer.destroy();
284 hintHideTimer = null;
285 if (sdhint !is null && !sdhint.closed) sdhint.hide();
290 trayicon.onLeave = delegate () {
291 //conwriteln("icon leave");
292 //if (sdhint !is null && !sdhint.closed) sdhint.hide();
297 // ////////////////////////////////////////////////////////////////////////// //
298 __gshared ubyte vbNewScale = 1;
301 // ////////////////////////////////////////////////////////////////////////// //
302 class ScrollTitleEvent {}
303 __gshared ScrollTitleEvent evScrollTitle;
305 shared static this () {
306 evScrollTitle = new ScrollTitleEvent();
310 // ////////////////////////////////////////////////////////////////////////// //
311 __gshared bool mainDrag = false;
312 __gshared int mainDrawPrevX, mainDrawPrevY;
315 // ////////////////////////////////////////////////////////////////////////// //
316 void closeAllIfMainIsClosed () {
317 bool doQuit =
318 (glconCtlWindow is null || glconCtlWindow.closed) ||
319 (sdampwin is null || sdampwin.closed);
320 if (doQuit) {
321 if (sdhint !is null && !sdhint.closed) sdhint.close();
322 if (sdeqwin !is null && !sdeqwin.closed) sdeqwin.close();
323 if (sdplwin !is null && !sdplwin.closed) sdplwin.close();
324 if (sdampwin !is null && !sdampwin.closed) sdampwin.close();
325 if (glconCtlWindow !is null && !glconCtlWindow.closed) glconCtlWindow.close();
330 // ////////////////////////////////////////////////////////////////////////// //
331 // create hidden control window
332 void createCtlWindow () {
333 sdpyWindowClass = "AMPER_PLAYER_CTL";
334 glconCtlWindow = new SimpleWindow(1, 1, "AmperCtl", OpenGlOptions.no, Resizability.fixedSize, WindowTypes.hidden);
336 glconCtlWindow.onClosing = delegate () {
337 //conwriteln("closing ctl window...");
338 if (sdeqwin !is null && !sdeqwin.closed) sdeqwin.close();
339 if (sdplwin !is null && !sdplwin.closed) sdplwin.close();
340 if (sdampwin !is null && !sdampwin.closed) sdampwin.close();
343 glconCtlWindow.onDestroyed = delegate () {
344 //conwriteln("ctl window destroyed");
345 closeAllIfMainIsClosed();
348 glconCtlWindow.addEventListener((QuitEvent evt) {
349 scope(exit) if (!conQueueEmpty()) glconPostDoConCommands();
350 scope(exit) closeAllIfMainIsClosed();
351 if (glconCtlWindow.closed) return;
352 if (isQuitRequested) { glconCtlWindow.close(); return; }
353 concmd("quit");
356 void rebuildRepaint () {
357 scope(exit) if (!conQueueEmpty()) glconPostDoConCommands();
358 scope(exit) closeAllIfMainIsClosed();
359 if (glconCtlWindow.closed) return;
360 if (isQuitRequested) { glconCtlWindow.close(); return; }
361 //conwriteln("rebuilding screen");
363 //if (aplayIsPlaying) ampMain.curtime = aplayCurTime;
365 if (sdampwin !is null && !sdampwin.closed && !sdampwin.hidden) sdampwin.redraw();
366 if (sdplwin !is null && !sdplwin.closed && !sdplwin.hidden) sdplwin.redraw();
367 if (sdeqwin !is null && !sdeqwin.closed && !sdeqwin.hidden) sdeqwin.redraw();
369 //glFlush();
370 flushGui();
373 glconCtlWindow.addEventListener((GLConScreenRebuildEvent evt) { rebuildRepaint(); });
374 glconCtlWindow.addEventListener((GLConScreenRepaintEvent evt) { rebuildRepaint(); });
376 glconCtlWindow.addEventListener((GLConDoConsoleCommandsEvent evt) {
377 scope(exit) if (!conQueueEmpty()) glconPostDoConCommands();
378 scope(exit) closeAllIfMainIsClosed();
379 bool sendAnother = false;
380 bool prevVisible = isConsoleVisible;
382 consoleLock();
383 scope(exit) consoleUnlock();
384 conProcessQueue();
385 sendAnother = !conQueueEmpty();
387 if (glconCtlWindow.closed) return;
388 if (isQuitRequested) { glconCtlWindow.close(); return; }
389 if (sendAnother) glconPostDoConCommands();
391 if (prevVisible || isConsoleVisible) glconPostScreenRepaintDelayed();
392 if (vbNewScale != vbufEffScale) glconPostScreenRepaint();
395 if (vbNewScale != vglWindowScale(glconCtlWindow)) {
396 vbNewScale = vglScaleWindow(glconCtlWindow, vbNewScale);
397 vglScaleWindow(sdplwin, vbNewScale);
398 } else {
399 glconPostScreenRebuild();
402 glconPostScreenRebuild();
405 glconCtlWindow.addEventListener((ScrollTitleEvent evt) {
406 scope(exit) if (!conQueueEmpty()) glconPostDoConCommands();
407 scope(exit) closeAllIfMainIsClosed();
408 if (glconCtlWindow.closed) return;
409 if (isQuitRequested) { glconCtlWindow.close(); return; }
410 //conwriteln("scrolling title");
411 ampMain.scrollTitle();
412 if (!glconCtlWindow.eventQueued!ScrollTitleEvent) glconCtlWindow.postTimeout(evScrollTitle, 100);
413 glconPostScreenRebuild();
416 glconCtlWindow.addEventListener((EventFileLoaded evt) {
417 if (!evt.success) {
418 conwriteln("ERROR loading '", evt.filename, "'");
419 ampMain.newSong("not playing");
420 setHint("not playing");
421 concmd("song_next");
422 } else {
423 string cursong = evt.artist~" \u2014 "~evt.title;
424 setHint(cursong);
425 ampMain.newSong(cursong);
426 //conwriteln("playing '", evt.filename, "': ", evt.artist, " -- ", evt.title);
430 glconCtlWindow.addEventListener((EventFileScanned evt) {
431 //conwriteln("scanned: '", evt.filename, "': ", evt.success);
432 if (ampPList is null) return;
433 if (evt.success) {
434 ampPList.scanResult(evt.filename, evt.album, evt.artist, evt.title, evt.durationms);
435 } else {
436 ampPList.scanResultFailed(evt.filename);
440 glconCtlWindow.addEventListener((EventFileComplete evt) {
441 //glconCtlWindow.close();
442 setHint("not playing");
443 concmd("song_next tan");
448 // ////////////////////////////////////////////////////////////////////////// //
449 void createAmpWindow () {
450 ampMain = new AmpMainWindow();
451 sdampwin = new EgfxWindow(ampMain, "AMPER_PLAYER", "Amper");
452 if (!sdampwin.eventQueued!ScrollTitleEvent) glconCtlWindow.postEvent(evScrollTitle);
456 // ////////////////////////////////////////////////////////////////////////// //
457 void createPListWindow () {
458 ampPList = new AmpPListWindow();
459 sdplwin = new EgfxWindow(ampPList, "AMPER_PLAYLIST", "Amper Playlist", 25, 29);
463 void createEqWindow () {
464 ampEq = new AmpEqWindow();
465 sdeqwin = new EgfxWindow(ampEq, "AMPER_EQIALIZER", "Amper Eqializer");
469 // ////////////////////////////////////////////////////////////////////////// //
471 void fakeScanDir () {
472 ampPList.appendListItem(PListItem("Sonata Arctica \u2014 Replica", "/mnt/muzax/wtf", 142));
473 ampPList.appendListItem(PListItem("Zonata \u2014 Geronimo", "/mnt/muzax/wtf", 242));
475 foreach (immutable int idx; 3..42) {
476 import std.format : format;
477 ampPList.appendListItem(PListItem("song #%d".format(idx), "/mnt/muzax/wtf", 100+idx));
483 // ////////////////////////////////////////////////////////////////////////// //
484 void scanDir (ConString path, bool append) {
485 void appendFile (const(char)[] fname) {
486 ampPList.appendListItem(fname.idup);
489 // scan directory
490 import std.file;
491 try {
492 if (!append) ampPList.clear();
493 if (path.exists && path.isFile) { appendFile(path); return; }
494 foreach (DirEntry de; dirEntries(path.idup, SpanMode.shallow)) {
495 if (!de.isFile) continue;
496 appendFile(de.name);
498 } catch (Exception e) {
499 conwriteln("ERROR scanning: ", e.msg);
501 //if (modeShuffle) ampPList.state.curitem = ampPList.findShuffleFirst();
502 //conwriteln(ampPList.findShuffleFirst(), " : ", ampPList.state.shuffleidx, " : ", ampPList.state.curplayingitem);
506 // ////////////////////////////////////////////////////////////////////////// //
507 void main (string[] args) {
508 //vbNewScale = 2;
509 //vbufEffScale = 2;
510 glconShowKey = "M-Grave";
512 //conRegVar!vbNewScale(1, 8, "v_scale", "window scale");
514 conRegFunc!((ConString key, ConString cmd) { addBinding(key, cmd); })("gh_bind", "global hotkey bind: key command");
515 conRegFunc!((ConString key) { addBinding(key, null); })("gh_unbind", "global hotkey unbind: key");
516 conRegFunc!((ConString key) { removeAllBindings(); })("gh_unbind_all", "unbind all global hotkeys unbind: key");
518 conRegVar!skinfile("skin_file", "load skin from the given file",
519 delegate (ConVarBase self, string oldval, string newval) {
520 try {
521 loadSkin(newval);
522 skinfile = newval;
523 glconPostScreenRebuild();
524 } catch (Exception e) {
525 conwriteln("ERROR loading skin: ", e.msg);
530 aplayStart();
531 scope(exit) aplayShutdown();
533 loadSkin!true("!BUILTIN!");
535 createCtlWindow();
536 createAmpWindow();
537 if (plVisible) createPListWindow();
538 if (eqVisible) createEqWindow();
539 dgCreatePListWindow = delegate () { createPListWindow(); };
540 dgCreateEqWindow = delegate () { createEqWindow(); };
542 concmd("gh_bind M-H-A win_toggle");
543 concmd("gh_bind M-H-Z song_prev");
544 concmd("gh_bind M-H-X song_play");
545 concmd("gh_bind M-H-C song_pause_toggle");
546 concmd("gh_bind M-H-V song_stop");
547 concmd("gh_bind M-H-B song_next");
548 concmd("gh_bind M-H-Left \"song_seek_rel -10\"");
549 concmd("gh_bind M-H-Right \"song_seek_rel +10\"");
550 concmd("gh_bind M-H-Up \"soft_volume_rel +2\"");
551 concmd("gh_bind M-H-Down \"soft_volume_rel -2\"");
552 concmd("gh_bind M-H-Delete \"soft_volume 31\"");
555 conRegFunc!((ConString path, bool append=false) {
556 try {
557 scanDir(path, append);
558 } catch (Exception e) {
559 conwriteln("scanning error: ", e.msg);
561 glconPostScreenRebuild();
562 })("scan_dir", "scan the given directory; 2nd ard is \"append\" bool flag");
564 conRegFunc!(() {
565 ampPList.clear();
566 glconPostScreenRebuild();
567 })("pl_clear", "clear playlist");
569 conRegFunc!(() { hideShowWindows(); })("win_toggle", "show/hide Amper windows");
571 conRegFunc!((int plidx, bool forcestart=false) {
572 if (!sdampwin.closed) {
573 ampPList.playSongByIndex(plidx, forcestart);
574 glconPostScreenRebuild();
576 })("song_play_by_index", "play song from playlist by index");
578 conRegFunc!((){ if (!sdampwin.closed) ampPList.playPrevSong(); })("song_prev", "play previous song");
579 conRegFunc!((bool forceplay=false){ if (!sdampwin.closed) ampPList.playNextSong(forceplay); })("song_next", "play next song");
580 conRegFunc!((){ if (!sdampwin.closed) ampPList.playCurrentSong(); })("song_play", "play current song");
581 conRegFunc!((){ if (!sdampwin.closed) ampPList.stopSong(); })("song_stop", "stop current song");
582 conRegFunc!((){ aplayTogglePause(); })("song_pause_toggle", "pause/unpause current song");
583 conRegFunc!((bool pause){ aplayPause(pause); })("song_pause", "pause/unpause current song, with bool arg");
585 conRegFunc!((uint msecs){ aplaySeekMS(msecs*1000); })("song_seek_abs", "absolute seek, in seconds");
586 conRegFunc!((int msecs){ aplaySeekMS(aplayCurTimeMS+msecs*1000); })("song_seek_rel", "relative seek, in seconds");
589 concmd("exec /etc/amper.rc tan"); // global config
590 conProcessQueue(); // load config
592 import core.stdc.stdlib : getenv;
593 auto home = getenv("HOME");
594 if (home !is null && home[0]) {
595 import std.string : fromStringz;
596 string s = home.fromStringz.idup;
597 if (s[$-1] != '/') s ~= '/';
598 s ~= ".amper.rc";
599 //conwriteln("home config: [", s, "]");
600 concmdf!"exec \"%s\" tan"(s); // user config
601 conProcessQueue(); // load config
604 // local config
605 concmd("exec amper.rc tan");
606 conProcessQueue();
608 conProcessArgs!true(args);
609 conProcessQueue(int.max/4);
611 startRPCServer();
612 scope(exit) stopRPCServer();
614 if (plVisible) {
615 static class EventFixupPListPosition {}
616 glconCtlWindow.addEventListener((EventFixupPListPosition evt) {
617 int xmain, ymain, wdtmain, hgtmain;
618 int xpl, ypl, wdtpl, hgtpl;
619 int xwork, ywork, wdtwork, hgtwork;
620 getWorkAreaRect(xwork, ywork, wdtwork, hgtwork);
621 sdampwin.getWindowRect(xmain, ymain, wdtmain, hgtmain);
622 sdplwin.getWindowRect(xpl, ypl, wdtpl, hgtpl);
623 conwriteln("work: (", xwork, ",", ywork, ")-(", wdtwork, "x", hgtwork, ")");
624 conwriteln("main: (", xmain, ",", ymain, ")-(", wdtmain, "x", hgtmain, ")");
625 conwriteln("list: (", xpl, ",", ypl, ")-(", wdtpl, "x", hgtpl, ")");
626 if (GxRect(xmain, ymain, wdtmain, hgtmain).overlaps(GxRect(xpl, ypl, wdtpl, hgtpl))) {
627 int nx = xmain;
628 int ny = ymain+hgtmain;
629 if (nx+wdtpl > xwork+wdtwork) nx = xwork+wdtwork-wdtpl;
630 if (ny+hgtpl > ywork+hgtwork) ny = ywork+hgtwork-hgtpl;
631 if (nx < xwork) nx = xwork;
632 if (ny < ywork) ny = ywork;
633 conwriteln("newpos: (", nx, ",", ny, ")");
634 sdplwin.move(nx, ny);
635 //sdplwin.move(50, 50);
636 flushGui();
639 sdplwin.visibleForTheFirstTime = delegate () {
640 flushGui();
641 switchToWindow(sdampwin);
642 //glconCtlWindow.postTimeout(new EventFixupPListPosition(), 50);
645 prepareTrayIcon();
646 flushGui();
648 foreach (string path; args[1..$]) concmdf!"scan_dir \"%s\" tan"(path);
650 glconCtlWindow.eventLoop(0);
652 flushGui();
653 conProcessQueue(int.max/4);