summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore7
-rw-r--r--LICENSE2
-rw-r--r--README.md4
-rw-r--r--desktop/Makefile29
-rw-r--r--desktop/grab.c51
-rw-r--r--desktop/grab.h15
-rw-r--r--desktop/icons/Faenza-Radiance/apps/16/vkpc.pngbin0 -> 512 bytes
-rw-r--r--desktop/icons/Faenza-Radiance/apps/22/vkpc.pngbin0 -> 685 bytes
-rw-r--r--desktop/icons/Faenza-Radiance/apps/24/vkpc.pngbin0 -> 621 bytes
-rw-r--r--desktop/icons/Faenza-Radiance/apps/32/vkpc.pngbin0 -> 904 bytes
-rw-r--r--desktop/icons/Faenza-Radiance/apps/48/vkpc.pngbin0 -> 1343 bytes
-rw-r--r--desktop/icons/Faenza-Radiance/apps/64/vkpc.pngbin0 -> 1747 bytes
-rw-r--r--desktop/icons/Faenza-Radiance/apps/96/vkpc.pngbin0 -> 2717 bytes
-rw-r--r--desktop/icons/Faenza/apps/16/vkpc.pngbin0 -> 627 bytes
-rw-r--r--desktop/icons/Faenza/apps/22/vkpc.pngbin0 -> 824 bytes
-rw-r--r--desktop/icons/Faenza/apps/24/vkpc.pngbin0 -> 804 bytes
-rw-r--r--desktop/icons/Faenza/apps/32/vkpc.pngbin0 -> 1141 bytes
-rw-r--r--desktop/icons/Faenza/apps/48/vkpc.pngbin0 -> 1685 bytes
-rw-r--r--desktop/icons/Faenza/apps/64/vkpc.pngbin0 -> 2288 bytes
-rw-r--r--desktop/icons/Faenza/apps/96/vkpc.pngbin0 -> 3596 bytes
-rw-r--r--desktop/icons/hicolor/128x128/apps/vkpc.pngbin0 -> 3663 bytes
-rw-r--r--desktop/icons/hicolor/22x22/apps/vkpc.pngbin0 -> 664 bytes
-rw-r--r--desktop/icons/hicolor/24x24/apps/vkpc.pngbin0 -> 615 bytes
-rw-r--r--desktop/icons/hicolor/32x32/apps/vkpc.pngbin0 -> 860 bytes
-rw-r--r--desktop/icons/hicolor/36x36/apps/vkpc.pngbin0 -> 999 bytes
-rw-r--r--desktop/icons/hicolor/40x40/apps/vkpc.pngbin0 -> 1119 bytes
-rw-r--r--desktop/icons/hicolor/48x48/apps/vkpc.pngbin0 -> 1302 bytes
-rw-r--r--desktop/icons/hicolor/64x64/apps/vkpc.pngbin0 -> 1917 bytes
-rw-r--r--desktop/icons/hicolor/72x72/apps/vkpc.pngbin0 -> 2184 bytes
-rw-r--r--desktop/icons/hicolor/96x96/apps/vkpc.pngbin0 -> 2825 bytes
-rw-r--r--desktop/icons/ubuntu-mono-dark/16/vkpc.pngbin0 -> 479 bytes
-rw-r--r--desktop/icons/ubuntu-mono-dark/22/vkpc.pngbin0 -> 559 bytes
-rw-r--r--desktop/icons/ubuntu-mono-dark/24/vkpc.pngbin0 -> 597 bytes
-rw-r--r--desktop/icons/ubuntu-mono-light/16/vkpc.pngbin0 -> 359 bytes
-rw-r--r--desktop/icons/ubuntu-mono-light/22/vkpc.pngbin0 -> 396 bytes
-rw-r--r--desktop/icons/ubuntu-mono-light/24/vkpc.pngbin0 -> 414 bytes
-rw-r--r--desktop/info.h13
-rwxr-xr-xdesktop/install_icons.sh16
-rw-r--r--desktop/main.c140
-rw-r--r--desktop/server.c182
-rw-r--r--desktop/server.h14
-rw-r--r--desktop/vector.c74
-rw-r--r--desktop/vector.h23
-rw-r--r--extensions/chrome/_locales/en/messages.json8
-rw-r--r--extensions/chrome/common.js187
-rw-r--r--extensions/chrome/inject_and_return.js28
-rw-r--r--extensions/chrome/inject_exec.js74
-rw-r--r--extensions/chrome/manifest.json23
-rw-r--r--extensions/firefox/chrome.manifest2
-rw-r--r--extensions/firefox/chrome/background.js258
-rw-r--r--extensions/firefox/chrome/inject_on_load.js149
-rw-r--r--extensions/firefox/chrome/overlay.xul6
-rw-r--r--extensions/firefox/install.rdf20
53 files changed, 1323 insertions, 2 deletions
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 <stdio.h>
+#include <stdbool.h>
+#include <X11/Xlib.h>
+#include <X11/Xutil.h>
+#include <X11/XF86keysym.h>
+#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 0000000..1eaf260
--- /dev/null
+++ b/desktop/icons/Faenza-Radiance/apps/16/vkpc.png
Binary files differ
diff --git a/desktop/icons/Faenza-Radiance/apps/22/vkpc.png b/desktop/icons/Faenza-Radiance/apps/22/vkpc.png
new file mode 100644
index 0000000..03ec012
--- /dev/null
+++ b/desktop/icons/Faenza-Radiance/apps/22/vkpc.png
Binary files differ
diff --git a/desktop/icons/Faenza-Radiance/apps/24/vkpc.png b/desktop/icons/Faenza-Radiance/apps/24/vkpc.png
new file mode 100644
index 0000000..ad1fc6c
--- /dev/null
+++ b/desktop/icons/Faenza-Radiance/apps/24/vkpc.png
Binary files differ
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 0000000..4107fc3
--- /dev/null
+++ b/desktop/icons/Faenza-Radiance/apps/32/vkpc.png
Binary files differ
diff --git a/desktop/icons/Faenza-Radiance/apps/48/vkpc.png b/desktop/icons/Faenza-Radiance/apps/48/vkpc.png
new file mode 100644
index 0000000..041f8a6
--- /dev/null
+++ b/desktop/icons/Faenza-Radiance/apps/48/vkpc.png
Binary files differ
diff --git a/desktop/icons/Faenza-Radiance/apps/64/vkpc.png b/desktop/icons/Faenza-Radiance/apps/64/vkpc.png
new file mode 100644
index 0000000..ba1980d
--- /dev/null
+++ b/desktop/icons/Faenza-Radiance/apps/64/vkpc.png
Binary files differ
diff --git a/desktop/icons/Faenza-Radiance/apps/96/vkpc.png b/desktop/icons/Faenza-Radiance/apps/96/vkpc.png
new file mode 100644
index 0000000..853b46d
--- /dev/null
+++ b/desktop/icons/Faenza-Radiance/apps/96/vkpc.png
Binary files differ
diff --git a/desktop/icons/Faenza/apps/16/vkpc.png b/desktop/icons/Faenza/apps/16/vkpc.png
new file mode 100644
index 0000000..29e3991
--- /dev/null
+++ b/desktop/icons/Faenza/apps/16/vkpc.png
Binary files differ
diff --git a/desktop/icons/Faenza/apps/22/vkpc.png b/desktop/icons/Faenza/apps/22/vkpc.png
new file mode 100644
index 0000000..73daaa8
--- /dev/null
+++ b/desktop/icons/Faenza/apps/22/vkpc.png
Binary files differ
diff --git a/desktop/icons/Faenza/apps/24/vkpc.png b/desktop/icons/Faenza/apps/24/vkpc.png
new file mode 100644
index 0000000..3f1ca5e
--- /dev/null
+++ b/desktop/icons/Faenza/apps/24/vkpc.png
Binary files differ
diff --git a/desktop/icons/Faenza/apps/32/vkpc.png b/desktop/icons/Faenza/apps/32/vkpc.png
new file mode 100644
index 0000000..3502985
--- /dev/null
+++ b/desktop/icons/Faenza/apps/32/vkpc.png
Binary files differ
diff --git a/desktop/icons/Faenza/apps/48/vkpc.png b/desktop/icons/Faenza/apps/48/vkpc.png
new file mode 100644
index 0000000..3f9ffa5
--- /dev/null
+++ b/desktop/icons/Faenza/apps/48/vkpc.png
Binary files differ
diff --git a/desktop/icons/Faenza/apps/64/vkpc.png b/desktop/icons/Faenza/apps/64/vkpc.png
new file mode 100644
index 0000000..7278517
--- /dev/null
+++ b/desktop/icons/Faenza/apps/64/vkpc.png
Binary files differ
diff --git a/desktop/icons/Faenza/apps/96/vkpc.png b/desktop/icons/Faenza/apps/96/vkpc.png
new file mode 100644
index 0000000..d0e8b61
--- /dev/null
+++ b/desktop/icons/Faenza/apps/96/vkpc.png
Binary files differ
diff --git a/desktop/icons/hicolor/128x128/apps/vkpc.png b/desktop/icons/hicolor/128x128/apps/vkpc.png
new file mode 100644
index 0000000..7c5f49e
--- /dev/null
+++ b/desktop/icons/hicolor/128x128/apps/vkpc.png
Binary files differ
diff --git a/desktop/icons/hicolor/22x22/apps/vkpc.png b/desktop/icons/hicolor/22x22/apps/vkpc.png
new file mode 100644
index 0000000..0f9186c
--- /dev/null
+++ b/desktop/icons/hicolor/22x22/apps/vkpc.png
Binary files differ
diff --git a/desktop/icons/hicolor/24x24/apps/vkpc.png b/desktop/icons/hicolor/24x24/apps/vkpc.png
new file mode 100644
index 0000000..18b2b38
--- /dev/null
+++ b/desktop/icons/hicolor/24x24/apps/vkpc.png
Binary files differ
diff --git a/desktop/icons/hicolor/32x32/apps/vkpc.png b/desktop/icons/hicolor/32x32/apps/vkpc.png
new file mode 100644
index 0000000..8cad27c
--- /dev/null
+++ b/desktop/icons/hicolor/32x32/apps/vkpc.png
Binary files differ
diff --git a/desktop/icons/hicolor/36x36/apps/vkpc.png b/desktop/icons/hicolor/36x36/apps/vkpc.png
new file mode 100644
index 0000000..6448800
--- /dev/null
+++ b/desktop/icons/hicolor/36x36/apps/vkpc.png
Binary files differ
diff --git a/desktop/icons/hicolor/40x40/apps/vkpc.png b/desktop/icons/hicolor/40x40/apps/vkpc.png
new file mode 100644
index 0000000..df33c09
--- /dev/null
+++ b/desktop/icons/hicolor/40x40/apps/vkpc.png
Binary files differ
diff --git a/desktop/icons/hicolor/48x48/apps/vkpc.png b/desktop/icons/hicolor/48x48/apps/vkpc.png
new file mode 100644
index 0000000..d34540e
--- /dev/null
+++ b/desktop/icons/hicolor/48x48/apps/vkpc.png
Binary files differ
diff --git a/desktop/icons/hicolor/64x64/apps/vkpc.png b/desktop/icons/hicolor/64x64/apps/vkpc.png
new file mode 100644
index 0000000..f94329a
--- /dev/null
+++ b/desktop/icons/hicolor/64x64/apps/vkpc.png
Binary files differ
diff --git a/desktop/icons/hicolor/72x72/apps/vkpc.png b/desktop/icons/hicolor/72x72/apps/vkpc.png
new file mode 100644
index 0000000..3f26601
--- /dev/null
+++ b/desktop/icons/hicolor/72x72/apps/vkpc.png
Binary files differ
diff --git a/desktop/icons/hicolor/96x96/apps/vkpc.png b/desktop/icons/hicolor/96x96/apps/vkpc.png
new file mode 100644
index 0000000..b05a855
--- /dev/null
+++ b/desktop/icons/hicolor/96x96/apps/vkpc.png
Binary files differ
diff --git a/desktop/icons/ubuntu-mono-dark/16/vkpc.png b/desktop/icons/ubuntu-mono-dark/16/vkpc.png
new file mode 100644
index 0000000..440e6b9
--- /dev/null
+++ b/desktop/icons/ubuntu-mono-dark/16/vkpc.png
Binary files differ
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 0000000..6781df2
--- /dev/null
+++ b/desktop/icons/ubuntu-mono-dark/22/vkpc.png
Binary files differ
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 0000000..e57de23
--- /dev/null
+++ b/desktop/icons/ubuntu-mono-dark/24/vkpc.png
Binary files differ
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 0000000..dfb7295
--- /dev/null
+++ b/desktop/icons/ubuntu-mono-light/16/vkpc.png
Binary files differ
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 0000000..adc4436
--- /dev/null
+++ b/desktop/icons/ubuntu-mono-light/22/vkpc.png
Binary files differ
diff --git a/desktop/icons/ubuntu-mono-light/24/vkpc.png b/desktop/icons/ubuntu-mono-light/24/vkpc.png
new file mode 100644
index 0000000..e04c84d
--- /dev/null
+++ b/desktop/icons/ubuntu-mono-light/24/vkpc.png
Binary files differ
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. <ch1p@ch1p.com>"
+#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 <stdio.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <pthread.h>
+#include <gtk/gtk.h>
+
+#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 <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <libwebsockets.h>
+#include <stdbool.h>
+
+#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 <pthread.h>
+
+#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 <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#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('<xml></xml>', "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('<xml></xml>', "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 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!DOCTYPE overlay >
+<overlay id="vkpc-overlay" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <script type="application/javascript" src="chrome://vkpc/content/background.js"/>
+</overlay>
diff --git a/extensions/firefox/install.rdf b/extensions/firefox/install.rdf
new file mode 100644
index 0000000..30690ef
--- /dev/null
+++ b/extensions/firefox/install.rdf
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:em="http://www.mozilla.org/2004/em-rdf#">
+ <Description about="urn:mozilla:install-manifest">
+ <em:id>vkpc@ch1p.org</em:id>
+ <em:name>VK Player Controller Client</em:name>
+ <em:version>0.1</em:version>
+ <em:type>2</em:type>
+ <em:creator>ch1p</em:creator>
+ <em:description>Firefox client for VK Player Controller app.</em:description>
+
+ <em:targetApplication>
+ <Description>
+ <em:id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</em:id>
+ <em:minVersion>4.0</em:minVersion>
+ <em:maxVersion>30.0</em:maxVersion>
+ </Description>
+ </em:targetApplication>
+ </Description>
+</RDF>