cosmetix in cli
[amper.git] / amper.d
blobedf8e924746540a7e35b8bfd26851328046fe4be
1 module amper is aliced;
3 import std.concurrency;
5 import arsd.color;
6 import arsd.image;
7 import arsd.simpledisplay;
9 import iv.cmdcon;
10 import iv.cmdcongl;
11 import iv.sdpyutil;
12 import iv.strex;
13 import iv.vfs;
15 import aplayer;
17 import egfx;
19 import amperrpc;
20 import amperskin;
21 import amperopts;
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);
34 if (plVisible) {
35 if (sdplwin is null || sdplwin.closed) {
36 if (dgGreatePListWindow !is null) dgGreatePListWindow();
37 } else {
38 vglShowWindow(sdplwin);
40 switchToWindow(sdampwin);
41 flushGui();
43 } else {
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) {
56 //trayicon.close();
57 //trayicon = null;
58 glconCtlWindow.close();
59 return;
61 if (button == MouseButton.left) {
62 hideShowWindows();
63 return;
65 });
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 () {
89 bool doQuit =
90 (glconCtlWindow is null || glconCtlWindow.closed) ||
91 (sdampwin is null || sdampwin.closed);
92 if (doQuit) {
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; }
122 concmd("quit");
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();
137 //glFlush();
138 flushGui();
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;
150 consoleLock();
151 scope(exit) consoleUnlock();
152 conProcessQueue();
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);
165 } else {
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) {
182 if (!evt.success) {
183 conwriteln("ERROR loading '", evt.filename, "'");
184 //glconCtlWindow.close();
185 concmd("song_next");
186 return;
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 () {
217 ampMain.onPaint();
218 //conwriteln("PAINT: sdampwin");
221 sdampwin.visibleForTheFirstTime = delegate () {
222 vglRegisterWindow!"main"(sdampwin);
223 prepareTrayIcon();
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) {
242 if (mainDrag) {
243 if (event.type == MouseEventType.motion /*&& event.modifierState&ModifierState.leftButtonDown*/) {
244 flushGui();
245 Window dummyw;
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;
252 mainDrawPrevX = nx;
253 mainDrawPrevY = ny;
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);
266 flushGui();
268 if (event.type == MouseEventType.buttonReleased && event.button == MouseButton.left) mainDrag = false;
269 return;
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) {
275 flushGui();
276 Window dummyw;
277 mainDrag = true;
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 () {
315 ampPList.onPaint();
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);
324 flushGui();
326 Window dummyw;
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);
331 flushGui();
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) {
376 // scan directory
377 import std.file;
378 if (!append) ampPList.clear();
379 foreach (DirEntry de; dirEntries(path.idup, SpanMode.shallow)) {
380 if (!de.isFile) continue;
381 try {
382 auto sio = AudioStream.open(VFile(de.name));
383 if (sio.valid) {
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));
386 sio.close();
387 } else {
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) {
399 //vbNewScale = 2;
400 //vbufEffScale = 2;
401 glconShowKey = "M-Grave";
403 conRegVar!vbNewScale(1, 8, "v_scale", "window scale");
405 aplayStart();
406 scope(exit) aplayShutdown();
408 conProcessQueue(); // load config
409 conProcessArgs!true(args);
411 loadSkin();
413 createCtlWindow();
414 createAmpWindow();
415 createPListWindow();
416 dgGreatePListWindow = delegate () { createPListWindow(); };
417 if (!plVisible) sdplwin.hide();
418 flushGui();
420 try {
421 GlobalHotkeyManager.register("M-H-A", delegate () { hideShowWindows(); });
422 } catch (Exception e) {
423 conwriteln("ERROR registering hotkey!");
426 conRegFunc!((ConString path, bool append=false) {
427 try {
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");
435 conRegFunc!(() {
436 ampPList.clear();
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);
455 startRPCServer();
456 scope(exit) stopRPCServer();
458 glconCtlWindow.eventLoop(0);
460 flushGui();
461 conProcessQueue(int.max/4);