1 module amper
is aliced
;
3 import std
.concurrency
;
7 import arsd
.simpledisplay
;
24 // ////////////////////////////////////////////////////////////////////////// //
25 __gshared NotificationAreaIcon trayicon
;
26 __gshared Image trayimage
;
27 __gshared MemoryImage icon
; // 0: normal
30 void hideShowWindows () {
31 if (sdampwin
is null || sdampwin
.closed
) return;
32 if (sdampwin
.hidden
) {
33 vglShowWindow(sdampwin
);
35 if (sdplwin
is null || sdplwin
.closed
) {
36 if (dgGreatePListWindow
!is null) dgGreatePListWindow();
38 vglShowWindow(sdplwin
);
40 switchToWindow(sdampwin
);
44 vglHideWindow(sdplwin
);
45 vglHideWindow(sdampwin
);
50 void prepareTrayIcon () {
51 icon
= readPng("skins/notifyicon.png");
52 trayimage
= Image
.fromMemoryImage(icon
);
53 trayicon
= new NotificationAreaIcon("Amper", trayimage
, (MouseButton button
) {
54 //if (button == MouseButton.left) vbwin.switchToWindow();
55 if (button
== MouseButton
.middle
) {
58 glconCtlWindow
.close();
61 if (button
== MouseButton
.left
) {
69 // ////////////////////////////////////////////////////////////////////////// //
70 __gshared
ubyte vbNewScale
= 1;
73 // ////////////////////////////////////////////////////////////////////////// //
74 class ScrollTitleEvent
{}
75 __gshared ScrollTitleEvent evScrollTitle
;
77 shared static this () {
78 evScrollTitle
= new ScrollTitleEvent();
82 // ////////////////////////////////////////////////////////////////////////// //
83 __gshared
bool mainDrag
= false;
84 __gshared
int mainDrawPrevX
, mainDrawPrevY
;
87 // ////////////////////////////////////////////////////////////////////////// //
88 void closeAllIfMainIsClosed () {
90 (glconCtlWindow
is null || glconCtlWindow
.closed
) ||
91 (sdampwin
is null || sdampwin
.closed
);
93 if (sdplwin
!is null && !sdplwin
.closed
) sdplwin
.close();
94 if (sdampwin
!is null && !sdampwin
.closed
) sdampwin
.close();
95 if (glconCtlWindow
!is null && !glconCtlWindow
.closed
) glconCtlWindow
.close();
100 // ////////////////////////////////////////////////////////////////////////// //
101 // create hidden control window
102 void createCtlWindow () {
103 sdpyWindowClass
= "AMPER_PLAYER_CTL";
104 glconCtlWindow
= new SimpleWindow(1, 1, "AmperCtl", OpenGlOptions
.no
, Resizability
.fixedSize
, WindowTypes
.hidden
);
106 glconCtlWindow
.onClosing
= delegate () {
107 //conwriteln("closing ctl window...");
108 if (sdplwin
!is null && !sdplwin
.closed
) sdplwin
.close();
109 if (sdampwin
!is null && !sdampwin
.closed
) sdampwin
.close();
112 glconCtlWindow
.onDestroyed
= delegate () {
113 //conwriteln("ctl window destroyed");
114 closeAllIfMainIsClosed();
117 glconCtlWindow
.addEventListener((QuitEvent evt
) {
118 scope(exit
) if (!conQueueEmpty()) glconPostDoConCommands();
119 scope(exit
) closeAllIfMainIsClosed();
120 if (glconCtlWindow
.closed
) return;
121 if (isQuitRequested
) { glconCtlWindow
.close(); return; }
125 void rebuildRepaint () {
126 scope(exit
) if (!conQueueEmpty()) glconPostDoConCommands();
127 scope(exit
) closeAllIfMainIsClosed();
128 if (glconCtlWindow
.closed
) return;
129 if (isQuitRequested
) { glconCtlWindow
.close(); return; }
130 //conwriteln("rebuilding screen");
132 //if (aplayIsPlaying) ampMain.curtime = aplayCurTime;
134 if (sdampwin
!is null && !sdampwin
.closed
&& !sdampwin
.hidden
) sdampwin
.redrawOpenGlSceneNow();
135 if (sdplwin
!is null && !sdplwin
.closed
&& !sdplwin
.hidden
) sdplwin
.redrawOpenGlSceneNow();
141 glconCtlWindow
.addEventListener((GLConScreenRebuildEvent evt
) { rebuildRepaint(); });
142 glconCtlWindow
.addEventListener((GLConScreenRepaintEvent evt
) { rebuildRepaint(); });
144 glconCtlWindow
.addEventListener((GLConDoConsoleCommandsEvent evt
) {
145 scope(exit
) if (!conQueueEmpty()) glconPostDoConCommands();
146 scope(exit
) closeAllIfMainIsClosed();
147 bool sendAnother
= false;
148 bool prevVisible
= isConsoleVisible
;
151 scope(exit
) consoleUnlock();
153 sendAnother
= !conQueueEmpty();
155 if (glconCtlWindow
.closed
) return;
156 if (isQuitRequested
) { glconCtlWindow
.close(); return; }
157 if (sendAnother
) glconPostDoConCommands();
159 if (prevVisible || isConsoleVisible) glconPostScreenRepaintDelayed();
160 if (vbNewScale != vbufEffScale) glconPostScreenRepaint();
162 if (vbNewScale
!= vglWindowScale(glconCtlWindow
)) {
163 vbNewScale
= vglScaleWindow(glconCtlWindow
, vbNewScale
);
164 vglScaleWindow(sdplwin
, vbNewScale
);
166 glconPostScreenRebuild();
170 glconCtlWindow
.addEventListener((ScrollTitleEvent evt
) {
171 scope(exit
) if (!conQueueEmpty()) glconPostDoConCommands();
172 scope(exit
) closeAllIfMainIsClosed();
173 if (glconCtlWindow
.closed
) return;
174 if (isQuitRequested
) { glconCtlWindow
.close(); return; }
175 //conwriteln("scrolling title");
176 ampMain
.scrollTitle();
177 if (!glconCtlWindow
.eventQueued
!ScrollTitleEvent
) glconCtlWindow
.postTimeout(evScrollTitle
, 100);
178 glconPostScreenRebuild();
181 glconCtlWindow
.addEventListener((EventFileLoaded evt
) {
183 conwriteln("ERROR loading '", evt
.filename
, "'");
184 //glconCtlWindow.close();
188 //conwriteln("playing '", evt.filename, "': ", evt.artist, " -- ", evt.title);
191 glconCtlWindow
.addEventListener((EventFileComplete evt
) {
192 //glconCtlWindow.close();
193 concmd("song_next tan");
198 // ////////////////////////////////////////////////////////////////////////// //
199 void createAmpWindow () {
200 sdpyWindowClass
= "AMPER_PLAYER";
202 sdampwin
= new SimpleWindow(skinImgMain
.width
*vbufEffScale
, skinImgMain
.height
*vbufEffScale
, "Amper", OpenGlOptions
.yes
, Resizability
.fixedSize
, WindowTypes
.undecorated
);
203 sdampwin
.setMinSize(skinImgMain
.width
*vbufEffScale
, skinImgMain
.height
*vbufEffScale
);
204 sdampwin
.setMaxSize(skinImgMain
.width
*vbufEffScale
, skinImgMain
.height
*vbufEffScale
);
205 //sdwin.hideCursor();
207 static if (is(typeof(&sdampwin
.closeQuery
))) {
208 sdampwin
.closeQuery
= delegate () { concmd("quit"); glconPostDoConCommands(); };
211 sdampwin
.windowResized
= delegate (int wdt
, int hgt
) {
212 sdampwin
.setMinSize(wdt
, hgt
);
213 sdampwin
.setMaxSize(wdt
, hgt
);
216 sdampwin
.redrawOpenGlScene
= delegate () {
218 //conwriteln("PAINT: sdampwin");
221 sdampwin
.visibleForTheFirstTime
= delegate () {
222 vglRegisterWindow
!"main"(sdampwin
);
227 sdampwin.handlePulse = delegate () {
231 sdampwin
.handleKeyEvent
= delegate (KeyEvent event
) {
232 if (event
.pressed
&& event
== "Escape") { concmd("quit"); return; }
233 //if (event.pressed && event == "Plus") { concmd("v_scale 2"); return; }
234 //if (event.pressed && event == "Minus") { concmd("v_scale 1"); return; }
235 //conwriteln(event.toStr);
236 ampMain
.onKey(event
);
237 glconPostScreenRebuild();
238 //sdwin.redrawOpenGlSceneNow();
241 sdampwin
.handleMouseEvent
= delegate (MouseEvent event
) {
243 if (event
.type
== MouseEventType
.motion
/*&& event.modifierState&ModifierState.leftButtonDown*/) {
247 int nx
= event
.x
*vbufEffScale
;
248 int ny
= event
.y
*vbufEffScale
;
249 XTranslateCoordinates(sdampwin
.display
, sdampwin
.window
, RootWindow(sdampwin
.display
, DefaultScreen(sdampwin
.display
)), nx
, ny
, &nx
, &ny
, &dummyw
);
250 int dx
= nx
-mainDrawPrevX
;
251 int dy
= ny
-mainDrawPrevY
;
255 XWindowAttributes xwa
;
256 XGetWindowAttributes(sdampwin
.display
, sdampwin
.window
, &xwa
);
257 XTranslateCoordinates(sdampwin
.display
, sdampwin
.window
, RootWindow(sdampwin
.display
, DefaultScreen(sdampwin
.display
)), xwa
.x
, xwa
.y
, &xwa
.x
, &xwa
.y
, &dummyw
);
258 sdampwin
.move(xwa
.x
+dx
, xwa
.y
+dy
);
260 if (sdplwin
!is null && !sdplwin
.closed
) {
261 XGetWindowAttributes(sdplwin
.display
, sdplwin
.window
, &xwa
);
262 XTranslateCoordinates(sdplwin
.display
, sdplwin
.window
, RootWindow(sdplwin
.display
, DefaultScreen(sdplwin
.display
)), xwa
.x
, xwa
.y
, &xwa
.x
, &xwa
.y
, &dummyw
);
263 sdplwin
.move(xwa
.x
+dx
, xwa
.y
+dy
);
268 if (event
.type
== MouseEventType
.buttonReleased
&& event
.button
== MouseButton
.left
) mainDrag
= false;
271 if (!ampMain
.onMouse(event
)) {
273 if (event.type == MouseEventType.buttonPressed && event.button == MouseButton.left) {
274 if (ampMain.widgetAt(event.x, event.y) is null) {
278 mainDrawPrevX = event.x*vbufEffScale;
279 mainDrawPrevY = event.y*vbufEffScale;
280 XTranslateCoordinates(sdampwin.display, sdampwin.window, RootWindow(sdampwin.display, DefaultScreen(sdampwin.display)), mainDrawPrevX, mainDrawPrevY, &mainDrawPrevX, &mainDrawPrevY, &dummyw);
285 glconPostScreenRebuild();
286 //sdwin.redrawOpenGlSceneNow();
290 sdampwin.handleCharEvent = delegate (dchar ch) {
294 sdampwin
.onFocusChange
= delegate (bool focused
) { ampMain
.focusChanged(focused
); };
296 if (!sdampwin
.eventQueued
!ScrollTitleEvent
) glconCtlWindow
.postEvent(evScrollTitle
);
300 // ////////////////////////////////////////////////////////////////////////// //
301 void createPListWindow () {
302 sdpyWindowClass
= "AMPER_PLAYLIST";
304 sdplwin
= new SimpleWindow(skinImgMain
.width
*vbufEffScale
, skinImgMain
.height
*vbufEffScale
, "Amper Playlist", OpenGlOptions
.yes
, Resizability
.allowResizing
, WindowTypes
.undecorated
);
305 sdplwin
.setMinSize(skinImgMain
.width
*vbufEffScale
, skinImgMain
.height
*vbufEffScale
);
306 sdplwin
.setMaxSize(4096, 4096);
307 //sdplwin.setResizeGranularity(5, 4);
308 sdplwin
.setResizeGranularity(25, 29);
310 static if (is(typeof(&sdplwin
.closeQuery
))) {
311 sdplwin
.closeQuery
= delegate () { /*concmd("quit"); glconPostDoConCommands();*/ };
314 sdplwin
.redrawOpenGlScene
= delegate () {
316 //conwriteln("PAINT: sdplwin");
317 //conwriteln(ampPList.curitem);
320 sdplwin
.visibleForTheFirstTime
= delegate () {
321 vglRegisterWindow
!"normal"(sdplwin
);
322 if (sdampwin
!is null && !sdampwin
.closed
) {
323 switchToWindow(sdampwin
);
327 XWindowAttributes xwa;
328 XGetWindowAttributes(sdampwin.display, sdampwin.window, &xwa);
329 XTranslateCoordinates(sdampwin.display, sdampwin.window, RootWindow(sdampwin.display, DefaultScreen(sdampwin.display)), xwa.x, xwa.y, &xwa.x, &xwa.y, &dummyw);
330 sdplwin.move(xwa.x, xwa.y+sdampwin.height);
336 sdplwin
.handleKeyEvent
= delegate (KeyEvent event
) {
337 ampPList
.onKey(event
);
338 glconPostScreenRebuild();
339 //sdplwin.redrawOpenGlSceneNow();
342 sdplwin
.handleMouseEvent
= delegate (MouseEvent event
) {
343 //conwriteln("PLMOUSE BEFORE: ", ampPList.curitem, " : ", event.type);
344 ampPList
.onMouse(event
);
345 //conwriteln("PLMOUSE AFTER: ", ampPList.curitem, " : ", event.type);
346 glconPostScreenRebuild();
347 //sdplwin.redrawOpenGlSceneNow();
350 sdplwin
.windowResized
= delegate (int wdt
, int hgt
) {
351 ampPList
.imgrc
= GxRect(0, 0, wdt
/vbufEffScale
, hgt
/vbufEffScale
);
352 //vglResizeBuffer(wdt/vbufEffScale, hgt/vbufEffScale, vbufEffScale);
353 //sdplwin.redrawOpenGlSceneNow();
356 sdplwin
.onFocusChange
= delegate (bool focused
) { ampPList
.focusChanged(focused
); };
360 // ////////////////////////////////////////////////////////////////////////// //
362 void fakeScanDir () {
363 ampPList.appendListItem(PListItem("Sonata Arctica \u2014 Replica", "/mnt/muzax/wtf", 142));
364 ampPList.appendListItem(PListItem("Zonata \u2014 Geronimo", "/mnt/muzax/wtf", 242));
366 foreach (immutable int idx; 3..42) {
367 import std.format : format;
368 ampPList.appendListItem(PListItem("song #%d".format(idx), "/mnt/muzax/wtf", 100+idx));
374 // ////////////////////////////////////////////////////////////////////////// //
375 void scanDir (ConString path
, bool append
) {
378 if (!append
) ampPList
.clear();
379 foreach (DirEntry
de; dirEntries(path
.idup
, SpanMode
.shallow
)) {
380 if (!de.isFile
) continue;
382 auto sio
= AudioStream
.open(VFile(de.name
));
384 ampPList
.appendListItem(PListItem(sio
.artist
~" \u2014 "~sio
.title
, de.name
, cast(int)(sio
.timetotal
/1000)));
385 //conwriteln("+++ ", de.name, ": ", sio.artist, " -- ", sio.title, ": ", cast(int)(sio.timetotal/1000));
388 //conwriteln("+++ ", de.name, ": FUUUUUUU");
390 } catch (Exception e
) {}
392 //if (modeShuffle) ampPList.state.curitem = ampPList.findShuffleFirst();
393 //conwriteln(ampPList.findShuffleFirst(), " : ", ampPList.state.shuffleidx, " : ", ampPList.state.curplayingitem);
397 // ////////////////////////////////////////////////////////////////////////// //
398 void main (string
[] args
) {
401 glconShowKey
= "M-Grave";
403 conRegVar
!vbNewScale(1, 8, "v_scale", "window scale");
406 scope(exit
) aplayShutdown();
408 conProcessQueue(); // load config
409 conProcessArgs
!true(args
);
416 dgGreatePListWindow
= delegate () { createPListWindow(); };
417 if (!plVisible
) sdplwin
.hide();
421 GlobalHotkeyManager
.register("M-H-A", delegate () { hideShowWindows(); });
422 } catch (Exception e
) {
423 conwriteln("ERROR registering hotkey!");
426 conRegFunc
!((ConString path
, bool append
=false) {
428 scanDir(path
, append
);
429 } catch (Exception e
) {
430 conwriteln("scanning error: ", e
.msg
);
432 glconPostScreenRebuild();
433 })("scan_dir", "scan the given directory; 2nd ard is \"append\" bool flag");
437 glconPostScreenRebuild();
438 })("pl_clear", "clear playlist");
440 conRegFunc
!((int plidx
, bool forcestart
=false) {
441 if (!sdampwin
.closed
) {
442 ampPList
.playSongByIndex(plidx
, forcestart
);
443 glconPostScreenRebuild();
445 })("song_play_by_index", "play song from playlist by index");
447 conRegFunc
!((){ if (!sdampwin
.closed
) ampPList
.playPrevSong(); })("song_prev", "play previous song");
448 conRegFunc
!((bool forceplay
=false){ if (!sdampwin
.closed
) ampPList
.playNextSong(forceplay
); })("song_next", "play next song");
449 conRegFunc
!((){ if (!sdampwin
.closed
) ampPList
.playCurrentSong(); })("song_play", "play current song");
450 conRegFunc
!((){ if (!sdampwin
.closed
) ampPList
.stopSong(); })("song_stop", "stop current song");
451 conRegFunc
!((){ aplayTogglePause(); })("song_pause_toggle", "pause/unpause current song");
453 foreach (string path
; args
[1..$]) concmdf
!"scan_dir \"%s\" tan"(path
);
456 scope(exit
) stopRPCServer();
458 glconCtlWindow
.eventLoop(0);
461 conProcessQueue(int.max
/4);