From bdc7ff9d08b2f4030e0c27c7752c4bdf2fed6172 Mon Sep 17 00:00:00 2001 From: ch1p Date: Sun, 29 Dec 2013 17:43:49 +0200 Subject: [PATCH] initial commit --- .gitignore | 7 + LICENSE | 2 +- README.md | 4 +- desktop/Makefile | 29 ++ desktop/grab.c | 51 ++++ desktop/grab.h | 15 + .../icons/Faenza-Radiance/apps/16/vkpc.png | Bin 0 -> 512 bytes .../icons/Faenza-Radiance/apps/22/vkpc.png | Bin 0 -> 685 bytes .../icons/Faenza-Radiance/apps/24/vkpc.png | Bin 0 -> 621 bytes .../icons/Faenza-Radiance/apps/32/vkpc.png | Bin 0 -> 904 bytes .../icons/Faenza-Radiance/apps/48/vkpc.png | Bin 0 -> 1343 bytes .../icons/Faenza-Radiance/apps/64/vkpc.png | Bin 0 -> 1747 bytes .../icons/Faenza-Radiance/apps/96/vkpc.png | Bin 0 -> 2717 bytes desktop/icons/Faenza/apps/16/vkpc.png | Bin 0 -> 627 bytes desktop/icons/Faenza/apps/22/vkpc.png | Bin 0 -> 824 bytes desktop/icons/Faenza/apps/24/vkpc.png | Bin 0 -> 804 bytes desktop/icons/Faenza/apps/32/vkpc.png | Bin 0 -> 1141 bytes desktop/icons/Faenza/apps/48/vkpc.png | Bin 0 -> 1685 bytes desktop/icons/Faenza/apps/64/vkpc.png | Bin 0 -> 2288 bytes desktop/icons/Faenza/apps/96/vkpc.png | Bin 0 -> 3596 bytes desktop/icons/hicolor/128x128/apps/vkpc.png | Bin 0 -> 3663 bytes desktop/icons/hicolor/22x22/apps/vkpc.png | Bin 0 -> 664 bytes desktop/icons/hicolor/24x24/apps/vkpc.png | Bin 0 -> 615 bytes desktop/icons/hicolor/32x32/apps/vkpc.png | Bin 0 -> 860 bytes desktop/icons/hicolor/36x36/apps/vkpc.png | Bin 0 -> 999 bytes desktop/icons/hicolor/40x40/apps/vkpc.png | Bin 0 -> 1119 bytes desktop/icons/hicolor/48x48/apps/vkpc.png | Bin 0 -> 1302 bytes desktop/icons/hicolor/64x64/apps/vkpc.png | Bin 0 -> 1917 bytes desktop/icons/hicolor/72x72/apps/vkpc.png | Bin 0 -> 2184 bytes desktop/icons/hicolor/96x96/apps/vkpc.png | Bin 0 -> 2825 bytes desktop/icons/ubuntu-mono-dark/16/vkpc.png | Bin 0 -> 479 bytes desktop/icons/ubuntu-mono-dark/22/vkpc.png | Bin 0 -> 559 bytes desktop/icons/ubuntu-mono-dark/24/vkpc.png | Bin 0 -> 597 bytes desktop/icons/ubuntu-mono-light/16/vkpc.png | Bin 0 -> 359 bytes desktop/icons/ubuntu-mono-light/22/vkpc.png | Bin 0 -> 396 bytes desktop/icons/ubuntu-mono-light/24/vkpc.png | Bin 0 -> 414 bytes desktop/info.h | 13 + desktop/install_icons.sh | 16 ++ desktop/main.c | 140 ++++++++++ desktop/server.c | 182 ++++++++++++ desktop/server.h | 14 + desktop/vector.c | 74 +++++ desktop/vector.h | 23 ++ extensions/chrome/_locales/en/messages.json | 8 + extensions/chrome/common.js | 187 +++++++++++++ extensions/chrome/inject_and_return.js | 28 ++ extensions/chrome/inject_exec.js | 74 +++++ extensions/chrome/manifest.json | 23 ++ extensions/firefox/chrome.manifest | 2 + extensions/firefox/chrome/background.js | 258 ++++++++++++++++++ extensions/firefox/chrome/inject_on_load.js | 149 ++++++++++ extensions/firefox/chrome/overlay.xul | 6 + extensions/firefox/install.rdf | 20 ++ 53 files changed, 1323 insertions(+), 2 deletions(-) create mode 100644 .gitignore create mode 100644 desktop/Makefile create mode 100644 desktop/grab.c create mode 100644 desktop/grab.h create mode 100644 desktop/icons/Faenza-Radiance/apps/16/vkpc.png create mode 100644 desktop/icons/Faenza-Radiance/apps/22/vkpc.png create mode 100644 desktop/icons/Faenza-Radiance/apps/24/vkpc.png create mode 100644 desktop/icons/Faenza-Radiance/apps/32/vkpc.png create mode 100644 desktop/icons/Faenza-Radiance/apps/48/vkpc.png create mode 100644 desktop/icons/Faenza-Radiance/apps/64/vkpc.png create mode 100644 desktop/icons/Faenza-Radiance/apps/96/vkpc.png create mode 100644 desktop/icons/Faenza/apps/16/vkpc.png create mode 100644 desktop/icons/Faenza/apps/22/vkpc.png create mode 100644 desktop/icons/Faenza/apps/24/vkpc.png create mode 100644 desktop/icons/Faenza/apps/32/vkpc.png create mode 100644 desktop/icons/Faenza/apps/48/vkpc.png create mode 100644 desktop/icons/Faenza/apps/64/vkpc.png create mode 100644 desktop/icons/Faenza/apps/96/vkpc.png create mode 100644 desktop/icons/hicolor/128x128/apps/vkpc.png create mode 100644 desktop/icons/hicolor/22x22/apps/vkpc.png create mode 100644 desktop/icons/hicolor/24x24/apps/vkpc.png create mode 100644 desktop/icons/hicolor/32x32/apps/vkpc.png create mode 100644 desktop/icons/hicolor/36x36/apps/vkpc.png create mode 100644 desktop/icons/hicolor/40x40/apps/vkpc.png create mode 100644 desktop/icons/hicolor/48x48/apps/vkpc.png create mode 100644 desktop/icons/hicolor/64x64/apps/vkpc.png create mode 100644 desktop/icons/hicolor/72x72/apps/vkpc.png create mode 100644 desktop/icons/hicolor/96x96/apps/vkpc.png create mode 100644 desktop/icons/ubuntu-mono-dark/16/vkpc.png create mode 100644 desktop/icons/ubuntu-mono-dark/22/vkpc.png create mode 100644 desktop/icons/ubuntu-mono-dark/24/vkpc.png create mode 100644 desktop/icons/ubuntu-mono-light/16/vkpc.png create mode 100644 desktop/icons/ubuntu-mono-light/22/vkpc.png create mode 100644 desktop/icons/ubuntu-mono-light/24/vkpc.png create mode 100644 desktop/info.h create mode 100755 desktop/install_icons.sh create mode 100644 desktop/main.c create mode 100644 desktop/server.c create mode 100644 desktop/server.h create mode 100644 desktop/vector.c create mode 100644 desktop/vector.h create mode 100644 extensions/chrome/_locales/en/messages.json create mode 100644 extensions/chrome/common.js create mode 100644 extensions/chrome/inject_and_return.js create mode 100644 extensions/chrome/inject_exec.js create mode 100644 extensions/chrome/manifest.json create mode 100644 extensions/firefox/chrome.manifest create mode 100644 extensions/firefox/chrome/background.js create mode 100644 extensions/firefox/chrome/inject_on_load.js create mode 100644 extensions/firefox/chrome/overlay.xul create mode 100644 extensions/firefox/install.rdf diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c421d4e --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +desktop/icons/*_psd +extensions/chrome.crx +extensions/chrome.pem +extensions/chrome.zip +extensions/firefox.xpi +desktop/*.o +desktop/vkpc diff --git a/LICENSE b/LICENSE index 133d991..92f4195 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2013 41P +Copyright (c) 2013 ch1p Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/README.md b/README.md index 06d22ba..1c3a5e8 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,6 @@ -vkpc-linux +VK Player Controller ========== Application for Linux that allows you to control audio player on vk.com by media keys. + +Installation instructions: http://ch1p.com/vkpc/?linux diff --git a/desktop/Makefile b/desktop/Makefile new file mode 100644 index 0000000..49e6f7a --- /dev/null +++ b/desktop/Makefile @@ -0,0 +1,29 @@ +LIBS = gtk+-2.0 glib-2.0 cairo pango gdk-pixbuf-2.0 atk libwebsockets x11 +CC = gcc +CCFLAGS = -Wall -std=c99 -pthread `pkg-config --cflags --libs ${LIBS}` +LDFLAGS = -lm +BINARIES = vkpc + +all : vkpc + +vkpc : server.o grab.o vector.o main.o + ${CC} ${CCFLAGS} server.o grab.o vector.o main.o ${LDFLAGS} -o vkpc + +server.o : server.c + ${CC} ${CCFLAGS} -c server.c + +grab.o : grab.c + ${CC} ${CCFLAGS} -c grab.c + +vector.o : vector.c + ${CC} ${CCFLAGS} -c vector.c + +main.o : main.c + ${CC} ${CCFLAGS} -c main.c + +install: + cp vkpc /usr/bin + sh install_icons.sh + +clean: + rm -f $(BINARIES) *.o diff --git a/desktop/grab.c b/desktop/grab.c new file mode 100644 index 0000000..716b0c6 --- /dev/null +++ b/desktop/grab.c @@ -0,0 +1,51 @@ +#include +#include +#include +#include +#include +#include "grab.h" + +static int error_handler(Display *dpy, XErrorEvent *err) { + fprintf(stderr, "Failed to grab key!\n"); + return 0; +} + +void grab_init(void (*handler)(enum HotkeyEvent e)) { + Display *dpy = XOpenDisplay(0); + Window root = DefaultRootWindow(dpy); + XEvent ev; + + struct Hotkey hotkeys[HOTKEYS_COUNT] = { + { HK_PAUSE, XKeysymToKeycode(dpy, XF86XK_AudioPause) }, + { HK_PLAY, XKeysymToKeycode(dpy, XF86XK_AudioPlay) }, + { HK_NEXT, XKeysymToKeycode(dpy, XF86XK_AudioNext) }, + { HK_PREV, XKeysymToKeycode(dpy, XF86XK_AudioPrev) } + }; + + XSetErrorHandler(error_handler); + + for (int i = 0; i < HOTKEYS_COUNT; i++) { + XGrabKey(dpy, hotkeys[i].keycode, 0, root, false, GrabModeAsync, GrabModeAsync); + } + + XSelectInput(dpy, root, KeyPressMask); + while (true) { + XNextEvent(dpy, &ev); + + switch (ev.type) { + case KeyPress: ; + for (int i = 0; i < HOTKEYS_COUNT; i++) { + if (ev.xkey.keycode == hotkeys[i].keycode) { + (*handler)(hotkeys[i].event); + break; + } + } + break; + + default: + break; + } + } + + XCloseDisplay(dpy); +} diff --git a/desktop/grab.h b/desktop/grab.h new file mode 100644 index 0000000..762f9d8 --- /dev/null +++ b/desktop/grab.h @@ -0,0 +1,15 @@ +#ifndef GRAB_H__ +#define GRAB_H__ + +enum HotkeyEvent { + HK_PREV, HK_NEXT, HK_PAUSE, HK_PLAY, + HOTKEYS_COUNT +}; +struct Hotkey { + enum HotkeyEvent event; + int keycode; +}; + +void grab_init(); + +#endif diff --git a/desktop/icons/Faenza-Radiance/apps/16/vkpc.png b/desktop/icons/Faenza-Radiance/apps/16/vkpc.png new file mode 100644 index 0000000000000000000000000000000000000000..1eaf260c0dfc868f9720cec062997cc50b036e3f GIT binary patch literal 512 zcmV+b0{{JqP)M3xos=f&@V-Q78lt&4v<48`|!6`{KI;0(3W5Qm2_r zM)AFd*>ob2_)4eKN_vjt1Yn!m&1SR0IY9HGwK4%mMXY@e*5mQG9f?GaAUsk_Y&n~S zyO2yKMRVV7xA#b-L~BW>+wCeMJtI0sRATxJ?U`SJ!gLi=C8*cy7eNq&5??2f4{`H- zUrfrmTyBY|253*(| zDXwF4g%whqF}VKEK$0-YgLp!06Olu*k7~8Li|CxIO=9d|5;mL7C(a(Mzds6GmFIa1 z@@uVD+i_i2BI};4Mx#-bhZP(Aw_Ft2M!Q`r#X@C4u(G$3HR3O@&?X>+X?EErAc(lcA|b&rc_)+c zoFRi7ovkE@;DwVr_j{c0%y;jdD6Z?uDuya68vh=#QYaMuh@f7t|D{$ait+_;7x?hg zth@wTz!UB*0gYzhWYZQREJiFOeJx3f7(-eh0)7iA0`&)L7V9?5Sm0hd!~+pZR<) zv)PREBamz&C>QI>1VfulCO08sn@U*(>v#Qr|MBU~)BOh$Bj5z>dk%FE z**7FznNFuiBp|WJs%f;pfnv|byy0-TQ>|8=Kw_#MjYh@yULZQDZ-Zy~vgwxoVNpNN|PpU{>ywuS`|yIU%iq*kl#1zr%x zairJlNvG2}hLY=0bmAW+f@(qfdORLqM)(2O5Fz<|p4csy%lCg%><_ALx7&P83u@Lf znamEzx3F0z2#s#H`w@NzDz|gF+;^Oe+b9r$pKaR!#j`WN6?{diNvG42&1Susia{)m zMnjs-rgtf*v{I=AUTHnZ$bNGpTdfv*?iJw%awBm9JfA&NI?!Yw6xWhEfGNxHM9k}IYlASEKNZ{AZHLbC^Ym13J$4Arg?e&-f=}l zg-a0RgU7r7?|uBL7;a;z|8->SBCKHo$ z1mtXfOOQ&X?m)6foe%*E&G~%3et9|t&V(_oRx1z&yM^L9ilrP(v%&NH<&Q79*=(+& zSd}`mSS*yh@Avx$R%KJh1Ss73dmTcQello zLs|h5P<*XclN1w37z(pesnnB5ByNAaMQ?)SO>{qr&OB6rh|nQZsZ^4mUMOKorBcGG z)habH4_Bn62}r`>u+L_*onc*;&pzZlL$7c(2k3gzH2Hj<<#M_0WHK3P=|hXh<7sp@ ziVpAJt{^F`1;r2Q_4*LP_UVjinZw~AT?_;QOD%uNu~>{R7K;NIKZ12!px6_vST2{( zqtWO~p-`BGZx?voYPB+_)5+|1`(#TiYX9uo1b>I%Re%8iV_Cb}DDUQ)00000NkvXX Hu0mjf6SNlP literal 0 HcmV?d00001 diff --git a/desktop/icons/Faenza-Radiance/apps/32/vkpc.png b/desktop/icons/Faenza-Radiance/apps/32/vkpc.png new file mode 100644 index 0000000000000000000000000000000000000000..4107fc389b36abd58895010a9ad6737334af28c4 GIT binary patch literal 904 zcmV;319$w1P)V#g)X}5N<~FbF$-M<6~PdSAWESsF6$!MqzNue47d?2 z^pl3yCQT%tmwN6a6P~eELl-K%3zx~+7Gbh=f8!C-_la?E{#AdI&b2*Q26UjK&YySlm<<_@h^ zdqPz~?ie6J315*;9--yWsMn^{vW6edZ`9UG%?=CGZef%u}vPPp3 z#*WsT&6&kwVP>;=+T-zzHP6?$%P~pF3z&z2H<(PORu1^MBuO`S>$NL^cs#z1+j@tX zQxP<0Hk(bbTCFdAKHo{iPqh<3Y#d~70T%#HCQdo1vw`O4VzF4Yqwhc<5YRiF&NPY~ zqB)ANQmMqs<+3a(H6iKAYPFi2BMgxxLi9wTBr6t+Qws|VPxkr_==Mr1E-uz4CMKpK z=R3$tZ&@zf)6*m00mS5yj4RbVo>sh4sSr8azP^OPVDJ%0O!1uR;x3(A49;s2&#Wx!n73I9$Ovi5p7prg)7cD7@;#a~&qtP-*oCfAGj$$2zMW`X#?RF6amZ6GS{X?P>Pzk66 e{wV_A1sDJ*3{k(|hcF`m00006m#TguV-h`+WDFd;b4(&b{}%QBNci+JoHk zvc*9B^8Yje^cA0M}#0H7RnUW1{(Fc>pgs|tAn@gttp zJDD3nZXν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 literal 0 HcmV?d00001 diff --git a/desktop/icons/Faenza/apps/16/vkpc.png b/desktop/icons/Faenza/apps/16/vkpc.png new file mode 100644 index 0000000000000000000000000000000000000000..29e399188895fa2eb446f9490420e4f6e55efdc2 GIT binary patch literal 627 zcmV-(0*w8MP)@en$a!-i5(7g=hWHQULIVj>a?~gT3DiSix4J&E2A0>?@gAS| zn5_->M%P)RXv4S9+28)w+G~A#6Y{TzOccex0;tt$IWRwu5g^HJ)a@b8Y~(&6E^XxA z(231vvx$DcFAW9*DISkMwA<~s8jVJ^+wG1Kqf93A1a(}wTz-j~b+6Yu$mjFVkh?C& zv>S`XTBFg32!fz+yWMx2&E`Qam-_;Bn(1_^V;JTl%d%>d$#io#9MU>@I-ULsY=hay z&}cM`QmIsPI-OTgbR!fBZLL#tEYASSh1?LGc?ak@aUvfqnByt~pIY%4G zaokm0|A%8Qk@{UOm&9l^p2zYI!tlp~`GL5N&>iM>VD$z0|Hm%@1^{DXDGEsWi?9Fy N002ovPDHLkV1l6>94r6; literal 0 HcmV?d00001 diff --git a/desktop/icons/Faenza/apps/22/vkpc.png b/desktop/icons/Faenza/apps/22/vkpc.png new file mode 100644 index 0000000000000000000000000000000000000000..73daaa8eec1f4aa6c8be2f558634e285fb5418cb GIT binary patch literal 824 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 KMNUMnLSTaS4kW(- literal 0 HcmV?d00001 diff --git a/desktop/icons/Faenza/apps/96/vkpc.png b/desktop/icons/Faenza/apps/96/vkpc.png new file mode 100644 index 0000000000000000000000000000000000000000..d0e8b617cf7ef52295369b4e841c2d1011689f52 GIT binary patch literal 3596 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 U_XS89>h}&XF|;svtLKLPA18OSkpKVy literal 0 HcmV?d00001 diff --git a/desktop/icons/hicolor/128x128/apps/vkpc.png b/desktop/icons/hicolor/128x128/apps/vkpc.png new file mode 100644 index 0000000000000000000000000000000000000000..7c5f49e65074fe41d1ea409be47af380d2de0f6d GIT binary patch literal 3663 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 literal 0 HcmV?d00001 diff --git a/desktop/icons/hicolor/22x22/apps/vkpc.png b/desktop/icons/hicolor/22x22/apps/vkpc.png new file mode 100644 index 0000000000000000000000000000000000000000..0f9186c32f80edce07c6e6eee0620ce758b99c94 GIT binary patch literal 664 zcmV;J0%!e+P)2H%3r^@YxL$Zv6QAX$C@!K@dpq2V!QlpjZhCCK&(4%~OS+UOxPQl0p80yamN> zB}@L^JhGbU=jH5#QfriKR&%($0M%zk&&52kb#krA1KHC`{%d0@@9T}F05_aFRW}L z#>B#+1ri78`}gm!(8H4(XMuB`UH(Fx+49iDct&WB;{;`YXioe9)bkB0#|Ok*P;n6W y1;lT_2HWN>q^L|dJSm=mu7Gj0YJvfP00RJ%R{YF8s7WIL0000xBdf$7!=zF)~wV9D*pfI^3g6JUkHfVfS3n}oq%#+y?_7yjY87H@ayM~ z%Qz$X-IJSB|NZ~>=N~ZaK=|*!e`O%?(;Ft;2C~1x#6fs{OZY$k>J?J|85w<$#Q%JJ z`Jfl41;lu z)*nYUlM$qmk&zKDU>s1OEun6D8YsmHO$*n7>?k1PJX-vP1Mv!w8DPu){5o)KNqyj_ zmk&SSO+{xnOt|sm>!%qAZ!>5D>GR0J1H~(Wm=VT*ar0Cmvf=nL+TWW;)^z;&^XJyz zf1r3E4vVT=#t@y8pIq4c`ptvOX~2m6jK$?Z{O|YQzkhBbivbf*u}ksdZeojy|L52B z9DDolS{N`Qp8+G10U7`L@nikbxh0Ojfi#+2iBsX?GGfc3-xt<)?7hBgj@|dq@0$Po z`F#oK@*hCg{{Qp)*YO{p-aNmszBA+J_b&^9;=kZY`|qDHyr~Ea07-HKF()+VgZQAx z{03!%ayJ(&H9>Rw8+bW4JPJEn`eL-oBnSWk3;-FNs*M5+zQh0k002ovPDHLkV1k&# BEENC% literal 0 HcmV?d00001 diff --git a/desktop/icons/hicolor/32x32/apps/vkpc.png b/desktop/icons/hicolor/32x32/apps/vkpc.png new file mode 100644 index 0000000000000000000000000000000000000000..8cad27c8d3e6eca3ac6f947b962e57347da5e0a9 GIT binary patch literal 860 zcmV-i1Ec(jP)WDXF&Uegrzm0X7~!dwChf#BMeAcL<1u@De* zLe-oDVym^y;Xi@$AWQcFu`Zf9bAa-x$O1sMwLq#7WHb=}1>#RoDI=iT2QYh4(g0B6 z%ilkL-U8(~LGn;+^zZLq2dMnJ=eN#g0mc3x$%BH^AKhpF{{4>u$}vLKFaxn5P#w>Q z=MO&MON9TweSSadA5b+i{`>!bILzE*E4xno`TOTSiu^y+@;sn=g#l1N4c(kyKYwi7 zJE#03-du3~(CVfC{{4H7>_bLIrZE4i6~a&@fBygb{|+q#exv&SUlyu782|b3{C*cS z-Qae><4XrWe*f}eHmd6xxc~n9R|U5K&1XRT4<_eZxm+6<^6}_ufM!lRvZV18G3CI2 zpd+RMeenjAP+%Ac@_Z_m=`ldOz=13W3jv@!8_+HDfqXV(`MAQZWAc<@a6(! zai{F5wH!Qxjc9JW29$~h(#L=_Cz3eO0gP5aMl~o2pyl^>Pc9vByu4%fW#r(8+l#{i z45G@0g2sV`XBnB86)7ojzI}XMa&TtB1ZY;oD2Q+t#jo$4|M2PclL}z`1JfKR-pRwP zoP4b4mJ<^NFu?AZJ8LcrJ6ALXfdH~RX?IV|B9cM?3Vz-?xV-f5pWo-v5*-%&@9V#R ze;;9y0~SL6=eZOwP9&=~e0pjBi$~{nME?2n=L)`{{`%of{kgT>wts&AI*LV}8Hi^) z6)kolr#k+B=g`XAH}=f){r&yRVxVvSVhaIaR@?Xf;g!dCkFAXbhS)XiA@Og4W8orB za@qs`UR*u=@bL7^+@}}!+5Yag(TwFH&Mm6o+{44_FI>`9e$VV}O1Dl6kKW zz*X|h0Spe8?7($p1UNed&`1IL5DCWxbTxoJz$ZW@pdO$Cl#$6&NCHe!W9k9l2#fI+&dbyo)LvP5N)-7{iU$`8v(aF5(w2uA=&vE|ybjerN&R6q z8cXO|!WR~`|61{S3-4>w+d;F*P%0A*o_Y?0zJW44#-!60IE)Zk|M8LD41}=&VCKNM ziwGr1c?|11X3epMh{VfsdO_e-&95@^5ypQD2P6tgXQAg*JU=&HQOQX4rMmLJ}~^zzwAB zX3kVv{UAOg;z|*3(b2+Myrc``mOZC8_-)2} zAGA3vKHA}V0ie#|jd&>$vaDWFAI zg*<0CQZWHn101U?TdG5|5}Z)5tYT1qe}x@d36VX*u9km%=WmeHA*VzCokM>F7y!KA VsHJEx^8o+=002ovPDHLkV1lGb&SU@p literal 0 HcmV?d00001 diff --git a/desktop/icons/hicolor/40x40/apps/vkpc.png b/desktop/icons/hicolor/40x40/apps/vkpc.png new file mode 100644 index 0000000000000000000000000000000000000000..df33c0913f2f3634ed8fadae9e740ef01588a5bf GIT binary patch literal 1119 zcmV-l1fctgP)C`|b3ffBr4OFpO+8n_M=^%`ea|&@a$0@E@C! zs}Ka?)dU-k75gNhj2L7+;|jbIE#p!t&Y{Czaiv`sp4TwHH=u!x&zevpsp4&XqR)YVhq^>{Y}sx zDsl~)YI^lBCsEh_QaexKpsa*ysIU** z$MmhGb`yxx`E(Z7UOQ0aayhFJ8w$jj1xS6N#M8Fv_8lLfo} zQ*#ExARB%qy@P@bn3b@#0+pyWp>(Z>(He=r@som-gWQ1ZdlqCSwaLB+oaD4YuEd_@2wTrH7=+9DgKn<16e=T6=%MUP)+^cI_uR+$ZC%=JZ)daDyGokuTz;9Eo%v?I-+bTfe6xoz#?)d? zG_}Z+LBJqj5HJWB1PlTOf%}=v$_hd#?g2JdHz%aPcl!$C>OVJjGpltfsBX4Emafti zaP2FM&KmH)Yz?3cuo94|7&ibI034HaB`F2-F_75^&0d(37;z#cBgp~j2Rq8+3Kfh3V$3h=A6y$JXbU?s=~0c&B-n`#z_*#qvb)^QPw zK}@OsGDR$WINJhP`2LekCBBh?T?85FXB9;LakUm0uS9CskWCE-eYEaP+|g1Wi3I(p zB%*2nk+mBtzs;WUNs5B{#GmbFK?d_AIW8PxRGz1R-5n$u34$QFub%v z2$TsE1~rL-P^2UvVX)__Ulg&2s0h!;j3EFdElu^hV2JcO=9% zk&H^w=pDzN(>B?KrhZVz;Ceoy3_^m+bEk&uP~dj%-k-jypU*iUj=68ufE76Pl|k9C z+B=jMkin5H!1?DqgH2CAtFihRR{n#b-gd7Rfta_DBAN1?M;k3Esc#bA$Sg>#g2Lc5 zYfS?6 z84A%kq`oGr?P0mR1+bIUPsDh5pu4ST_@BqFfX-`5Cs0Fmh$rsr`gdVcC^0>{1%7E`yp{ynDp{jE1Di# zko!RA*@GQH@7P|gQ!{a(Y{-AkD{oq_ zg_m|W`hPiIUe1LB$91e#o_u9Zss(24ZGe4Me%X_mIZrsuDa`Z8WLW`2JqRP3r(!%H zc=wOX`Hw!6A5sdo)osmq?(O}hHv2={;iO(JsW4}qc#-F>2PZt6I!;v$saYUl0_)yu zn98rzZIzT|WI`5MnR^3DLD@L;jR_AxE>*sNtA#aKKG-Ith0y&~BEHWY^3D9+2A*(f zkLjy&X%EhK{$#k&|C%%{uo(aFGro-)1PlTO0fT@+z#wox)4u`?09k?^7=n%bzyJUM M07*qoM6N<$f+8MmhX4Qo literal 0 HcmV?d00001 diff --git a/desktop/icons/hicolor/64x64/apps/vkpc.png b/desktop/icons/hicolor/64x64/apps/vkpc.png new file mode 100644 index 0000000000000000000000000000000000000000..f94329abd8b24d4507c07e754ea7c7fa0d669155 GIT binary patch literal 1917 zcmV-@2ZH#CP)={L(jaoJ_x?t;cJZ}OO7-kaaN@Atmry>9?<94G0|g_891A`&1H zAQB)FAQB)FAQB)FAQB)FAQB)FAQI3Yi`4uELdY(Fkhr6-Re+Q0L+$Esp8pUvz*dI^ zaYu((Ys*|8+T2q<>CWX7)Tk*7B0;M{;Mb8*0Soc(94vHcfjQB@`96p7*}H{VTSiY^ zh;0ms<|h&=?hOKvxk?Bofaa~r2%~6eDqMT?pimE3qalKpx?Jx@&W2DlfAyiQh@xXj z2ZJ%YBs)GLZNb{37I|e2`U$jbKyDWTtrZ=N?MiLs)!ElF|6m<40fag13tc@pfo{tc zy^m<=1`w7qZ{snpXK#zsv@}Fuj(gEFPUkZ-Op{`vjem!PmPW(%U;->x7~2<=ROaq* z6li=@mI-t*5}vUhDw3eUpe2dk-P+)1ZDon>d`i@5jpDi777kxwc7~e9WTDM(!Ymnu z(3d1QCM?X}FFE$R-cbU$;-iUmMuYwg0oyXyw|s)LHio@4bMw|5;ZMB!>7P^-<#o_v zYv!zJz^Ew;rvTkUNHmGB21a-Cf5mcu0H{WF^NXD%F9TJJ3xUSl10(ec*suU?;QML{wbsMHdgua~zU?Q(~$3y~(C&BY@U`Fw*Y7?))&X_=F&*H=J)q?+VdN07~HA+UOYS}`dc~a1V zl$+Vfw1x0Ss=Y7uJ+mnsc?}=42AtnJB+s&#ExVnYoBDmIdg z10q6V(6XI==JJD)c#}NeA%D$3c{19?Ee)<^B;2W*>!VAK#FyJM`@{tBphXAcvS6wD zuWeOCgJf==r!>l@&Y0~t=_?m-623OopF%$T>1;v-^8I+)a(Kp{MD-b+di$ka^5gua zy?X@G-5|$+FHko(tRtI6GKl!Eoi;;SF*8g3FN&lC{l{HL_f1$kR{IGMCweyeR?y0$>IJvM=F&y&Q%x4ElQ! z^i@17%?~Kg-CM(lY!cOXCLds07bu?I`2|4qWMK&nf>(sV*_Skof#4rOe=m~0dR6N> z5dNvXcJE9=guQtpv6|7Ulk8ZQ_F2^^8w!P)cQ`j-F99YkxLa^&D`t(=T>B30wN(%A z;o*+XyfY~=wyvx|+1B{%2YcdeUldyE@?(&EpPaXwS4n_rw|Vuznj%J{+DvNOeN+)d zi_M?M?py093HC&RZuvw{pWz-8!hg$x#ZTbtPHu9TO~_07c9kuY=q9fd1n?bYA52>` z>}~%U`7jml^@<=Ir-UI$0p8sK5|SQYKfU5%aVCBhX>pS&+$+d<-cWrbnB#Drv%-U8 zy3GI*<%jQeity2P?nQY-EjZ!IT+)I&e*iBtvR8# zG_Ss{>{5MUYRqDTUi+857!pLU42(`+N=ky$A4aCj5Af>7l$}YgdVJ$_kkP2WK$f`Z zel$L~l_xA|bw<5LQ@lqWWi)6{+KYo|I%(>%wDGUz+aRz$J+_^MM@1*=ic_P%)+w9h z9OfGBS=B)Q`s#v=R^Ac-&!kyKomyL-vwbNx7%tkmN^+qn={6Gis9uoYR09PPVCK{% zd*o^NE*hii$(%;6SZc);NVJQE3u$EB?m=XWeJ8krn>H~@$6>DW0@?tM={ z7yFwQV;VHSF4xwTUaT)pjY_z>M?S8t{%MFor{2LDjkkC&Z*I33bFGY~ZHsw2Ou#cV z-oM#WvODG*t+FWwv+O-vegNCZFL2@J#2&RQ^khD-K z?+*h8x(ymSprfy%rmR&}U&E)w^?=7(!QwGo`06w2lYy?DUar>0^yEfPlaL$GnXguR za~)tRQ>_rtwOme)qy(?raYhy_i61BiJkAP6$MY0nDvuTRE?l1y@TL{048Gw7 ziT^Sqnt(`vNPtLyNPtLyNPtLyNPtLyNI-uq{|PVvxUd@R(nO|b00000NkvXXu0mjf DLcxh& literal 0 HcmV?d00001 diff --git a/desktop/icons/hicolor/72x72/apps/vkpc.png b/desktop/icons/hicolor/72x72/apps/vkpc.png new file mode 100644 index 0000000000000000000000000000000000000000..3f266017358596da929a66577000f32914c904fd GIT binary patch literal 2184 zcmV;32zU31P)~+sQ|2_9R|NozJ{=>*&j5VxyFFZrQ z05L!e5Cg;jF+dCu1H=jg!~iis3=jjv05L!e5GxE2bfaO1<$4J{Z|9nmp=8z zwWsv6wg>dFFBuc}bu)VkjqbW7exQ@P2hd$w>IE`g#~ zI(YQmU{ZGrg7$&D^r^$1eRnVNcC$-&t{%d|Tpt5Y+Bbpp0ciwR+Pr^R{9@3H1F;}d z7XuoiIvxq`VP30S51r+`j1p(5zr5aVwWNb}Sm;NXdnoR;jXteteDb#YAZX5qYu?9w zq%P9^9R&_OOXI#jAQyAAnD;AAKp|iqF>wn%xzl4kl*6AU|N8aXnX8-YjI(HUDtb9? zz<#h2Pj%#XLC{T6qmKxZhmEMT&5gxJ}A8JbTD16ZQZ2QT;vYE zwxXP+UxUtC6>%a=<2`=xoS=PHZQOA>Z2L8oF~}#NtCeSMqOExw?SrASFZYYSSMXFf zF*TQb$)eRFA|#d5>V(m5@dhX^F>5H8*bsGqu)X@+$z4Qsz<|gLRh1pd0#j_Fc{;e+ z;76yVjPX`Kaft#6VHsVbeIp2{lBenNz_NHgC?sr_+G$I2IC2aHpVcdY8wWnkqw?=} zn?$>H3)awmc%3fkKt??8t)6rfVPvD#2PS8C@wxdkYUYZ55P+u9>T!LLrM9AEHK>y& zEZKvx1_um?Xpm~oozlZdw$Q{t@Ut;vleP}`TCc2FHFnyViV$|V+uV>ZGp2E&QMV;y z%!R9o&-P|>M(F|lCE^*a`xEFw5g7`yY9)s~n|l8e9FkFzpDB2Sj3UFxF<$E!S!{I+RdsOK#CRpb>_SE2%$Rc4kR53@R&O`dB^ICWBYjW@4=&{2ZM_| zM%z9lqF)SA^5mlJlL7cV+I9%Z)y7|nUol_#%Su>{dutc=+&|xi^K_Gsgc+#_ik-i4 zPH=eNOjT(SjE-Yj-U@I=bG$H3Mau>xNqUGyZ^A!9DH};mwSSI3z9X#|u0??3H5>5F zsuz7G%+vxRMF{HEJ2YmV{s>I9!+K(t(CM)13pdPq8!qi*Q@q0=<=fT10r`_KL!!;% zFkiixgvo-dixXP@$d&V_))C8z12};u%=`hQ#G(^B-!F06Tei{0gzl8ksDZp|wM-Ku zyC&tTq%ZzBo|lG^e4(egSf5)5>4!d-67)~!q~)s8ZyU{5t7gMMI92AXx>R647#9^i zWq_B*>bhYYBUtifCXe_PjWT)#(6kwZV%a-Op)E~Vy@J~ zq8!E5&KK>fF;$jLK^WC^rX9eVW;?{X`(eYm5VqkBe!APE;BJR(W>aK$yO{En*d!OE zvi!BbU90?g0!HX!XV4*fN^?IV3m$=SOcTk-@k9xxe>D=LMRc8zIVB8xTqgnG(8*3__mL=yYe;CQK zbg6ZkBE}7=bk7IJnoTt`$PtcTjI_F?N7ld0cS{WN)QOQUNXkLsnZ4=7#rjvDY&IF= zWl1blvwxFBTdk(OZUV`1D(9U7OTF<02;3$=765~2kDden^A|63<;cWrh9%uJ{ktKO&SB+B+bx*#9au8c_R_^_imGl6sGl7v>Gbz3m+ z#V|zz0rX2g^j_CP_FcN$9vt1fzT%`!w3=mKxgxi22k1ti!)Llf+bKc1BZ#kO-#AGw z-jE=@Y(f8}(pd?!|5agt z{F%kre>T7@hyh}N7$63S0b+m{AXXS428aP-fEXYK=-*rZ7GMBQnKzomC1$Ju0000< KMNUMnLSTY#;UjYZ literal 0 HcmV?d00001 diff --git a/desktop/icons/hicolor/96x96/apps/vkpc.png b/desktop/icons/hicolor/96x96/apps/vkpc.png new file mode 100644 index 0000000000000000000000000000000000000000..b05a855eb1f3b02c1a99c52613790d8c28e1d5a3 GIT binary patch literal 2825 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 Vv0CH2jlKW?002ovPDHLkV1nV{)>Z%j literal 0 HcmV?d00001 diff --git a/desktop/icons/ubuntu-mono-dark/22/vkpc.png b/desktop/icons/ubuntu-mono-dark/22/vkpc.png new file mode 100644 index 0000000000000000000000000000000000000000..6781df207a217fa8eddab04a7f8751d43e4fd0fe GIT binary patch literal 559 zcmV+~0?_@5P)RCwC#RXt0?U=Yrmq$;9=AR-PT zf`W>!&iW5@P;nL1!AXlwE`CgkI6F8ii2pzb!9gdrpkiAseni@)U+G7_n#3oxDKAZ} zND&b{c(}{md(U0&y{5#nEaoSKWqwHh9x)#Z_-Z2X-GzQ{6n1H=h|{8v0Q*1`P=PdX zLgkDP_H|hl9$mz-gw#SMi~v3MYd!?u8ez8rbwCp^>f~vfzC?E^rE_gA;zTlYK<{)u zZ>-u~sG7cLm}aH9c6dDeu;a6H(cHOs()*>x;xwNp^U0~Y#8SHiy#Ke zFO0q5tN!wEz?YNNQU9ax z`W{Kx8^f5zRYb81bUpMchzGGmnxR(|v4zqlKlyK}eFvJM=71@nA7}vPQFsqA5A0oF x2B=Zg{)$c6v_002ovPDHLkV1nK;^63Bo literal 0 HcmV?d00001 diff --git a/desktop/icons/ubuntu-mono-dark/24/vkpc.png b/desktop/icons/ubuntu-mono-dark/24/vkpc.png new file mode 100644 index 0000000000000000000000000000000000000000..e57de234f2d17d78e5fc832c7fd6c5c33a2567b8 GIT binary patch literal 597 zcmV-b0;>IqP)i^GQzYG8V`v({Q_wOGw)b&!VtSsg*agbi1Bah;=;QNnX ztsg#pg`338%oGJQL=DJ%1`H>-&HtfnphXiI85x;j;;-L+{P*SSk8*s;^wFEQpVk0_ z0L%et2C~-yEzx6QVq%0_4CDaCqJdNhOziX5?+mZrd~5+SFW}7wKYsoydGqemZ@2;` zMr|N<5k(UdBgnEP$YRf5zB>dAq9$T8+T%CxKKA_l`J1HVs3a$G14#WPDkuN_`>*-& zvp089x|{riuffe#+N{PO$v9}G!QLb!AP*)<@IE>!}w zq>R|I=<>JkKb>yhd-mk{tM{N3#_$iCgn^cx1o}en$+I^r-oF3LfSj~}aydA2G8S+e z7))Ak-hNC6;s_vL3y3*@cmXIRfd*N8q03g5sd0z35;7K%500000NkvXXu0mjfC5RC5 literal 0 HcmV?d00001 diff --git a/desktop/icons/ubuntu-mono-light/16/vkpc.png b/desktop/icons/ubuntu-mono-light/16/vkpc.png new file mode 100644 index 0000000000000000000000000000000000000000..dfb72956c74f0472211ba7c9f91d7bac06abeda0 GIT binary patch literal 359 zcmV-t0hs=YP)`2p1z8unvfS0Wl*GCj#+VAbtRqV*_GY zAQk}PFsK;F&<)52yo4G80wF*w4a6VOT$=*KJW%!|AifJ?Fw4ow!4%vE;tWv00al;z%I=gC2;NfOrj*odCqjNODF%EDvSR2jcIzVxbgj8$M71#45N9 zcnG!qFPeA}5MPF}MS*xD5cA?n2Wx@Y6^Ktk`Jkv>1jJrId=Dz71;lX-&;Y{*_5-mw z%oZT{45c-Im=9{`CwvAlplO7G-$48pS%7iCb0a{20RU+tHlX84Xv_cr002ovPDHLk FV1j&>f`b46 literal 0 HcmV?d00001 diff --git a/desktop/icons/ubuntu-mono-light/22/vkpc.png b/desktop/icons/ubuntu-mono-light/22/vkpc.png new file mode 100644 index 0000000000000000000000000000000000000000..adc4436e452257b4c17677c8d949f72d98aa868d GIT binary patch literal 396 zcmV;70dxL|P)Dv5Pr@n3)_GKLBERD0@E;JAveZcrg%1AQ^fdh>d|5l$?TqcrB87_kli8 zLU-|MB(@_Et3ovwLM?y+Lm=jZvcu6FdKz0$Oa|iLNc>zV{ThfrqZ#lWh^2to4@v9~ z5VvD9_z@59?(JO<)kBt9sTE@FxFWoW)g2fFqzq2vY%r)wl8=Nw|v@powQ`iR32Q21U& z6DzT`we2P*gIoq;S7^|ngXKW14-F1V@6+i_X|)bt1%959dy@&%O5j3tPhfS3b`-wHJ208h({*c&Y3JS+-BwCO|bUpyO zzCxeTO0Y5g%4>E%GI55&u`SW-rGP6k>6idbzR zE(PMVKn(I3DAJBWBQpVrrvdR#ByGV2BYCv28?|6?SpX1V0HbkbF%zrZSpWb407*qo IM6N<$f=Y9sCjbBd literal 0 HcmV?d00001 diff --git a/desktop/info.h b/desktop/info.h new file mode 100644 index 0000000..f13bbdc --- /dev/null +++ b/desktop/info.h @@ -0,0 +1,13 @@ +#ifndef INFO_H__ +#define INFO_H__ + +#define APP_NAME "VK Player Controller" +#define APP_ABOUT "Use media buttons to switch between tracks." +#define APP_VERSION "0.1" +#define APP_AUTHOR "Eugene Z. " +#define APP_URL "http://ch1p.com/vkpc/" + +#define SERVER_PORT 52178 +#define SERVER_HOST "localhost" + +#endif diff --git a/desktop/install_icons.sh b/desktop/install_icons.sh new file mode 100755 index 0000000..cbdb4e1 --- /dev/null +++ b/desktop/install_icons.sh @@ -0,0 +1,16 @@ +#!/bin/bash + +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +ICONS_PATH=/usr/share/icons +ICONS=( "Faenza" "Faenza-Radiance" "hicolor" "ubuntu-mono-dark" "ubuntu-mono-light" ) +if [[ $EUID -ne 0 ]]; then + echo "This script must be run as root." 1>&2 + exit 1 +fi + +for i in "${ICONS[@]}" +do + if [ -d "${ICONS_PATH}/$i" ]; then + cp -r ${DIR}/icons/$1/* ${ICONS_PATH}/$1 + fi +done diff --git a/desktop/main.c b/desktop/main.c new file mode 100644 index 0000000..8703991 --- /dev/null +++ b/desktop/main.c @@ -0,0 +1,140 @@ +#include +#include +#include +#include +#include + +#include "info.h" +#include "server.h" +#include "grab.h" + +static GtkStatusIcon *tray_icon; +static GtkWidget *menu; + +enum server_last_cmd_enum server_last_cmd = NONE; +static pthread_t grab_thread; +static pthread_t server_thread; + +pthread_mutex_t server_last_cmd_mutex; + +void tray_icon_on_click(GtkStatusIcon *status_icon, gpointer user_data) { + // left-click +} + +void tray_icon_on_menu(GtkStatusIcon *status_icon, guint button, guint activate_time, gpointer user_data) { + // right-click + gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL, button, activate_time); +} + +void menu_about(GtkWidget *widget, gpointer data) { + GtkWidget *about_dialog; + + const gchar *authors[] = { + APP_AUTHOR, + NULL + }; + + about_dialog = gtk_about_dialog_new(); + gtk_about_dialog_set_version((GtkAboutDialog *)about_dialog, APP_VERSION); + gtk_about_dialog_set_authors((GtkAboutDialog *)about_dialog, authors); + gtk_about_dialog_set_comments((GtkAboutDialog *)about_dialog, (const gchar *)APP_ABOUT); + gtk_about_dialog_set_name((GtkAboutDialog *)about_dialog, APP_NAME); + gtk_about_dialog_set_website((GtkAboutDialog *)about_dialog, APP_URL); + + g_signal_connect_swapped(about_dialog, "response", G_CALLBACK(gtk_widget_hide), about_dialog); + + gtk_widget_show(about_dialog); +} + +void menu_quit(GtkWidget *widget, gpointer data) { + // quit app + exit(0); +} + +void create_tray_icon() { + tray_icon = gtk_status_icon_new(); + + g_signal_connect(G_OBJECT(tray_icon), "activate", + G_CALLBACK(tray_icon_on_click), NULL); + g_signal_connect(G_OBJECT(tray_icon), "popup-menu", + G_CALLBACK(tray_icon_on_menu), NULL); + + gtk_status_icon_set_from_icon_name(tray_icon, "vkpc"); + gtk_status_icon_set_tooltip(tray_icon, APP_NAME); + gtk_status_icon_set_visible(tray_icon, true); +} + +void create_menu() { + GtkWidget *item; + menu = gtk_menu_new(); + + // About + item = gtk_image_menu_item_new_from_stock(GTK_STOCK_DIALOG_INFO, NULL); + gtk_menu_item_set_label((GtkMenuItem *)item, "About"); + g_signal_connect(G_OBJECT(item), "activate", G_CALLBACK(menu_about), NULL); + gtk_menu_shell_append(GTK_MENU_SHELL(menu), item); + gtk_widget_show(item); + + // Quit + item = gtk_image_menu_item_new_from_stock(GTK_STOCK_QUIT, NULL); + gtk_menu_item_set_label((GtkMenuItem *)item, "Quit"); + g_signal_connect(G_OBJECT(item), "activate", G_CALLBACK(menu_quit), NULL); + gtk_menu_shell_append(GTK_MENU_SHELL(menu), item); + gtk_widget_show(item); +} + +void handle_hotkeys(enum HotkeyEvent e) { + pthread_mutex_lock(&server_last_cmd_mutex); + switch (e) { + case HK_PLAY: + server_last_cmd = PLAY; + break; + + case HK_PAUSE: + server_last_cmd = PAUSE; + break; + + case HK_NEXT: + server_last_cmd = NEXT; + break; + + case HK_PREV: + server_last_cmd = PREV; + break; + + default: + break; + } + pthread_mutex_unlock(&server_last_cmd_mutex); +} + +void start_grab() { + int rc = pthread_create(&grab_thread, NULL, (void *)grab_init, handle_hotkeys); + if (rc) { + fprintf(stderr, "ERROR creating grab_thread, code = %d\n", rc); + exit(-1); + } +} + +void start_server() { + int rc = pthread_create(&server_thread, NULL, (void *)server_init, NULL); + if (rc) { + fprintf(stderr, "ERROR creating server_thread, code = %d\n", rc); + exit(-1); + } +} + +int main(int argc, char **argv) { + pthread_mutex_init(&server_last_cmd_mutex, NULL); + + start_grab(); + start_server(); + + gtk_init(&argc, &argv); + + create_tray_icon(); + create_menu(); + + gtk_main(); + return 0; +} diff --git a/desktop/server.c b/desktop/server.c new file mode 100644 index 0000000..a3c308f --- /dev/null +++ b/desktop/server.c @@ -0,0 +1,182 @@ +/** + * TODO: logging level + */ + +#include +#include +#include +#include +#include + +#include "server.h" +#include "vector.h" + +#define SERVER_PORT 52178 +#define SERVER_HOST "localhost" + +static struct libwebsocket_context *context; +static char *server_last_cmd_values[] = { + "none", "play", "pause", "next", "prev" +}; + +struct per_session_data { + bool established; + char *next_command; +}; +struct session { + struct per_session_data *pss; + struct libwebsocket *wsi; +}; +vector *sessions; + +static void add_session(struct libwebsocket *wsi, struct per_session_data *pss) { + struct session *s = malloc(sizeof(struct session)); + s->wsi = wsi; + s->pss = pss; + vector_add(sessions, s); +} + +static void delete_session(struct libwebsocket *wsi) { + for (int i = 0; i < vector_count(sessions); i++) { + struct session *s = vector_get(sessions, i); + if (s != NULL && s->wsi == wsi) { + printf("(delete_session) found, i=%d\n", i); + free(s); + vector_delete(sessions, i); + break; + } + } +} + +static void send_command_to_all(char *command) { + printf("Got command: %s\n", command); + for (int i = 0; i < vector_count(sessions); i++) { + struct session *s = (struct session *)vector_get(sessions, i); + s->pss->next_command = command; + libwebsocket_callback_on_writable(context, s->wsi); + } +} + +static int callback_http(struct libwebsocket_context *this, + struct libwebsocket *wsi, + enum libwebsocket_callback_reasons reason, + void *user, + void *in, + size_t len) +{ + switch (reason) { + case LWS_CALLBACK_HTTP: ; + libwebsocket_callback_on_writable(context, wsi); + break; + + case LWS_CALLBACK_HTTP_WRITEABLE: ; + char *response = "vkpc, world!"; + libwebsocket_write(wsi, (unsigned char *)response, strlen(response), LWS_WRITE_HTTP); + return -1; + + default: + break; + } + return 0; +} + +static int callback_signaling(struct libwebsocket_context *this, + struct libwebsocket *wsi, + enum libwebsocket_callback_reasons reason, + void *user, + void *in, + size_t len) +{ + struct per_session_data *pss = (struct per_session_data *)user; + + switch (reason) { + case LWS_CALLBACK_ESTABLISHED: + lwsl_info("Connection established"); + + pss->established = true; + pss->next_command = NULL; + add_session(wsi, pss); + + libwebsocket_callback_on_writable(context, wsi); + break; + + case LWS_CALLBACK_SERVER_WRITEABLE: + if (pss->next_command != NULL) { + int length = strlen(pss->next_command); + unsigned char buf[LWS_SEND_BUFFER_PRE_PADDING + length + LWS_SEND_BUFFER_POST_PADDING]; + unsigned char *p = &buf[LWS_SEND_BUFFER_PRE_PADDING]; + + strcpy((char *)p, pss->next_command); + int m = libwebsocket_write(wsi, p, length, LWS_WRITE_TEXT); + + if (m < length) { + lwsl_err("ERROR while writing %d bytes to socket\n", length); + return -1; + } + + pss->next_command = NULL; + } + break; + + case LWS_CALLBACK_RECEIVE: + lwsl_info("Received: %s, length: %d\n", + (char *)in, (int)strlen((char *)in)); + break; + + case LWS_CALLBACK_CLOSED: + lwsl_info("Connection closed\n"); + delete_session(wsi); + break; + + default: + break; + } + + return 0; +} + +static struct libwebsocket_protocols protocols[] = { + { "http-only", callback_http, 0, 0 }, + { "signaling-protocol", callback_signaling, sizeof(struct per_session_data), 0 }, + { NULL, NULL, 0 } +}; + +void server_init() { + sessions = vector_create(); + + struct lws_context_creation_info info; + memset(&info, 0, sizeof(info)); + + info.port = SERVER_PORT; + info.iface = SERVER_HOST; + info.protocols = protocols; + info.extensions = libwebsocket_get_internal_extensions(); + info.ssl_cert_filepath = NULL; + info.ssl_private_key_filepath = NULL; + info.gid = -1; + info.uid = -1; + info.options = 0; + + context = libwebsocket_create_context(&info); + if (context == NULL) { + fprintf(stderr, "libwebsocket init failed\n"); + return; + } + + enum server_last_cmd_enum last_cmd = NONE; + while (1) { + pthread_mutex_lock(&server_last_cmd_mutex); + last_cmd = server_last_cmd; + server_last_cmd = NONE; + pthread_mutex_unlock(&server_last_cmd_mutex); + + if (last_cmd != NONE) { + send_command_to_all(server_last_cmd_values[last_cmd]); + } + + libwebsocket_service(context, 50); + } + + libwebsocket_context_destroy(context); + return; +} diff --git a/desktop/server.h b/desktop/server.h new file mode 100644 index 0000000..1b7af3f --- /dev/null +++ b/desktop/server.h @@ -0,0 +1,14 @@ +#include + +#ifndef SERVER_H__ +#define SERVER_H__ + +enum server_last_cmd_enum { + NONE = 0, PLAY, PAUSE, NEXT, PREV +}; +extern enum server_last_cmd_enum server_last_cmd; +extern pthread_mutex_t server_last_cmd_mutex; + +void server_init(); + +#endif diff --git a/desktop/vector.c b/desktop/vector.c new file mode 100644 index 0000000..16b8804 --- /dev/null +++ b/desktop/vector.c @@ -0,0 +1,74 @@ +/** + * Based on https://gist.github.com/EmilHernvall/953968 + */ + +#include +#include +#include +#include "vector.h" + +vector * vector_create() { + vector *v; + v = malloc(sizeof(vector)); + + v->data = NULL; + v->size = 0; + v->count = 0; + + return v; +} + +int vector_count(vector *v) { + return v->count; +} + +void vector_add(vector *v, void *e) { + if (v->size == 0) { + v->size = 10; + v->data = malloc(sizeof(void *) * v->size); + memset(v->data, '\0', sizeof(void *) * v->size); + } + + if (v->size == v->count) { + v->size += 10; + v->data = realloc(v->data, sizeof(void *) * v->size); + } + + v->data[v->count++] = e; +} + +void vector_set(vector *v, int index, void *e) { + if (index >= v->count) { + return; + } + + v->data[index] = e; +} + +void * vector_get(vector *v, int index) { + if (index >= v->count) { + return NULL; + } + + return v->data[index]; +} + +void vector_delete(vector *v, int index) { + if (index >= v->count) { + return; + } + + for (int i = index+1; i < v->count; i++) { + v->data[i-1] = v->data[i]; + } + + v->data[--v->count] = NULL; +} + +void vector_free_data(vector *v) { + free(v->data); +} + +void vector_free(vector *v) { + free(v); +} diff --git a/desktop/vector.h b/desktop/vector.h new file mode 100644 index 0000000..c9e9c98 --- /dev/null +++ b/desktop/vector.h @@ -0,0 +1,23 @@ +/** + * Based on https://gist.github.com/EmilHernvall/953968 + */ + +#ifndef VECTOR_H__ +#define VECTOR_H__ + +typedef struct vector_ { + void **data; + int size; + int count; +} vector; + +vector * vector_create(); +int vector_count(vector *); +void vector_add(vector *, void *); +void vector_set(vector *, int, void *); +void *vector_get(vector *, int); +void vector_delete(vector*, int); +void vector_free_data(vector *); +void vector_free(vector *); + +#endif diff --git a/extensions/chrome/_locales/en/messages.json b/extensions/chrome/_locales/en/messages.json new file mode 100644 index 0000000..eebbc14 --- /dev/null +++ b/extensions/chrome/_locales/en/messages.json @@ -0,0 +1,8 @@ +{ + "title": { + "message": "VK Player Controller Client" + }, + "description_short": { + "message": "Chrome client for VK Player Controller app." + } +} diff --git a/extensions/chrome/common.js b/extensions/chrome/common.js new file mode 100644 index 0000000..30dc55a --- /dev/null +++ b/extensions/chrome/common.js @@ -0,0 +1,187 @@ +function init() { + // receive messages from webpage + chrome.runtime.onMessageExternal.addListener(receiveMessage); +} +function receiveMessage(msg, sender, sendResponse) { + if (msg.cmd == "injection_result") { + var obj = Injections.get(msg.id); + if (obj) obj.addResponse(sender.tab.id, msg.data); + } +} +function extend(dest, source) { + for (var i in source) { + dest[i] = source[i]; + } +} +function getWebSocket() { + return window.WebSocket || window.MozWebSocket; +} +function print() { + var msgs = [], i, tmp; + for (i = 0; i < arguments.length; i++) { + if (arguments[i] instanceof Error) tmp = [arguments[i], arguments[i].stack]; + else tmp = arguments[i]; + msgs.push(tmp); + } + + try { + console.log.apply(console, msgs); + } catch(e) {} +} +function getExtensionId() { + return chrome.i18n.getMessage("@@extension_id"); +} +function getVKTabs(callback) { + var vkTabs = []; + chrome.tabs.query({}, function(tabs) { + for (var i = 0; i < tabs.length; i++) { + var tab = tabs[i]; + if (tab.url.match(new RegExp('https?://vk.com/.*', 'gi'))) { + vkTabs.push(tab); + } + } + callback(vkTabs); + }); +} +function executeCommand(cmd) { + var injId = Injections.getNextId(); + var code_inj = "var el = document.createElement('script'); el.src = chrome.extension.getURL('inject_and_return.js'); document.body.appendChild(el); var el1 = document.createElement('script'); el1.textContent = 'window.__vkpc_extid=\""+getExtensionId()+"\"; window.__vkpc_injid="+injId+"'; document.body.appendChild(el1)"; + var code_exec = "var el = document.createElement('script'); el.src = chrome.extension.getURL('inject_exec.js'); document.body.appendChild(el); var el1 = document.createElement('script'); el1.textContent = 'window.__vkpc_cmd=\""+cmd+"\"'; document.body.appendChild(el1)"; + + getVKTabs(function(tabs) { + if (!tabs.length) return; + + var injResponses, activeTabId = null; + var onDone = function() { + var ok = {nowPlaying: null, lsSource: null, recentlyPlayed: null, active: activeTabId, last: null}; + var results = injResponses.results, lsSource = injResponses.lsSource; + + for (var i = 0; i < results.length; i++) { + var data = results[i].data, id = results[i].tab; + ok.last = id; + + if (data.havePlayer && (data.isPlaying || typeof data.trackId == 'string')) { + ok.recentlyPlayed = id; + } + if (data.isPlaying) { + ok.nowPlaying = id; + } + if (lsSource && lsSource == data.instanceId) { + ok.lsSource = id; + } + } + injResponses.unregister(); + + var rightId = ok.nowPlaying || ok.lsSource || ok.recentlyPlayed || ok.active || ok.last; + if (rightId) { + chrome.tabs.executeScript(rightId, {code: code_exec}); + } + }; + injResponses = new InjectionResponses(injId, tabs.length, onDone); + + for (var i = 0; i < tabs.length; i++) { + if (tabs[i].active) activeTabId = tabs[i].id; + chrome.tabs.executeScript(tabs[i].id, { + code: code_inj + }); + } + }); +} + +var Injections = { + id: 0, + objs: {}, + getNextId: function() { + return ++this.id; + }, + get: function(id) { + return this.objs[id] || false; + }, + register: function(id, obj) { + this.objs[id] = obj; + }, + unregister: function(id) { + if (this.objs[id] !== undefined) delete this.objs[id]; + } +}; + +var WSClient = new function() { + var STATUS_NONE = 0, STATUS_OK = 1, STATUS_ERR = 2; + var _ws = getWebSocket(), ws; + var _status = STATUS_NONE; + var ping_timer, reconnect_timer; + + if (!_ws) return; + + function setTimers() { + ping_timer = setInterval(function() { + if (ws) ws.send("PING"); + }, 30000); + } + function unsetTimers() { + clearInterval(ping_timer); + } + + function connect() { + _status = STATUS_NONE; + + print("[connect]"); + ws = new _ws("ws://localhost:52178", "signaling-protocol"); + ws.onopen = function() { + _status = STATUS_OK; + setTimers(); + }; + ws.onerror = function() { + unsetTimers(); + if (_status != STATUS_ERR) { + _status = STATUS_ERR; + tryToReconnect(); + } + } + ws.onclose = function() { + unsetTimers(); + if (_status != STATUS_ERR) { + _status = STATUS_ERR; + tryToReconnect(); + } + }; + ws.onmessage = function(e) { + onCommand(e.data); + }; + } + function tryToReconnect() { + print("[tryToReconnect]"); + + clearTimeout(reconnect_timer); + reconnect_timer = setTimeout(connect, 5000); + } + function onCommand(msg) { + executeCommand(msg); + } + + connect(); +}; + +function InjectionResponses(id, count, callback) { + this.id = id; + this.results = []; + this.lsSource = null; + this.maxCount = count; + this.callback = callback || function() {}; + + Injections.register(this.id, this); +} +extend(InjectionResponses.prototype, { + addResponse: function(id, response) { + this.results.push({tab: id, data: response}); + if (!this.lsSource && response && response.lastInstanceId) this.lsSource = response.lastInstanceId; + if (this.results.length == this.maxCount) { + this.callback(); + } + }, + unregister: function() { + Injections.unregister(this.id); + } +}); + +init(); diff --git a/extensions/chrome/inject_and_return.js b/extensions/chrome/inject_and_return.js new file mode 100644 index 0000000..ebb2827 --- /dev/null +++ b/extensions/chrome/inject_and_return.js @@ -0,0 +1,28 @@ +(function() { + function getLastInstanceId() { + var id = null, pp = ls.get('pad_playlist'); + if (pp && pp.source) id = pp.source; + return id; + } + + var data = {}; + try { + var havePlayer = window.audioPlayer !== undefined; + var havePlaylist = havePlayer && !!padAudioPlaylist(); + + data = { + havePlayer: havePlayer, + havePlaylist: havePlaylist, + isPlaying: havePlayer && window.audioPlayer.player && !window.audioPlayer.player.paused(), + instanceId: window.curNotifier && curNotifier.instance_id, + trackId: havePlayer && audioPlayer.id, + lastInstanceId: getLastInstanceId() + }; + } catch(e) {} + + chrome.runtime.sendMessage(window.__vkpc_extid, { + cmd: "injection_result", + id: parseInt(window.__vkpc_injid, 10), + data: data + }); +})(); diff --git a/extensions/chrome/inject_exec.js b/extensions/chrome/inject_exec.js new file mode 100644 index 0000000..c1df18f --- /dev/null +++ b/extensions/chrome/inject_exec.js @@ -0,0 +1,74 @@ +(function() { + function vkAudio__getPlayFirstId() { + var id = currentAudioId() || ls.get('audio_id') || (window.audioPlaylist && audioPlaylist.start); + return id || null; + } + function vkAudio__executeAfterPadLoading(f) { + Pads.show('mus'); + window.onPlaylistLoaded = function() { + if (f) { + try { + f(); + } catch(e) {} + } + setTimeout(function() { + Pads.show('mus'); + }, 10); + } + } + function vkAudio__next() { + console.log("Next"); + window.audioPlayer && audioPlayer.nextTrack(true, !window.audioPlaylist); + } + function vkAudio__prev() { + console.log("Prev"); + window.audioPlayer && audioPlayer.prevTrack(true, !window.audioPlaylist); + } + function vkAudio__playPause() { + console.log("PlayPause"); + if (!window.audioPlayer || !padAudioPlaylist()) { + stManager.add(['audioplayer.js'], function() { + vkAudio__executeAfterPadLoading(function() { + var plist = padAudioPlaylist(), id = vkAudio__getPlayFirstId(); + if (id) { + playAudioNew(id); + } else if (plist && plist.start) { + playAudioNew(plist.start); + } + }); + }); + } else { + if (window.audioPlayer && audioPlayer.player) { + if (audioPlayer.player.paused()) { + audioPlayer.playTrack(); + } else { + audioPlayer.pauseTrack(); + } + } + } + } + + try { + var data = window.__vkpc_cmd; + if (data) { + switch (data) { + case "next": + vkAudio__next(); + break; + + case "prev": + vkAudio__prev(); + break; + + case "play": + case "pause": + vkAudio__playPause(); + break; + } + + delete window.__vkpc_cmd; + } + } catch (e) { + console.log('[VKPC]', e, e.stack); + } +})(); diff --git a/extensions/chrome/manifest.json b/extensions/chrome/manifest.json new file mode 100644 index 0000000..f7b29d7 --- /dev/null +++ b/extensions/chrome/manifest.json @@ -0,0 +1,23 @@ +{ + "manifest_version": 2, + "name": "__MSG_title__", + "description": "__MSG_description_short__", + "version": "0.1", + "default_locale": "en", + "permissions": [ + "background", + "tabs", + "https://vk.com/*", + "http://vk.com/*", + "https://*.vk.com/*", + "http://*.vk.com/*" + ], + "background": { + "scripts": ["common.js"] + }, + "externally_connectable": { + "matches": ["https://vk.com/*", "http://vk.com/*", "https://*.vk.com/*", "http://*.vk.com/*"] + }, + "content_security_policy": "script-src 'self' 'unsafe-eval' https://vk.com; object-src 'self' 'unsafe-eval'", + "web_accessible_resources": ["inject_and_return.js", "inject_exec.js"] +} diff --git a/extensions/firefox/chrome.manifest b/extensions/firefox/chrome.manifest new file mode 100644 index 0000000..1d59370 --- /dev/null +++ b/extensions/firefox/chrome.manifest @@ -0,0 +1,2 @@ +content vkpc chrome/ +overlay chrome://browser/content/browser.xul chrome://vkpc/content/overlay.xul diff --git a/extensions/firefox/chrome/background.js b/extensions/firefox/chrome/background.js new file mode 100644 index 0000000..2795a8c --- /dev/null +++ b/extensions/firefox/chrome/background.js @@ -0,0 +1,258 @@ +var VKPC = new function() { + +function init() { + window.addEventListener("load", function load(event) { + window.removeEventListener("load", load, false); + injectOnLoad(); + }, false); + + WSClient.go(); +} +function extend(dest, source) { + for (var i in source) { + dest[i] = source[i]; + } +} +function remove(element) { + element.parentNode.removeChild(element); +} +function createCData(data) { + var docu = new DOMParser().parseFromString('', "application/xml"); + var cdata = docu.createCDATASection(data); + docu.getElementsByTagName('xml')[0].appendChild(cdata); + return cdata; +} +function getWebSocket() { + return window.WebSocket || window.MozWebSocket; +} +function print() { + var msgs = [], i, tmp; + for (i = 0; i < arguments.length; i++) { + if (arguments[i] instanceof Error) tmp = [arguments[i], arguments[i].stack]; + else tmp = arguments[i]; + msgs.push(tmp); + } + + try { + console.log.apply(console, msgs); + } catch(e) {} +} +function injectOnLoad() { + function onPageLoaded(e) { + var doc = e.originalTarget, loc = doc.location; + if (!loc.href.match(/^https?:\/\/vk.com\/.*$/)) return; + + doc.addEventListener("VKPCInjectedMessage", function(e) { + var target = e.target, json = JSON.parse(target.data || "{}"), doc = target.ownerDocument; + receiveMessage(json, doc, target); + }, false); + + var loader = Components.classes["@mozilla.org/moz/jssubscript-loader;1"].getService(Components.interfaces.mozIJSSubScriptLoader); + loader.loadSubScript("chrome://vkpc/content/inject_on_load.js", doc); + } + + var appcontent = document.getElementById("appcontent"); + if (appcontent) { + appcontent.addEventListener("DOMContentLoaded", onPageLoaded, true); + } +} +function receiveMessage(json, doc, target) { + switch (json.cmd) { + case "register": + Documents.add(doc); + break; + + case "params": + var id = json.id; + var obj = Injections.get(id); + if (obj) { + obj.addResponse(doc, json.data); + } + break; + } + + try { + remove(target); + } catch (e) {} +} +function executeCommand(cmd) { + var injId = Injections.getNextId(); + + var tabsCount = Documents.getCount(); + if (!tabsCount) return; + + var injResponses; + var onDone = function() { + var ok = {nowPlaying: null, lsSource: null, recentlyPlayed: null, active: null, last: null}; + var results = injResponses.results, lsSource = injResponses.lsSource; + + for (var i = 0; i < results.length; i++) { + var data = results[i].data, doc = results[i].tab; + ok.last = doc; + + if (data.havePlayer && (data.isPlaying || typeof data.trackId == 'string')) { + ok.recentlyPlayed = doc; + } + if (data.isPlaying) { + ok.nowPlaying = doc; + } + if (lsSource && lsSource == data.instanceId) { + ok.lsSource = doc; + } + if (data.isFocused) { + ok.active = doc; + } + } + injResponses.unregister(); + + var rightDoc = ok.nowPlaying || ok.lsSource || ok.recentlyPlayed || ok.active || ok.last; + if (rightDoc) { + Documents.sendToDoc(rightDoc, { + cmd: "audioCommand", + command: cmd + }); + } + }; + + injResponses = new InjectionResponses(injId, tabsCount, onDone); + + Documents.send({ + cmd: "getParams", + id: injId + }); +} + +var Injections = { + id: 0, + objs: {}, + getNextId: function() { + return ++this.id; + }, + get: function(id) { + return this.objs[id] || false; + }, + register: function(id, obj) { + this.objs[id] = obj; + }, + unregister: function(id) { + if (this.objs[id] !== undefined) delete this.objs[id]; + } +}; + +var Documents = { + list: [], + add: function(doc) { + this.cleanup(); + this.list.push(doc); + }, + cleanup: function() { + this.list = this.list.filter(function(t) { + return Object.prototype.toString.call(t) != '[object DeadObject]'; + }); + }, + send: function(json) { + var self = this; + this.cleanup(); + + this.list.forEach(function(doc) { + self.sendToDoc(doc, json); + }); + }, + sendToDoc: function(doc, json) { + var cdata = createCData(JSON.stringify(json)); + doc.getElementById('utils').appendChild(cdata); + + var evt = doc.createEvent("Events"); + evt.initEvent("VKPCBgMessage", true, false); + cdata.dispatchEvent(evt); + }, + getCount: function() { + this.cleanup(); + return this.list.length; + } +}; + +var WSClient = new function() { + var STATUS_NONE = 0, STATUS_OK = 1, STATUS_ERR = 2; + var _ws = getWebSocket(), ws; + var _status = STATUS_NONE; + var ping_timer, reconnect_timer; + + if (!_ws) return; + + function setTimers() { + ping_timer = setInterval(function() { + if (ws) ws.send("PING"); + }, 30000); + } + function unsetTimers() { + clearInterval(ping_timer); + } + + function connect() { + _status = STATUS_NONE; + + print("[connect]"); + ws = new _ws("ws://localhost:52178", "signaling-protocol"); + ws.onopen = function() { + _status = STATUS_OK; + setTimers(); + }; + ws.onerror = function() { + unsetTimers(); + if (_status != STATUS_ERR) { + _status = STATUS_ERR; + tryToReconnect(); + } + } + ws.onclose = function() { + unsetTimers(); + if (_status != STATUS_ERR) { + _status = STATUS_ERR; + tryToReconnect(); + } + }; + ws.onmessage = function(e) { + onCommand(e.data); + }; + } + function tryToReconnect() { + print("[tryToReconnect]"); + + clearTimeout(reconnect_timer); + reconnect_timer = setTimeout(connect, 5000); + } + function onCommand(msg) { + executeCommand(msg); + } + + this.go = function() { + connect(); + } +}; + +function InjectionResponses(id, count, callback) { + this.id = id; + this.results = []; + this.lsSource = null; + this.maxCount = count; + this.callback = callback || function() {}; + + Injections.register(this.id, this); +} +extend(InjectionResponses.prototype, { + addResponse: function(doc, response) { + this.results.push({tab: doc, data: response}); + if (!this.lsSource && response && response.lastInstanceId) this.lsSource = response.lastInstanceId; + if (this.results.length == this.maxCount) { + this.callback(); + } + }, + unregister: function() { + Injections.unregister(this.id); + } +}); + +init(); + +}; diff --git a/extensions/firefox/chrome/inject_on_load.js b/extensions/firefox/chrome/inject_on_load.js new file mode 100644 index 0000000..faa4dbf --- /dev/null +++ b/extensions/firefox/chrome/inject_on_load.js @@ -0,0 +1,149 @@ +(function() { + var isFocused = true; + + function vkAudio__getLastInstanceId() { + var id = null, pp = ls.get('pad_playlist'); + if (pp && pp.source) id = pp.source; + return id; + } + function vkAudio__getParams() { + var data = {}; + try { + var havePlayer = window.audioPlayer !== undefined; + var havePlaylist = havePlayer && !!padAudioPlaylist(); + + data = { + havePlayer: havePlayer, + havePlaylist: havePlaylist, + isPlaying: havePlayer && window.audioPlayer.player && !window.audioPlayer.player.paused(), + instanceId: window.curNotifier && curNotifier.instance_id, + trackId: havePlayer && audioPlayer.id, + lastInstanceId: vkAudio__getLastInstanceId() + }; + } catch(e) {} + + return data; + } + function vkAudio__getPlayFirstId() { + var id = currentAudioId() || ls.get('audio_id') || (window.audioPlaylist && audioPlaylist.start); + return id || null; + } + function vkAudio__executeAfterPadLoading(f) { + Pads.show('mus'); + window.onPlaylistLoaded = function() { + if (f) { + try { + f(); + } catch(e) {} + } + setTimeout(function() { + Pads.show('mus'); + }, 10); + } + } + function vkAudio__next() { + console.log("Next"); + window.audioPlayer && audioPlayer.nextTrack(true, !window.audioPlaylist); + } + function vkAudio__prev() { + console.log("Prev"); + window.audioPlayer && audioPlayer.prevTrack(true, !window.audioPlaylist); + } + function vkAudio__playPause() { + console.log("PlayPause"); + if (!window.audioPlayer || !padAudioPlaylist()) { + stManager.add(['audioplayer.js'], function() { + vkAudio__executeAfterPadLoading(function() { + var plist = padAudioPlaylist(), id = vkAudio__getPlayFirstId(); + if (id) { + playAudioNew(id); + } else if (plist && plist.start) { + playAudioNew(plist.start); + } + }); + }); + } else { + if (window.audioPlayer && audioPlayer.player) { + if (audioPlayer.player.paused()) { + audioPlayer.playTrack(); + } else { + audioPlayer.pauseTrack(); + } + } + } + } + + function createCData(data) { + var docu = new DOMParser().parseFromString('', "application/xml"); + var cdata = docu.createCDATASection(data); + docu.getElementsByTagName('xml')[0].appendChild(cdata); + return cdata; + } + function sendMessage(json) { + // Fucking crazy. + json.bg = 1; + + var cdata = createCData(JSON.stringify(json)); + document.getElementById('utils').appendChild(cdata); + + var evt = document.createEvent("Events"); + evt.initEvent("VKPCInjectedMessage", true, false); + cdata.dispatchEvent(evt); + } + function remove() { + remove.parentNode.removeChild(remove); + } + function receiveCommand(e) { + var target = e.target, json = JSON.parse(target.data || "{}"); + + switch (json.cmd) { + case "getParams": + var params = vkAudio__getParams(); + params.isFocused = isFocused; + sendMessage({ + data: params, + cmd: "params", + id: json.id + }); + break; + + case "audioCommand": + switch (json.command) { + case "play": + case "pause": + vkAudio__playPause(); + break; + + case "next": + vkAudio__next(); + break; + + case "prev": + vkAudio__prev(); + break; + } + break; + } + + try { + _VKPC.remove(target); + } catch (e) {} + } + + window.addEventListener("DOMContentLoaded", function(e) { + if (window.vk) { + document.addEventListener("VKPCBgMessage", receiveCommand, false); + + sendMessage({ + cmd: "register" + }); + } + }); + + window.addEventListener("focus", function(e) { + isFocused = true; + }, false); + window.addEventListener("blur", function(e) { + isFocused = false + }, false); +})(); diff --git a/extensions/firefox/chrome/overlay.xul b/extensions/firefox/chrome/overlay.xul new file mode 100644 index 0000000..7cf7229 --- /dev/null +++ b/extensions/firefox/chrome/overlay.xul @@ -0,0 +1,6 @@ + + + + +