initial commit
7
.gitignore
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
desktop/icons/*_psd
|
||||
extensions/chrome.crx
|
||||
extensions/chrome.pem
|
||||
extensions/chrome.zip
|
||||
extensions/firefox.xpi
|
||||
desktop/*.o
|
||||
desktop/vkpc
|
2
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
|
||||
|
@ -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
|
||||
|
29
desktop/Makefile
Normal file
@ -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
|
51
desktop/grab.c
Normal file
@ -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);
|
||||
}
|
15
desktop/grab.h
Normal file
@ -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
|
BIN
desktop/icons/Faenza-Radiance/apps/16/vkpc.png
Normal file
After Width: | Height: | Size: 512 B |
BIN
desktop/icons/Faenza-Radiance/apps/22/vkpc.png
Normal file
After Width: | Height: | Size: 685 B |
BIN
desktop/icons/Faenza-Radiance/apps/24/vkpc.png
Normal file
After Width: | Height: | Size: 621 B |
BIN
desktop/icons/Faenza-Radiance/apps/32/vkpc.png
Normal file
After Width: | Height: | Size: 904 B |
BIN
desktop/icons/Faenza-Radiance/apps/48/vkpc.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
BIN
desktop/icons/Faenza-Radiance/apps/64/vkpc.png
Normal file
After Width: | Height: | Size: 1.7 KiB |
BIN
desktop/icons/Faenza-Radiance/apps/96/vkpc.png
Normal file
After Width: | Height: | Size: 2.7 KiB |
BIN
desktop/icons/Faenza/apps/16/vkpc.png
Normal file
After Width: | Height: | Size: 627 B |
BIN
desktop/icons/Faenza/apps/22/vkpc.png
Normal file
After Width: | Height: | Size: 824 B |
BIN
desktop/icons/Faenza/apps/24/vkpc.png
Normal file
After Width: | Height: | Size: 804 B |
BIN
desktop/icons/Faenza/apps/32/vkpc.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
desktop/icons/Faenza/apps/48/vkpc.png
Normal file
After Width: | Height: | Size: 1.6 KiB |
BIN
desktop/icons/Faenza/apps/64/vkpc.png
Normal file
After Width: | Height: | Size: 2.2 KiB |
BIN
desktop/icons/Faenza/apps/96/vkpc.png
Normal file
After Width: | Height: | Size: 3.5 KiB |
BIN
desktop/icons/hicolor/128x128/apps/vkpc.png
Normal file
After Width: | Height: | Size: 3.6 KiB |
BIN
desktop/icons/hicolor/22x22/apps/vkpc.png
Normal file
After Width: | Height: | Size: 664 B |
BIN
desktop/icons/hicolor/24x24/apps/vkpc.png
Normal file
After Width: | Height: | Size: 615 B |
BIN
desktop/icons/hicolor/32x32/apps/vkpc.png
Normal file
After Width: | Height: | Size: 860 B |
BIN
desktop/icons/hicolor/36x36/apps/vkpc.png
Normal file
After Width: | Height: | Size: 999 B |
BIN
desktop/icons/hicolor/40x40/apps/vkpc.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
desktop/icons/hicolor/48x48/apps/vkpc.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
BIN
desktop/icons/hicolor/64x64/apps/vkpc.png
Normal file
After Width: | Height: | Size: 1.9 KiB |
BIN
desktop/icons/hicolor/72x72/apps/vkpc.png
Normal file
After Width: | Height: | Size: 2.1 KiB |
BIN
desktop/icons/hicolor/96x96/apps/vkpc.png
Normal file
After Width: | Height: | Size: 2.8 KiB |
BIN
desktop/icons/ubuntu-mono-dark/16/vkpc.png
Normal file
After Width: | Height: | Size: 479 B |
BIN
desktop/icons/ubuntu-mono-dark/22/vkpc.png
Normal file
After Width: | Height: | Size: 559 B |
BIN
desktop/icons/ubuntu-mono-dark/24/vkpc.png
Normal file
After Width: | Height: | Size: 597 B |
BIN
desktop/icons/ubuntu-mono-light/16/vkpc.png
Normal file
After Width: | Height: | Size: 359 B |
BIN
desktop/icons/ubuntu-mono-light/22/vkpc.png
Normal file
After Width: | Height: | Size: 396 B |
BIN
desktop/icons/ubuntu-mono-light/24/vkpc.png
Normal file
After Width: | Height: | Size: 414 B |
13
desktop/info.h
Normal file
@ -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
|
16
desktop/install_icons.sh
Executable file
@ -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
|
140
desktop/main.c
Normal file
@ -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;
|
||||
}
|
182
desktop/server.c
Normal file
@ -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;
|
||||
}
|
14
desktop/server.h
Normal file
@ -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
|
74
desktop/vector.c
Normal file
@ -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);
|
||||
}
|
23
desktop/vector.h
Normal file
@ -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
|
8
extensions/chrome/_locales/en/messages.json
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"title": {
|
||||
"message": "VK Player Controller Client"
|
||||
},
|
||||
"description_short": {
|
||||
"message": "Chrome client for VK Player Controller app."
|
||||
}
|
||||
}
|
187
extensions/chrome/common.js
Normal file
@ -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();
|
28
extensions/chrome/inject_and_return.js
Normal file
@ -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
|
||||
});
|
||||
})();
|
74
extensions/chrome/inject_exec.js
Normal file
@ -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);
|
||||
}
|
||||
})();
|
23
extensions/chrome/manifest.json
Normal file
@ -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"]
|
||||
}
|
2
extensions/firefox/chrome.manifest
Normal file
@ -0,0 +1,2 @@
|
||||
content vkpc chrome/
|
||||
overlay chrome://browser/content/browser.xul chrome://vkpc/content/overlay.xul
|
258
extensions/firefox/chrome/background.js
Normal file
@ -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();
|
||||
|
||||
};
|
149
extensions/firefox/chrome/inject_on_load.js
Normal file
@ -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);
|
||||
})();
|
6
extensions/firefox/chrome/overlay.xul
Normal file
@ -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>
|
20
extensions/firefox/install.rdf
Normal file
@ -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>
|