switched to GPLv3 ONLY, because i don't trust FSF anymore
[amper.git] / amper.d
blob2b574a7ad885b590611056554327593fce6af14e
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/>.
16 module amper;
18 import std.concurrency;
20 import arsd.color;
21 import arsd.image;
22 import arsd.simpledisplay;
24 import iv.cmdcon;
25 import iv.cmdcongl;
26 import iv.sdpyutil;
27 import iv.strex;
28 import iv.vfs;
30 import aplayer;
32 import egfx;
34 import amperrpcsrv;
35 import amperskin;
36 import amperopts;
39 // ////////////////////////////////////////////////////////////////////////// //
40 class EventSaveWindows {}
41 __gshared EventSaveWindows evSaveWindows;
43 shared static this () { evSaveWindows = new EventSaveWindows(); }
46 // ////////////////////////////////////////////////////////////////////////// //
47 public GxRect getWorkArea () {
48 GxRect rc;
49 getWorkAreaRect(rc.x0, rc.y0, rc.width, rc.height);
50 return rc;
54 // ////////////////////////////////////////////////////////////////////////// //
55 class GlobalHotkeyEx : GlobalHotkey {
56 string cmd;
57 override void doHandle () { concmd(cmd); }
59 this (ConString kname, ConString acmd) {
60 super(kname);
61 cmd = acmd.idup;
65 __gshared GlobalHotkeyEx[] ghbindings; // key is key, value is command
68 void removeAllBindings () {
69 char[128] knbuf;
70 foreach (GlobalHotkeyEx b; ghbindings) {
71 try { GlobalHotkeyManager.unregister(b.key.toStrBuf(knbuf[])); } catch (Exception e) {}
73 ghbindings.length = 0;
74 ghbindings.assumeSafeAppend;
78 void addBinding (ConString key, ConString cmd) {
79 cmd = cmd.xstrip;
80 key = key.xstrip;
81 if (cmd.length == 0) {
82 GlobalHotkeyManager.unregister(key);
83 foreach (immutable idx, GlobalHotkeyEx bind; ghbindings) {
84 if (bind.key == key) {
85 foreach (immutable cc; idx+1..ghbindings.length) ghbindings[cc-1] = ghbindings[cc];
86 ghbindings[$-1] = null;
87 ghbindings.length -= 1;
88 ghbindings.assumeSafeAppend;
89 return;
92 return;
94 try {
95 char[128] knbuf;
96 auto bind = new GlobalHotkeyEx(key, cmd);
97 GlobalHotkeyManager.unregister(bind.key.toStrBuf(knbuf[]));
98 GlobalHotkeyManager.register(bind);
99 foreach (immutable idx, GlobalHotkeyEx b; ghbindings) {
100 if (b.key == key) {
101 ghbindings[idx] = bind;
102 return;
105 auto optr = ghbindings.ptr;
106 ghbindings ~= bind;
107 if (optr !is ghbindings.ptr) {
108 import core.memory : GC;
109 if (ghbindings.ptr is GC.addrOf(ghbindings.ptr)) {
110 GC.setAttr(ghbindings.ptr, GC.BlkAttr.NO_INTERIOR);
113 } catch (Exception e) {
114 conwriteln("ERROR registering hotkey: '", key, "'");
119 // ////////////////////////////////////////////////////////////////////////// //
120 __gshared SimpleWindow sdhint;
121 __gshared string hinttext = "not playing";
122 __gshared Timer hintHideTimer;
123 __gshared int hintX, hintY;
126 // ////////////////////////////////////////////////////////////////////////// //
127 void setHint(T:const(char)[]) (T str) {
128 static if (is(T == typeof(null))) {
129 setHint("");
130 } else {
131 if (hinttext == str) return;
132 static if (is(T == string)) hinttext = str; else hinttext = str.idup;
133 if (sdhint is null || sdhint.closed) return;
134 if (sdhint.hidden) return;
135 createHintWindow(hintX, hintY);
140 // ////////////////////////////////////////////////////////////////////////// //
141 GxPoint hintWindowTextOffset () { return GxPoint(3, 2); }
143 GxSize hintWindowSize () {
144 int textWidth = gxTextWidthUtf(hinttext)+6;
145 if (textWidth < 8) textWidth = 8;
146 return GxSize(textWidth, gxTextHeightUtf+4);
150 void createHintWindow (int x, int y) {
151 if (sdhint !is null) {
152 sdhint.close();
153 sdhint = null;
156 sdpyWindowClass = "AMPER_HINT_WINDOW";
157 auto wsz = hintWindowSize();
159 auto wrc = getWorkArea();
160 int nx = x;
161 int ny = y;
162 if (nx+wsz.width > wrc.x1) nx = wrc.x1-wsz.width+1;
163 if (nx < wrc.x0) nx = wrc.x0;
164 if (ny+wsz.height > wrc.y1) ny = wrc.y1-wsz.height+1;
165 if (ny < wrc.y0) ny = wrc.y0;
166 hintX = nx;
167 hintY = ny;
169 sdhint = new SimpleWindow(wsz.width, wsz.height, "AmperHint", OpenGlOptions.no, Resizability.fixedSize, WindowTypes.undecorated, WindowFlags.skipTaskbar|WindowFlags.alwaysOnTop|WindowFlags.cannotBeActivated);
170 XSetWindowBackground(sdhint.impl.display, sdhint.impl.window, gxRGB!(255, 255, 0));
171 sdhint.handleExpose = delegate (int x, int y, int wdt, int hgt, int eventsLeft) {
172 if (eventsLeft == 0) {
173 if (sdhint is null || sdhint.closed) return false;
174 //if (sdhint.hidden) return;
175 auto wsz = hintWindowSize();
177 XSetForeground(sdhint.impl.display, sdhint.impl.gc, gxRGB!(255, 255, 0));
178 XFillRectangle(sdhint.impl.display, cast(Drawable)sdhint.impl.buffer, sdhint.impl.gc, 0, 0, wsz.width+1, wsz.height+1);
180 XSetForeground(sdhint.impl.display, sdhint.impl.gc, gxRGB!(0, 0, 0));
181 XDrawRectangle(sdhint.impl.display, cast(Drawable)sdhint.impl.buffer, sdhint.impl.gc, 0, 0, wsz.width-1, wsz.height-1);
183 auto tofs = hintWindowTextOffset;
184 gxDrawTextUtf(sdhint, tofs.x, tofs.y, hinttext, gxRGB!(0, 0, 0));
186 //flushGui();
187 return false; // sdpy will copy backbuffer
189 return true; // so sdpy will not draw backbuffer
192 // sorry for this hack
193 sdhint.setNetWMWindowType(GetAtom!("_NET_WM_WINDOW_TYPE_DOCK", true)(sdhint.display));
194 //sdhint.setNetWMWindowType(GetAtom!("_NET_WM_WINDOW_TYPE_TOOLTIP", true)(sdhint.display));
196 Atom[4] atoms;
197 atoms[0] = GetAtom!("_NET_WM_STATE_STICKY", true)(sdhint.impl.display);
198 atoms[1] = GetAtom!("_NET_WM_STATE_SKIP_TASKBAR", true)(sdhint.impl.display);
199 atoms[2] = GetAtom!("_NET_WM_STATE_SKIP_PAGER", true)(sdhint.impl.display);
200 atoms[3] = GetAtom!("_NET_WM_STATE_ABOVE", true)(sdhint.impl.display);
201 XChangeProperty(
202 sdhint.impl.display,
203 sdhint.impl.window,
204 GetAtom!("_NET_WM_STATE", true)(sdhint.impl.display),
205 XA_ATOM,
206 32 /* bits */,
207 0 /*PropModeReplace*/,
208 atoms.ptr,
209 cast(int)atoms.length);
211 sdhint.moveResize(hintX, hintY, wsz.width, wsz.height);
212 //repaintHintWindow(true);
216 // ////////////////////////////////////////////////////////////////////////// //
217 __gshared NotificationAreaIcon trayicon;
218 __gshared Image trayimage;
219 __gshared MemoryImage icon; // 0: normal
222 void hideShowWindows () {
223 if (sdampwin is null || sdampwin.closed) return;
224 if (sdampwin.hidden) {
225 sdampwin.show();
226 flushGui();
227 if (plVisible && sdplwin !is null && !sdplwin.closed) { sdplwin.show(); flushGui(); }
228 if (eqVisible && sdeqwin !is null && !sdeqwin.closed) { sdeqwin.show(); flushGui(); }
229 switchToWindow(sdampwin);
230 flushGui();
231 } else {
232 if (sdeqwin !is null && !sdeqwin.closed && !sdeqwin.hidden) { /*saveEqWindowPosition();*/ sdeqwin.hideInternal(); }
233 if (sdplwin !is null && !sdplwin.closed && !sdplwin.hidden) { /*savePlWindowPosition();*/ sdplwin.hideInternal(); }
234 sdampwin.hide();
235 flushGui();
240 void prepareTrayIcon () {
241 static immutable ubyte[] nticonpng = cast(immutable(ubyte)[])import("skins/notifyicon.png");
242 //icon = readPng("skins/notifyicon.png");
243 icon = imageFromPng(readPng(nticonpng));
244 trayimage = Image.fromMemoryImage(icon);
245 trayicon = new NotificationAreaIcon("Amper", trayimage, (int x, int y, MouseButton button, ModifierState mods) {
246 //conwritefln!"x=%d; y=%d; button=%u; mods=0x%04x"(x, y, button, mods);
247 if (button == MouseButton.middle) {
248 //trayicon.close();
249 //trayicon = null;
250 concmd("quit");
251 return;
253 if (button == MouseButton.left) {
254 concmd("win_toggle");
255 return;
258 trayicon.onEnter = delegate (int x, int y, ModifierState mods) {
259 //conwritefln!"icon enter: x=%d; y=%d; mods=0x%04x"(x, y, mods);
261 conwritefln!"icon enter: x=%d; y=%d; mods=0x%04x"(x, y, mods);
262 int wx, wy, wdt, hgt;
263 trayicon.getWindowRect(wx, wy, wdt, hgt);
264 conwriteln("window rect: wx=", wx, "; wy=", wy, "; wdt=", wdt, "; hgt=", hgt);
266 if (sdhint is null || sdhint.hidden) {
267 createHintWindow(x+18, y+2);
268 if (hintHideTimer !is null) hintHideTimer.destroy();
269 hintHideTimer = new Timer(3000, delegate () {
270 if (hintHideTimer !is null) {
271 hintHideTimer.destroy();
272 hintHideTimer = null;
273 if (sdhint !is null && !sdhint.closed) sdhint.hide();
278 trayicon.onLeave = delegate () {
279 //conwriteln("icon leave");
280 //if (sdhint !is null && !sdhint.closed) sdhint.hide();
285 // ////////////////////////////////////////////////////////////////////////// //
286 __gshared ubyte vbNewScale = 1;
289 // ////////////////////////////////////////////////////////////////////////// //
290 class ScrollTitleEvent {}
291 __gshared ScrollTitleEvent evScrollTitle;
293 shared static this () {
294 evScrollTitle = new ScrollTitleEvent();
298 // ////////////////////////////////////////////////////////////////////////// //
299 __gshared bool mainDrag = false;
300 __gshared int mainDrawPrevX, mainDrawPrevY;
303 // ////////////////////////////////////////////////////////////////////////// //
304 void closeAllIfMainIsClosed () {
305 bool doQuit =
306 (glconCtlWindow is null || glconCtlWindow.closed) ||
307 (sdampwin is null || sdampwin.closed);
308 if (doQuit) {
309 if (sdhint !is null && !sdhint.closed) sdhint.close();
310 if (sdeqwin !is null && !sdeqwin.closed) sdeqwin.close();
311 if (sdplwin !is null && !sdplwin.closed) sdplwin.close();
312 if (sdampwin !is null && !sdampwin.closed) sdampwin.close();
313 if (glconCtlWindow !is null && !glconCtlWindow.closed) glconCtlWindow.close();
318 // ////////////////////////////////////////////////////////////////////////// //
319 void fixWindowPosition (SimpleWindow sw, int ofsx, int ofsy, int* wdt=null, int* hgt=null) {
320 if (sdampwin is null || sdampwin.closed || sdampwin.hidden || sw is null || sw.closed) return;
322 int ax, ay, aw, ah;
323 getWindowRect(sdampwin, ax, ay, aw, ah);
324 //conwriteln("pl-onsetup: ax=", ax, "; ay=", ay, "; aw=", aw, "; ah=", ah);
325 if (aw < 1 || ah < 1) return;
327 if (ofsx != int.min && ofsy != int.min) {
328 //conwriteln("pl-onsetup: lastPlOffsetX=", lastPlOffsetX, "; lastPlOffsetY=", lastPlOffsetY);
329 sw.move(ax+ofsx, ay+ofsy);
330 if (wdt !is null && hgt !is null) sw.resize(*wdt, *hgt);
334 void fixPlWindowPosition () { fixWindowPosition(sdplwin, lastPlOffsetX, lastPlOffsetY, &lastPlWidth, &lastPlHeight); }
335 void fixEqWindowPosition () { fixWindowPosition(sdeqwin, lastEqOffsetX, lastEqOffsetY); }
338 // ////////////////////////////////////////////////////////////////////////// //
339 void saveWindowPosition (SimpleWindow sw, ref int ofsx, ref int ofsy, int* wdt=null, int* hgt=null) {
340 if (sdampwin is null || sdampwin.closed || sdampwin.hidden || sw is null || sw.closed) return;
342 int ax, ay, aw, ah;
343 getWindowRect(sdampwin, ax, ay, aw, ah);
344 if (aw < 1 || ah < 1) return;
346 int x, y, w, h;
347 getWindowRect(sw, x, y, w, h);
349 bool doSave = false;
351 if (ofsx != x-ax) { doSave = true; ofsx = x-ax; }
352 if (ofsy != y-ay) { doSave = true; ofsy = y-ay; }
354 if (wdt !is null && w >= 275 && w != *wdt) { doSave = true; *wdt = w; }
355 if (hgt !is null && h >= 116 && h != *hgt) { doSave = true; *hgt = h; }
357 if (doSave) saveWindowConfig();
360 void savePlWindowPosition () { saveWindowPosition(sdplwin, lastPlOffsetX, lastPlOffsetY, &lastPlWidth, &lastPlHeight); }
361 void saveEqWindowPosition () { saveWindowPosition(sdeqwin, lastEqOffsetX, lastEqOffsetY); }
364 // ////////////////////////////////////////////////////////////////////////// //
365 // create hidden control window
366 void createCtlWindow () {
367 sdpyWindowClass = "AMPER_PLAYER_CTL";
368 glconCtlWindow = new SimpleWindow(1, 1, "AmperCtl", OpenGlOptions.no, Resizability.fixedSize, WindowTypes.eventOnly);
372 void setupCtlWindow () {
373 glconCtlWindow.onClosing = delegate () {
374 //conwriteln("closing ctl window...");
375 if (sdeqwin !is null && !sdeqwin.closed) sdeqwin.close();
376 if (sdplwin !is null && !sdplwin.closed) sdplwin.close();
377 if (sdampwin !is null && !sdampwin.closed) sdampwin.close();
380 glconCtlWindow.onDestroyed = delegate () {
381 //conwriteln("ctl window destroyed");
382 closeAllIfMainIsClosed();
385 glconCtlWindow.addEventListener((QuitEvent evt) {
386 scope(exit) if (!conQueueEmpty()) glconPostDoConCommands();
387 scope(exit) closeAllIfMainIsClosed();
388 if (glconCtlWindow.closed) return;
389 if (isQuitRequested) { glconCtlWindow.close(); return; }
390 concmd("quit");
393 void rebuildRepaint () {
394 scope(exit) if (!conQueueEmpty()) glconPostDoConCommands();
395 scope(exit) closeAllIfMainIsClosed();
396 if (glconCtlWindow.closed) return;
397 if (isQuitRequested) { glconCtlWindow.close(); return; }
398 //conwriteln("rebuilding screen");
400 if (sdampwin !is null && !sdampwin.closed && !sdampwin.hidden) sdampwin.redraw();
401 if (sdplwin !is null && !sdplwin.closed && !sdplwin.hidden) sdplwin.redraw();
402 if (sdeqwin !is null && !sdeqwin.closed && !sdeqwin.hidden) sdeqwin.redraw();
404 flushGui();
407 glconCtlWindow.addEventListener((GLConScreenRepaintEvent evt) { rebuildRepaint(); });
409 glconCtlWindow.addEventListener((GLConDoConsoleCommandsEvent evt) {
410 scope(exit) if (!conQueueEmpty()) glconPostDoConCommands();
411 scope(exit) closeAllIfMainIsClosed();
412 glconProcessEventMessage();
413 if (glconCtlWindow.closed) return;
414 if (isQuitRequested) { glconCtlWindow.close(); return; }
417 glconCtlWindow.addEventListener((ScrollTitleEvent evt) {
418 scope(exit) if (!conQueueEmpty()) glconPostDoConCommands();
419 scope(exit) closeAllIfMainIsClosed();
420 if (glconCtlWindow.closed) return;
421 if (isQuitRequested) { glconCtlWindow.close(); return; }
422 //conwriteln("scrolling title");
423 ampMain.scrollTitle();
424 if (!glconCtlWindow.eventQueued!ScrollTitleEvent) glconCtlWindow.postTimeout(evScrollTitle, 100);
425 glconPostScreenRepaint();
428 glconCtlWindow.addEventListener((EventFileLoaded evt) {
429 if (!evt.success) {
430 conwriteln("ERROR loading '", evt.filename, "'");
431 ampMain.newSong("not playing");
432 setHint("not playing");
433 concmd("song_next");
434 } else {
435 string cursong = evt.artist~" \u2014 "~evt.title;
436 setHint(cursong);
437 ampMain.newSong(cursong);
438 //conwriteln("playing '", evt.filename, "': ", evt.artist, " -- ", evt.title);
442 glconCtlWindow.addEventListener((EventFileScanned evt) {
443 //conwriteln("scanned: '", evt.filename, "': ", evt.success);
444 if (ampPList is null) return;
445 if (evt.success) {
446 ampPList.scanResult(evt.filename, evt.album, evt.artist, evt.title, evt.durationms);
447 } else {
448 ampPList.scanResultFailed(evt.filename);
452 glconCtlWindow.addEventListener((EventFileComplete evt) {
453 //glconCtlWindow.close();
454 setHint("not playing");
455 concmd("song_next tan");
458 glconCtlWindow.addEventListener!EventSaveWindows((EventSaveWindows evt) {
459 saveEqWindowPosition();
460 savePlWindowPosition();
461 if (!glconCtlWindow.eventQueued!EventSaveWindows) glconCtlWindow.postTimeout(evSaveWindows, 10000);
466 // ////////////////////////////////////////////////////////////////////////// //
467 void createAmpWindow () {
468 ampMain = new AmpMainWindow();
469 sdampwin = new EgfxWindow(ampMain, "AMPER_PLAYER", "Amper");
470 if (!sdampwin.eventQueued!ScrollTitleEvent) glconCtlWindow.postEvent(evScrollTitle);
471 sdampwin.visibleForTheFirstTime = delegate () {
472 //flushGui();
473 //switchToWindow(sdampwin);
474 //glconCtlWindow.postTimeout(new EventFixupPListPosition(), 50);
475 if (plVisible) sdplwin.show();
476 if (eqVisible) sdeqwin.show();
477 if (!glconCtlWindow.eventQueued!EventSaveWindows) glconCtlWindow.postTimeout(evSaveWindows, 10000);
479 sdampwin.onDismiss = delegate () { saveWindowConfig(); };
480 sdampwin.onSetup = delegate () {
481 //if (plVisible) sdplwin.show();
482 //if (eqVisible) sdeqwin.show();
483 //flushGui();
484 //if (!glconCtlWindow.eventQueued!EventSetupWindows) glconCtlWindow.postTimeout!EventSetupWindows(new EventSetupWindows(), 100);
489 // ////////////////////////////////////////////////////////////////////////// //
490 void createPListWindow () {
491 ampPList = new AmpPListWindow();
492 sdplwin = new EgfxWindow(ampPList, "AMPER_PLAYLIST", "Amper Playlist", 25, 29);
493 sdplwin.onSetup = delegate () { fixPlWindowPosition(); };
494 sdplwin.onDismiss = delegate () { savePlWindowPosition(); };
495 sdplwin.visibilityChanged = delegate (bool visible) {
496 if (!visible) {
497 int x, y, w, h;
498 getWindowRect(sdplwin, x, y, w, h);
499 //conwriteln("vis=", visible, "; x=", x, "; y=", y, "; w=", w, "; h=", h);
500 if (w >= 275 && h >= 116 && x > 0 && y > 0) savePlWindowPosition();
506 void createEqWindow () {
507 ampEq = new AmpEqWindow();
508 sdeqwin = new EgfxWindow(ampEq, "AMPER_EQIALIZER", "Amper Eqializer");
509 sdeqwin.onSetup = delegate () { fixEqWindowPosition(); };
510 sdeqwin.onDismiss = delegate () { saveEqWindowPosition(); };
511 //sdeqwin.visibilityChanged = delegate (bool visible) { if (!visible) saveEqWindowPosition(); };
512 sdeqwin.visibilityChanged = delegate (bool visible) {
513 if (!visible) {
514 int x, y, w, h;
515 getWindowRect(sdeqwin, x, y, w, h);
516 //conwriteln("vis=", visible, "; x=", x, "; y=", y, "; w=", w, "; h=", h);
517 if (w >= 275 && h >= 116 && x > 0 && y > 0) saveEqWindowPosition();
523 // ////////////////////////////////////////////////////////////////////////// //
524 void scanDir (ConString path, bool append) {
525 void appendFile(T:const(char)[]) (T fname) {
526 static if (is(T == typeof(null))) {
527 return;
528 } else {
529 static if (is(T == string)) alias fn = fname; else string fn = fname.idup;
530 ampPList.appendListItem(fn);
534 // scan directory
535 import std.file;
536 try {
537 if (!append) ampPList.clear();
538 if (path.exists && path.isFile) { appendFile(path); return; }
539 foreach (DirEntry de; dirEntries(path.idup, SpanMode.shallow)) {
540 if (!de.isFile) continue;
541 appendFile(de.name);
543 } catch (Exception e) {
544 conwriteln("ERROR scanning: ", e.msg);
546 //if (modeShuffle) ampPList.state.curitem = ampPList.findShuffleFirst();
547 //conwriteln(ampPList.findShuffleFirst(), " : ", ampPList.state.shuffleidx, " : ", ampPList.state.curplayingitem);
551 // ////////////////////////////////////////////////////////////////////////// //
552 void main (string[] args) {
553 conRegFunc!(() { import core.memory : GC; GC.collect(); GC.minimize(); })("gc_collect", "force garbage collection");
555 //vbNewScale = 2;
556 //vbufEffScale = 2;
557 glconShowKey = "M-Grave";
559 //conRegVar!vbNewScale(1, 8, "v_scale", "window scale");
561 conRegFunc!((ConString key, ConString cmd) { addBinding(key, cmd); })("gh_bind", "global hotkey bind: key command");
562 conRegFunc!((ConString key) { addBinding(key, null); })("gh_unbind", "global hotkey unbind: key");
563 conRegFunc!((ConString key) { removeAllBindings(); })("gh_unbind_all", "unbind all global hotkeys unbind: key");
565 conRegVar!skinfile("skin_file", "load skin from the given file",
566 delegate (ConVarBase self, string oldval, string newval) {
567 try {
568 loadSkin(newval);
569 skinfile = newval;
570 glconPostScreenRepaint();
571 } catch (Exception e) {
572 conwriteln("ERROR loading skin: ", e.msg);
577 aplayStart();
578 scope(exit) aplayShutdown();
580 createCtlWindow();
582 loadSkin!true("!BUILTIN!");
584 createAmpWindow();
585 createPListWindow();
586 createEqWindow();
587 setupSkinRegions();
589 setupCtlWindow();
591 aplayStartScanner();
593 concmd("gh_bind M-H-A win_toggle");
594 concmd("gh_bind M-H-Z song_prev");
595 concmd("gh_bind M-H-X song_play");
596 concmd("gh_bind M-H-C song_pause_toggle");
597 concmd("gh_bind M-H-V song_stop");
598 concmd("gh_bind M-H-B song_next");
599 concmd("gh_bind M-H-Left \"song_seek_rel -10\"");
600 concmd("gh_bind M-H-Right \"song_seek_rel +10\"");
601 concmd("gh_bind M-H-Up \"soft_volume_rel +2\"");
602 concmd("gh_bind M-H-Down \"soft_volume_rel -2\"");
603 concmd("gh_bind M-H-Delete \"soft_volume 31\"");
606 conRegFunc!((ConString path, bool append=false) {
607 try {
608 scanDir(path, append);
609 } catch (Exception e) {
610 conwriteln("scanning error: ", e.msg);
612 glconPostScreenRepaint();
613 })("scan_dir", "scan the given directory; 2nd ard is \"append\" bool flag");
615 conRegFunc!(() {
616 ampPList.clear();
617 glconPostScreenRepaint();
618 })("pl_clear", "clear playlist");
620 conRegFunc!(() { hideShowWindows(); })("win_toggle", "show/hide Amper windows");
622 conRegFunc!((int plidx, bool forcestart=false) {
623 if (!sdampwin.closed) {
624 ampPList.playSongByIndex(plidx, forcestart);
625 glconPostScreenRepaint();
627 })("song_play_by_index", "play song from playlist by index");
629 conRegFunc!((){ if (!sdampwin.closed) ampPList.playPrevSong(); })("song_prev", "play previous song");
630 conRegFunc!((bool forceplay=false){ if (!sdampwin.closed) ampPList.playNextSong(forceplay); })("song_next", "play next song");
631 conRegFunc!((){ if (!sdampwin.closed) ampPList.playCurrentSong(); })("song_play", "play current song");
632 conRegFunc!((){ if (!sdampwin.closed) ampPList.stopSong(); })("song_stop", "stop current song");
633 conRegFunc!((){ aplayTogglePause(); })("song_pause_toggle", "pause/unpause current song");
634 conRegFunc!((bool pause){ aplayPause(pause); })("song_pause", "pause/unpause current song, with bool arg");
636 conRegFunc!((uint msecs){ aplaySeekMS(msecs*1000); })("song_seek_abs", "absolute seek, in seconds");
637 conRegFunc!((int msecs){ aplaySeekMS(aplayCurTimeMS+msecs*1000); })("song_seek_rel", "relative seek, in seconds");
640 concmd("exec /etc/amper.rc tan"); // global config
641 conProcessQueue(); // load config
642 loadHomeConfig();
643 loadWindowConfig();
644 // local config
645 concmd("exec amper.rc tan");
646 conProcessQueue();
648 conProcessArgs!true(args);
649 conProcessQueue(int.max/4);
651 startRPCServer();
652 scope(exit) stopRPCServer();
654 if (!plVisibleChanged) plVisible = plVisibleCfg;
655 if (!eqVisibleChanged) eqVisible = eqVisibleCfg;
657 sdampwin.show();
658 flushGui();
661 if (plVisible) {
663 static class EventFixupPListPosition {}
664 glconCtlWindow.addEventListener((EventFixupPListPosition evt) {
665 int xmain, ymain, wdtmain, hgtmain;
666 int xpl, ypl, wdtpl, hgtpl;
667 int xwork, ywork, wdtwork, hgtwork;
668 getWorkAreaRect(xwork, ywork, wdtwork, hgtwork);
669 sdampwin.getWindowRect(xmain, ymain, wdtmain, hgtmain);
670 sdplwin.getWindowRect(xpl, ypl, wdtpl, hgtpl);
671 conwriteln("work: (", xwork, ",", ywork, ")-(", wdtwork, "x", hgtwork, ")");
672 conwriteln("main: (", xmain, ",", ymain, ")-(", wdtmain, "x", hgtmain, ")");
673 conwriteln("list: (", xpl, ",", ypl, ")-(", wdtpl, "x", hgtpl, ")");
674 if (GxRect(xmain, ymain, wdtmain, hgtmain).overlaps(GxRect(xpl, ypl, wdtpl, hgtpl))) {
675 int nx = xmain;
676 int ny = ymain+hgtmain;
677 if (nx+wdtpl > xwork+wdtwork) nx = xwork+wdtwork-wdtpl;
678 if (ny+hgtpl > ywork+hgtwork) ny = ywork+hgtwork-hgtpl;
679 if (nx < xwork) nx = xwork;
680 if (ny < ywork) ny = ywork;
681 conwriteln("newpos: (", nx, ",", ny, ")");
682 sdplwin.move(nx, ny);
683 //sdplwin.move(50, 50);
684 flushGui();
687 sdplwin.visibleForTheFirstTime = delegate () {
688 flushGui();
689 switchToWindow(sdampwin);
690 //glconCtlWindow.postTimeout(new EventFixupPListPosition(), 50);
693 sdplwin.show();
697 prepareTrayIcon();
698 flushGui();
700 foreach (string path; args[1..$]) concmdf!"scan_dir \"%s\" tan"(path);
702 glconCtlWindow.eventLoop(0);
704 flushGui();
705 conProcessQueue(int.max/4);