pause is working again
[amper.git] / amper.d
blobf18b47bbbcbefe1cb14cb282e2ef4a751d5eee9e
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 class EventSaveWindows {}
42 __gshared EventSaveWindows evSaveWindows;
44 shared static this () { evSaveWindows = new EventSaveWindows(); }
47 // ////////////////////////////////////////////////////////////////////////// //
48 public GxRect getWorkArea () {
49 GxRect rc;
50 getWorkAreaRect(rc.x0, rc.y0, rc.width, rc.height);
51 return rc;
55 // ////////////////////////////////////////////////////////////////////////// //
56 class GlobalHotkeyEx : GlobalHotkey {
57 string cmd;
58 override void doHandle () { concmd(cmd); }
60 this (ConString kname, ConString acmd) {
61 super(kname);
62 cmd = acmd.idup;
66 __gshared GlobalHotkeyEx[] ghbindings; // key is key, value is command
69 void removeAllBindings () {
70 char[128] knbuf;
71 foreach (GlobalHotkeyEx b; ghbindings) {
72 try { GlobalHotkeyManager.unregister(b.key.toStrBuf(knbuf[])); } catch (Exception e) {}
74 ghbindings.length = 0;
75 ghbindings.assumeSafeAppend;
79 void addBinding (ConString key, ConString cmd) {
80 cmd = cmd.xstrip;
81 key = key.xstrip;
82 if (cmd.length == 0) {
83 GlobalHotkeyManager.unregister(key);
84 foreach (immutable idx, GlobalHotkeyEx bind; ghbindings) {
85 if (bind.key == key) {
86 foreach (immutable cc; idx+1..ghbindings.length) ghbindings[cc-1] = ghbindings[cc];
87 ghbindings[$-1] = null;
88 ghbindings.length -= 1;
89 ghbindings.assumeSafeAppend;
90 return;
93 return;
95 try {
96 char[128] knbuf;
97 auto bind = new GlobalHotkeyEx(key, cmd);
98 GlobalHotkeyManager.unregister(bind.key.toStrBuf(knbuf[]));
99 GlobalHotkeyManager.register(bind);
100 foreach (immutable idx, GlobalHotkeyEx b; ghbindings) {
101 if (b.key == key) {
102 ghbindings[idx] = bind;
103 return;
106 auto optr = ghbindings.ptr;
107 ghbindings ~= bind;
108 if (optr !is ghbindings.ptr) {
109 import core.memory : GC;
110 if (ghbindings.ptr is GC.addrOf(ghbindings.ptr)) {
111 GC.setAttr(ghbindings.ptr, GC.BlkAttr.NO_INTERIOR);
114 } catch (Exception e) {
115 conwriteln("ERROR registering hotkey: '", key, "'");
120 // ////////////////////////////////////////////////////////////////////////// //
121 __gshared SimpleWindow sdhint;
122 __gshared string hinttext = "not playing";
123 __gshared Timer hintHideTimer;
124 __gshared int hintX, hintY;
127 // ////////////////////////////////////////////////////////////////////////// //
128 void setHint(T:const(char)[]) (T str) {
129 static if (is(T == typeof(null))) {
130 setHint("");
131 } else {
132 if (hinttext == str) return;
133 static if (is(T == string)) hinttext = str; else hinttext = str.idup;
134 if (sdhint is null || sdhint.closed) return;
135 if (sdhint.hidden) return;
136 createHintWindow(hintX, hintY);
141 // ////////////////////////////////////////////////////////////////////////// //
142 GxPoint hintWindowTextOffset () { return GxPoint(3, 2); }
144 GxSize hintWindowSize () {
145 int textWidth = gxTextWidthUtf(hinttext)+6;
146 if (textWidth < 8) textWidth = 8;
147 return GxSize(textWidth, gxTextHeightUtf+4);
151 void createHintWindow (int x, int y) {
152 if (sdhint !is null) {
153 sdhint.close();
154 sdhint = null;
157 sdpyWindowClass = "AMPER_HINT_WINDOW";
158 auto wsz = hintWindowSize();
160 auto wrc = getWorkArea();
161 int nx = x;
162 int ny = y;
163 if (nx+wsz.width > wrc.x1) nx = wrc.x1-wsz.width+1;
164 if (nx < wrc.x0) nx = wrc.x0;
165 if (ny+wsz.height > wrc.y1) ny = wrc.y1-wsz.height+1;
166 if (ny < wrc.y0) ny = wrc.y0;
167 hintX = nx;
168 hintY = ny;
170 sdhint = new SimpleWindow(wsz.width, wsz.height, "AmperHint", OpenGlOptions.no, Resizability.fixedSize, WindowTypes.undecorated, WindowFlags.skipTaskbar|WindowFlags.alwaysOnTop|WindowFlags.cannotBeActivated);
171 XSetWindowBackground(sdhint.impl.display, sdhint.impl.window, gxRGB!(255, 255, 0));
172 sdhint.handleExpose = delegate (int x, int y, int wdt, int hgt, int eventsLeft) {
173 if (eventsLeft == 0) {
174 if (sdhint is null || sdhint.closed) return false;
175 //if (sdhint.hidden) return;
176 auto wsz = hintWindowSize();
178 XSetForeground(sdhint.impl.display, sdhint.impl.gc, gxRGB!(255, 255, 0));
179 XFillRectangle(sdhint.impl.display, cast(Drawable)sdhint.impl.buffer, sdhint.impl.gc, 0, 0, wsz.width+1, wsz.height+1);
181 XSetForeground(sdhint.impl.display, sdhint.impl.gc, gxRGB!(0, 0, 0));
182 XDrawRectangle(sdhint.impl.display, cast(Drawable)sdhint.impl.buffer, sdhint.impl.gc, 0, 0, wsz.width, wsz.height);
184 auto tofs = hintWindowTextOffset;
185 gxDrawTextUtf(sdhint, tofs.x, tofs.y, hinttext, gxRGB!(0, 0, 0));
187 //flushGui();
188 return false; // sdpy will copy backbuffer
190 return true; // so sdpy will not draw backbuffer
193 // sorry for this hack
194 sdhint.setNetWMWindowType(GetAtom!("_NET_WM_WINDOW_TYPE_DOCK", true)(sdhint.display));
195 //sdhint.setNetWMWindowType(GetAtom!("_NET_WM_WINDOW_TYPE_TOOLTIP", true)(sdhint.display));
197 Atom[4] atoms;
198 atoms[0] = GetAtom!("_NET_WM_STATE_STICKY", true)(sdhint.impl.display);
199 atoms[1] = GetAtom!("_NET_WM_STATE_SKIP_TASKBAR", true)(sdhint.impl.display);
200 atoms[2] = GetAtom!("_NET_WM_STATE_SKIP_PAGER", true)(sdhint.impl.display);
201 atoms[3] = GetAtom!("_NET_WM_STATE_ABOVE", true)(sdhint.impl.display);
202 XChangeProperty(
203 sdhint.impl.display,
204 sdhint.impl.window,
205 GetAtom!("_NET_WM_STATE", true)(sdhint.impl.display),
206 XA_ATOM,
207 32 /* bits */,
208 0 /*PropModeReplace*/,
209 atoms.ptr,
210 cast(int)atoms.length);
212 sdhint.moveResize(hintX, hintY, wsz.width, wsz.height);
213 //repaintHintWindow(true);
217 // ////////////////////////////////////////////////////////////////////////// //
218 __gshared NotificationAreaIcon trayicon;
219 __gshared Image trayimage;
220 __gshared MemoryImage icon; // 0: normal
223 void hideShowWindows () {
224 if (sdampwin is null || sdampwin.closed) return;
225 if (sdampwin.hidden) {
226 sdampwin.show();
227 flushGui();
228 if (plVisible && sdplwin !is null && !sdplwin.closed) { sdplwin.show(); flushGui(); }
229 if (eqVisible && sdeqwin !is null && !sdeqwin.closed) { sdeqwin.show(); flushGui(); }
230 switchToWindow(sdampwin);
231 flushGui();
232 } else {
233 if (sdeqwin !is null && !sdeqwin.closed && !sdeqwin.hidden) { /*saveEqWindowPosition();*/ sdeqwin.hideInternal(); }
234 if (sdplwin !is null && !sdplwin.closed && !sdplwin.hidden) { /*savePlWindowPosition();*/ sdplwin.hideInternal(); }
235 sdampwin.hide();
236 flushGui();
241 void prepareTrayIcon () {
242 static immutable ubyte[] nticonpng = cast(immutable(ubyte)[])import("skins/notifyicon.png");
243 //icon = readPng("skins/notifyicon.png");
244 icon = imageFromPng(readPng(nticonpng));
245 trayimage = Image.fromMemoryImage(icon);
246 trayicon = new NotificationAreaIcon("Amper", trayimage, (int x, int y, MouseButton button, ModifierState mods) {
247 //conwritefln!"x=%d; y=%d; button=%u; mods=0x%04x"(x, y, button, mods);
248 if (button == MouseButton.middle) {
249 //trayicon.close();
250 //trayicon = null;
251 concmd("quit");
252 return;
254 if (button == MouseButton.left) {
255 concmd("win_toggle");
256 return;
259 trayicon.onEnter = delegate (int x, int y, ModifierState mods) {
260 //conwritefln!"icon enter: x=%d; y=%d; mods=0x%04x"(x, y, mods);
262 conwritefln!"icon enter: x=%d; y=%d; mods=0x%04x"(x, y, mods);
263 int wx, wy, wdt, hgt;
264 trayicon.getWindowRect(wx, wy, wdt, hgt);
265 conwriteln("window rect: wx=", wx, "; wy=", wy, "; wdt=", wdt, "; hgt=", hgt);
267 if (sdhint is null || sdhint.hidden) {
268 createHintWindow(x+18, y+2);
269 if (hintHideTimer !is null) hintHideTimer.destroy();
270 hintHideTimer = new Timer(3000, delegate () {
271 if (hintHideTimer !is null) {
272 hintHideTimer.destroy();
273 hintHideTimer = null;
274 if (sdhint !is null && !sdhint.closed) sdhint.hide();
279 trayicon.onLeave = delegate () {
280 //conwriteln("icon leave");
281 //if (sdhint !is null && !sdhint.closed) sdhint.hide();
286 // ////////////////////////////////////////////////////////////////////////// //
287 __gshared ubyte vbNewScale = 1;
290 // ////////////////////////////////////////////////////////////////////////// //
291 class ScrollTitleEvent {}
292 __gshared ScrollTitleEvent evScrollTitle;
294 shared static this () {
295 evScrollTitle = new ScrollTitleEvent();
299 // ////////////////////////////////////////////////////////////////////////// //
300 __gshared bool mainDrag = false;
301 __gshared int mainDrawPrevX, mainDrawPrevY;
304 // ////////////////////////////////////////////////////////////////////////// //
305 void closeAllIfMainIsClosed () {
306 bool doQuit =
307 (glconCtlWindow is null || glconCtlWindow.closed) ||
308 (sdampwin is null || sdampwin.closed);
309 if (doQuit) {
310 if (sdhint !is null && !sdhint.closed) sdhint.close();
311 if (sdeqwin !is null && !sdeqwin.closed) sdeqwin.close();
312 if (sdplwin !is null && !sdplwin.closed) sdplwin.close();
313 if (sdampwin !is null && !sdampwin.closed) sdampwin.close();
314 if (glconCtlWindow !is null && !glconCtlWindow.closed) glconCtlWindow.close();
319 // ////////////////////////////////////////////////////////////////////////// //
320 void fixWindowPosition (SimpleWindow sw, int ofsx, int ofsy, int* wdt=null, int* hgt=null) {
321 if (sdampwin is null || sdampwin.closed || sdampwin.hidden || sw is null || sw.closed) return;
323 int ax, ay, aw, ah;
324 getWindowRect(sdampwin, ax, ay, aw, ah);
325 //conwriteln("pl-onsetup: ax=", ax, "; ay=", ay, "; aw=", aw, "; ah=", ah);
326 if (aw < 1 || ah < 1) return;
328 if (ofsx != int.min && ofsy != int.min) {
329 //conwriteln("pl-onsetup: lastPlOffsetX=", lastPlOffsetX, "; lastPlOffsetY=", lastPlOffsetY);
330 sw.move(ax+ofsx, ay+ofsy);
331 if (wdt !is null && hgt !is null) sw.resize(*wdt, *hgt);
335 void fixPlWindowPosition () { fixWindowPosition(sdplwin, lastPlOffsetX, lastPlOffsetY, &lastPlWidth, &lastPlHeight); }
336 void fixEqWindowPosition () { fixWindowPosition(sdeqwin, lastEqOffsetX, lastEqOffsetY); }
339 // ////////////////////////////////////////////////////////////////////////// //
340 void saveWindowPosition (SimpleWindow sw, ref int ofsx, ref int ofsy, int* wdt=null, int* hgt=null) {
341 if (sdampwin is null || sdampwin.closed || sdampwin.hidden || sw is null || sw.closed) return;
343 int ax, ay, aw, ah;
344 getWindowRect(sdampwin, ax, ay, aw, ah);
345 if (aw < 1 || ah < 1) return;
347 int x, y, w, h;
348 getWindowRect(sw, x, y, w, h);
350 bool doSave = false;
352 if (ofsx != x-ax) { doSave = true; ofsx = x-ax; }
353 if (ofsy != y-ay) { doSave = true; ofsy = y-ay; }
355 if (wdt !is null && w >= 275 && w != *wdt) { doSave = true; *wdt = w; }
356 if (hgt !is null && h >= 116 && h != *hgt) { doSave = true; *hgt = h; }
358 if (doSave) saveWindowConfig();
361 void savePlWindowPosition () { saveWindowPosition(sdplwin, lastPlOffsetX, lastPlOffsetY, &lastPlWidth, &lastPlHeight); }
362 void saveEqWindowPosition () { saveWindowPosition(sdeqwin, lastEqOffsetX, lastEqOffsetY); }
365 // ////////////////////////////////////////////////////////////////////////// //
366 // create hidden control window
367 void createCtlWindow () {
368 sdpyWindowClass = "AMPER_PLAYER_CTL";
369 glconCtlWindow = new SimpleWindow(1, 1, "AmperCtl", OpenGlOptions.no, Resizability.fixedSize, WindowTypes.eventOnly);
373 void setupCtlWindow () {
374 glconCtlWindow.onClosing = delegate () {
375 //conwriteln("closing ctl window...");
376 if (sdeqwin !is null && !sdeqwin.closed) sdeqwin.close();
377 if (sdplwin !is null && !sdplwin.closed) sdplwin.close();
378 if (sdampwin !is null && !sdampwin.closed) sdampwin.close();
381 glconCtlWindow.onDestroyed = delegate () {
382 //conwriteln("ctl window destroyed");
383 closeAllIfMainIsClosed();
386 glconCtlWindow.addEventListener((QuitEvent evt) {
387 scope(exit) if (!conQueueEmpty()) glconPostDoConCommands();
388 scope(exit) closeAllIfMainIsClosed();
389 if (glconCtlWindow.closed) return;
390 if (isQuitRequested) { glconCtlWindow.close(); return; }
391 concmd("quit");
394 void rebuildRepaint () {
395 scope(exit) if (!conQueueEmpty()) glconPostDoConCommands();
396 scope(exit) closeAllIfMainIsClosed();
397 if (glconCtlWindow.closed) return;
398 if (isQuitRequested) { glconCtlWindow.close(); return; }
399 //conwriteln("rebuilding screen");
401 if (sdampwin !is null && !sdampwin.closed && !sdampwin.hidden) sdampwin.redraw();
402 if (sdplwin !is null && !sdplwin.closed && !sdplwin.hidden) sdplwin.redraw();
403 if (sdeqwin !is null && !sdeqwin.closed && !sdeqwin.hidden) sdeqwin.redraw();
405 flushGui();
408 glconCtlWindow.addEventListener((GLConScreenRepaintEvent evt) { rebuildRepaint(); });
410 glconCtlWindow.addEventListener((GLConDoConsoleCommandsEvent evt) {
411 scope(exit) if (!conQueueEmpty()) glconPostDoConCommands();
412 scope(exit) closeAllIfMainIsClosed();
413 glconProcessEventMessage();
414 if (glconCtlWindow.closed) return;
415 if (isQuitRequested) { glconCtlWindow.close(); return; }
418 glconCtlWindow.addEventListener((ScrollTitleEvent evt) {
419 scope(exit) if (!conQueueEmpty()) glconPostDoConCommands();
420 scope(exit) closeAllIfMainIsClosed();
421 if (glconCtlWindow.closed) return;
422 if (isQuitRequested) { glconCtlWindow.close(); return; }
423 //conwriteln("scrolling title");
424 ampMain.scrollTitle();
425 if (!glconCtlWindow.eventQueued!ScrollTitleEvent) glconCtlWindow.postTimeout(evScrollTitle, 100);
426 glconPostScreenRepaint();
429 glconCtlWindow.addEventListener((EventFileLoaded evt) {
430 if (!evt.success) {
431 conwriteln("ERROR loading '", evt.filename, "'");
432 ampMain.newSong("not playing");
433 setHint("not playing");
434 concmd("song_next");
435 } else {
436 string cursong = evt.artist~" \u2014 "~evt.title;
437 setHint(cursong);
438 ampMain.newSong(cursong);
439 //conwriteln("playing '", evt.filename, "': ", evt.artist, " -- ", evt.title);
443 glconCtlWindow.addEventListener((EventFileScanned evt) {
444 //conwriteln("scanned: '", evt.filename, "': ", evt.success);
445 if (ampPList is null) return;
446 if (evt.success) {
447 ampPList.scanResult(evt.filename, evt.album, evt.artist, evt.title, evt.durationms);
448 } else {
449 ampPList.scanResultFailed(evt.filename);
453 glconCtlWindow.addEventListener((EventFileComplete evt) {
454 //glconCtlWindow.close();
455 setHint("not playing");
456 concmd("song_next tan");
459 glconCtlWindow.addEventListener!EventSaveWindows((EventSaveWindows evt) {
460 saveEqWindowPosition();
461 savePlWindowPosition();
462 if (!glconCtlWindow.eventQueued!EventSaveWindows) glconCtlWindow.postTimeout(evSaveWindows, 10000);
467 // ////////////////////////////////////////////////////////////////////////// //
468 void createAmpWindow () {
469 ampMain = new AmpMainWindow();
470 sdampwin = new EgfxWindow(ampMain, "AMPER_PLAYER", "Amper");
471 if (!sdampwin.eventQueued!ScrollTitleEvent) glconCtlWindow.postEvent(evScrollTitle);
472 sdampwin.visibleForTheFirstTime = delegate () {
473 //flushGui();
474 //switchToWindow(sdampwin);
475 //glconCtlWindow.postTimeout(new EventFixupPListPosition(), 50);
476 if (plVisible) sdplwin.show();
477 if (eqVisible) sdeqwin.show();
478 if (!glconCtlWindow.eventQueued!EventSaveWindows) glconCtlWindow.postTimeout(evSaveWindows, 10000);
480 sdampwin.onDismiss = delegate () { saveWindowConfig(); };
481 sdampwin.onSetup = delegate () {
482 //if (plVisible) sdplwin.show();
483 //if (eqVisible) sdeqwin.show();
484 //flushGui();
485 //if (!glconCtlWindow.eventQueued!EventSetupWindows) glconCtlWindow.postTimeout!EventSetupWindows(new EventSetupWindows(), 100);
490 // ////////////////////////////////////////////////////////////////////////// //
491 void createPListWindow () {
492 ampPList = new AmpPListWindow();
493 sdplwin = new EgfxWindow(ampPList, "AMPER_PLAYLIST", "Amper Playlist", 25, 29);
494 sdplwin.onSetup = delegate () { fixPlWindowPosition(); };
495 sdplwin.onDismiss = delegate () { savePlWindowPosition(); };
496 sdplwin.visibilityChanged = delegate (bool visible) {
497 if (!visible) {
498 int x, y, w, h;
499 getWindowRect(sdplwin, x, y, w, h);
500 //conwriteln("vis=", visible, "; x=", x, "; y=", y, "; w=", w, "; h=", h);
501 if (w >= 275 && h >= 116 && x > 0 && y > 0) savePlWindowPosition();
507 void createEqWindow () {
508 ampEq = new AmpEqWindow();
509 sdeqwin = new EgfxWindow(ampEq, "AMPER_EQIALIZER", "Amper Eqializer");
510 sdeqwin.onSetup = delegate () { fixEqWindowPosition(); };
511 sdeqwin.onDismiss = delegate () { saveEqWindowPosition(); };
512 //sdeqwin.visibilityChanged = delegate (bool visible) { if (!visible) saveEqWindowPosition(); };
513 sdeqwin.visibilityChanged = delegate (bool visible) {
514 if (!visible) {
515 int x, y, w, h;
516 getWindowRect(sdeqwin, x, y, w, h);
517 //conwriteln("vis=", visible, "; x=", x, "; y=", y, "; w=", w, "; h=", h);
518 if (w >= 275 && h >= 116 && x > 0 && y > 0) saveEqWindowPosition();
524 // ////////////////////////////////////////////////////////////////////////// //
525 void scanDir (ConString path, bool append) {
526 void appendFile(T:const(char)[]) (T fname) {
527 static if (is(T == typeof(null))) {
528 return;
529 } else {
530 static if (is(T == string)) alias fn = fname; else string fn = fname.idup;
531 ampPList.appendListItem(fn);
535 // scan directory
536 import std.file;
537 try {
538 if (!append) ampPList.clear();
539 if (path.exists && path.isFile) { appendFile(path); return; }
540 foreach (DirEntry de; dirEntries(path.idup, SpanMode.shallow)) {
541 if (!de.isFile) continue;
542 appendFile(de.name);
544 } catch (Exception e) {
545 conwriteln("ERROR scanning: ", e.msg);
547 //if (modeShuffle) ampPList.state.curitem = ampPList.findShuffleFirst();
548 //conwriteln(ampPList.findShuffleFirst(), " : ", ampPList.state.shuffleidx, " : ", ampPList.state.curplayingitem);
552 // ////////////////////////////////////////////////////////////////////////// //
553 void main (string[] args) {
554 conRegFunc!(() { import core.memory : GC; GC.collect(); GC.minimize(); })("gc_collect", "force garbage collection");
556 //vbNewScale = 2;
557 //vbufEffScale = 2;
558 glconShowKey = "M-Grave";
560 //conRegVar!vbNewScale(1, 8, "v_scale", "window scale");
562 conRegFunc!((ConString key, ConString cmd) { addBinding(key, cmd); })("gh_bind", "global hotkey bind: key command");
563 conRegFunc!((ConString key) { addBinding(key, null); })("gh_unbind", "global hotkey unbind: key");
564 conRegFunc!((ConString key) { removeAllBindings(); })("gh_unbind_all", "unbind all global hotkeys unbind: key");
566 conRegVar!skinfile("skin_file", "load skin from the given file",
567 delegate (ConVarBase self, string oldval, string newval) {
568 try {
569 loadSkin(newval);
570 skinfile = newval;
571 glconPostScreenRepaint();
572 } catch (Exception e) {
573 conwriteln("ERROR loading skin: ", e.msg);
578 aplayStart();
579 scope(exit) aplayShutdown();
581 createCtlWindow();
583 loadSkin!true("!BUILTIN!");
585 createAmpWindow();
586 createPListWindow();
587 createEqWindow();
588 setupSkinRegions();
590 setupCtlWindow();
592 aplayStartScanner();
594 concmd("gh_bind M-H-A win_toggle");
595 concmd("gh_bind M-H-Z song_prev");
596 concmd("gh_bind M-H-X song_play");
597 concmd("gh_bind M-H-C song_pause_toggle");
598 concmd("gh_bind M-H-V song_stop");
599 concmd("gh_bind M-H-B song_next");
600 concmd("gh_bind M-H-Left \"song_seek_rel -10\"");
601 concmd("gh_bind M-H-Right \"song_seek_rel +10\"");
602 concmd("gh_bind M-H-Up \"soft_volume_rel +2\"");
603 concmd("gh_bind M-H-Down \"soft_volume_rel -2\"");
604 concmd("gh_bind M-H-Delete \"soft_volume 31\"");
607 conRegFunc!((ConString path, bool append=false) {
608 try {
609 scanDir(path, append);
610 } catch (Exception e) {
611 conwriteln("scanning error: ", e.msg);
613 glconPostScreenRepaint();
614 })("scan_dir", "scan the given directory; 2nd ard is \"append\" bool flag");
616 conRegFunc!(() {
617 ampPList.clear();
618 glconPostScreenRepaint();
619 })("pl_clear", "clear playlist");
621 conRegFunc!(() { hideShowWindows(); })("win_toggle", "show/hide Amper windows");
623 conRegFunc!((int plidx, bool forcestart=false) {
624 if (!sdampwin.closed) {
625 ampPList.playSongByIndex(plidx, forcestart);
626 glconPostScreenRepaint();
628 })("song_play_by_index", "play song from playlist by index");
630 conRegFunc!((){ if (!sdampwin.closed) ampPList.playPrevSong(); })("song_prev", "play previous song");
631 conRegFunc!((bool forceplay=false){ if (!sdampwin.closed) ampPList.playNextSong(forceplay); })("song_next", "play next song");
632 conRegFunc!((){ if (!sdampwin.closed) ampPList.playCurrentSong(); })("song_play", "play current song");
633 conRegFunc!((){ if (!sdampwin.closed) ampPList.stopSong(); })("song_stop", "stop current song");
634 conRegFunc!((){ aplayTogglePause(); })("song_pause_toggle", "pause/unpause current song");
635 conRegFunc!((bool pause){ aplayPause(pause); })("song_pause", "pause/unpause current song, with bool arg");
637 conRegFunc!((uint msecs){ aplaySeekMS(msecs*1000); })("song_seek_abs", "absolute seek, in seconds");
638 conRegFunc!((int msecs){ aplaySeekMS(aplayCurTimeMS+msecs*1000); })("song_seek_rel", "relative seek, in seconds");
641 concmd("exec /etc/amper.rc tan"); // global config
642 conProcessQueue(); // load config
643 loadHomeConfig();
644 loadWindowConfig();
645 // local config
646 concmd("exec amper.rc tan");
647 conProcessQueue();
649 conProcessArgs!true(args);
650 conProcessQueue(int.max/4);
652 startRPCServer();
653 scope(exit) stopRPCServer();
655 if (!plVisibleChanged) plVisible = plVisibleCfg;
656 if (!eqVisibleChanged) eqVisible = eqVisibleCfg;
658 sdampwin.show();
659 flushGui();
662 if (plVisible) {
664 static class EventFixupPListPosition {}
665 glconCtlWindow.addEventListener((EventFixupPListPosition evt) {
666 int xmain, ymain, wdtmain, hgtmain;
667 int xpl, ypl, wdtpl, hgtpl;
668 int xwork, ywork, wdtwork, hgtwork;
669 getWorkAreaRect(xwork, ywork, wdtwork, hgtwork);
670 sdampwin.getWindowRect(xmain, ymain, wdtmain, hgtmain);
671 sdplwin.getWindowRect(xpl, ypl, wdtpl, hgtpl);
672 conwriteln("work: (", xwork, ",", ywork, ")-(", wdtwork, "x", hgtwork, ")");
673 conwriteln("main: (", xmain, ",", ymain, ")-(", wdtmain, "x", hgtmain, ")");
674 conwriteln("list: (", xpl, ",", ypl, ")-(", wdtpl, "x", hgtpl, ")");
675 if (GxRect(xmain, ymain, wdtmain, hgtmain).overlaps(GxRect(xpl, ypl, wdtpl, hgtpl))) {
676 int nx = xmain;
677 int ny = ymain+hgtmain;
678 if (nx+wdtpl > xwork+wdtwork) nx = xwork+wdtwork-wdtpl;
679 if (ny+hgtpl > ywork+hgtwork) ny = ywork+hgtwork-hgtpl;
680 if (nx < xwork) nx = xwork;
681 if (ny < ywork) ny = ywork;
682 conwriteln("newpos: (", nx, ",", ny, ")");
683 sdplwin.move(nx, ny);
684 //sdplwin.move(50, 50);
685 flushGui();
688 sdplwin.visibleForTheFirstTime = delegate () {
689 flushGui();
690 switchToWindow(sdampwin);
691 //glconCtlWindow.postTimeout(new EventFixupPListPosition(), 50);
694 sdplwin.show();
698 prepareTrayIcon();
699 flushGui();
701 foreach (string path; args[1..$]) concmdf!"scan_dir \"%s\" tan"(path);
703 glconCtlWindow.eventLoop(0);
705 flushGui();
706 conProcessQueue(int.max/4);