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/>.
18 import std
.concurrency
;
22 import arsd
.simpledisplay
;
39 // ////////////////////////////////////////////////////////////////////////// //
40 class EventSaveWindows
{}
41 __gshared EventSaveWindows evSaveWindows
;
43 shared static this () { evSaveWindows
= new EventSaveWindows(); }
46 // ////////////////////////////////////////////////////////////////////////// //
47 public GxRect
getWorkArea () {
49 getWorkAreaRect(rc
.x0
, rc
.y0
, rc
.width
, rc
.height
);
54 // ////////////////////////////////////////////////////////////////////////// //
55 class GlobalHotkeyEx
: GlobalHotkey
{
57 override void doHandle () { concmd(cmd
); }
59 this (ConString kname
, ConString acmd
) {
65 __gshared GlobalHotkeyEx
[] ghbindings
; // key is key, value is command
68 void removeAllBindings () {
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
) {
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
;
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
) {
101 ghbindings
[idx
] = bind
;
105 auto optr
= ghbindings
.ptr
;
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))) {
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) {
156 sdpyWindowClass
= "AMPER_HINT_WINDOW";
157 auto wsz
= hintWindowSize();
159 auto wrc
= getWorkArea();
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
;
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));
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));
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
);
204 GetAtom
!("_NET_WM_STATE", true)(sdhint
.impl
.display
),
207 0 /*PropModeReplace*/,
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
) {
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
);
232 if (sdeqwin
!is null && !sdeqwin
.closed
&& !sdeqwin
.hidden
) { /*saveEqWindowPosition();*/ sdeqwin
.hideInternal(); }
233 if (sdplwin
!is null && !sdplwin
.closed
&& !sdplwin
.hidden
) { /*savePlWindowPosition();*/ sdplwin
.hideInternal(); }
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
) {
253 if (button
== MouseButton
.left
) {
254 concmd("win_toggle");
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 () {
306 (glconCtlWindow
is null || glconCtlWindow
.closed
) ||
307 (sdampwin
is null || sdampwin
.closed
);
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;
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;
343 getWindowRect(sdampwin
, ax
, ay
, aw
, ah
);
344 if (aw
< 1 || ah
< 1) return;
347 getWindowRect(sw
, x
, y
, w
, h
);
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; }
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();
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
) {
430 conwriteln("ERROR loading '", evt
.filename
, "'");
431 ampMain
.newSong("not playing");
432 setHint("not playing");
435 string cursong
= evt
.artist
~" \u2014 "~evt
.title
;
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;
446 ampPList
.scanResult(evt
.filename
, evt
.album
, evt
.artist
, evt
.title
, evt
.durationms
);
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 () {
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();
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
) {
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
) {
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))) {
529 static if (is(T
== string
)) alias fn
= fname
; else string fn
= fname
.idup
;
530 ampPList
.appendListItem(fn
);
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;
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");
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
) {
570 glconPostScreenRepaint();
571 } catch (Exception e
) {
572 conwriteln("ERROR loading skin: ", e
.msg
);
578 scope(exit
) aplayShutdown();
582 loadSkin
!true("!BUILTIN!");
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) {
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");
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
645 concmd("exec amper.rc tan");
648 conProcessArgs
!true(args
);
649 conProcessQueue(int.max
/4);
652 scope(exit
) stopRPCServer();
654 if (!plVisibleChanged
) plVisible
= plVisibleCfg
;
655 if (!eqVisibleChanged
) eqVisible
= eqVisibleCfg
;
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))) {
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);
687 sdplwin.visibleForTheFirstTime = delegate () {
689 switchToWindow(sdampwin);
690 //glconCtlWindow.postTimeout(new EventFixupPListPosition(), 50);
700 foreach (string path
; args
[1..$]) concmdf
!"scan_dir \"%s\" tan"(path
);
702 glconCtlWindow
.eventLoop(0);
705 conProcessQueue(int.max
/4);