Commit 16265571 authored by Jonathan Wilkes's avatar Jonathan Wilkes
Browse files

first draft for opening files from command line or graphical file manager in...

first draft for opening files from command line or graphical file manager in the current Purr Data instance
parent d70f46c5
......@@ -25,7 +25,7 @@ function have_args() {
}
function set_vars(win) {
var port_no, gui_dir, font_engine_sanity;
var port_no, gui_dir, font_engine_sanity, pd_engine_id;
// If the GUI was started by Pd, our port number is going to be
// the first argument. If the GUI is supposed to start Pd, we won't
// have any arguments and need to set it here.
......@@ -33,6 +33,8 @@ function set_vars(win) {
port_no = gui.App.argv[0]; // fed to us by the Pd process
// looks like this is the same as pwd below
gui_dir = gui.App.argv[3];
// address unique to the pd_engine
pd_engine_id = gui.App.argv[4];
} else {
// If we're starting Pd, this is the first port number to try. (We'll
// increment it if that port happens to be taken.
......@@ -44,6 +46,7 @@ function set_vars(win) {
gui_dir = process.platform === "darwin" ? "bin" : pwd;
}
pdgui.set_port(port_no);
pdgui.set_pd_engine_id(pd_engine_id);
pdgui.set_pwd(pwd);
pdgui.set_gui_dir(gui_dir);
pdgui.set_pd_window(win);
......@@ -256,6 +259,14 @@ function add_events() {
pdgui.pdsend("pd dsp", dsp_state);
}
);
// Opening another file
nw.App.on("open", function(argv_string) {
var port, pd_engine_id;
port = argv_string.split(" ").slice(-5, -4);
pd_engine_id = argv_string.split(" ").slice(-1);
pdgui.connect_as_client_to_secondary_instance("localhost", port,
pd_engine_id);
});
// Browser Window Close
gui.Window.get().on("close", function () {
pdgui.menu_quit();
......
......@@ -4,6 +4,7 @@ var pwd;
var gui_dir;
var lib_dir;
var last_clipboard_data;
var pd_engine_id;
exports.set_pwd = function(pwd_string) {
pwd = pwd_string;
......@@ -21,6 +22,10 @@ function defunkify_windows_path(s) {
return ret;
}
exports.set_pd_engine_id = function (id) {
pd_engine_id = id;
}
exports.defunkify_windows_path = defunkify_windows_path;
exports.set_gui_dir = function(dir_string) {
......@@ -217,8 +222,6 @@ var pd_myversion, // Pd version string
popup_coords = [0,0];
var startup_files = []; // Array of files to be opened at startup (from the command line)
// Keycode vs Charcode: A Primer
// -----------------------------
// * keycode is a unique number assigned to a physical key on the keyboard
......@@ -786,19 +789,6 @@ function open_file(file) {
}
}
// This doesn't work yet... need to figure out how to send command line args
// (files) to be opened by the unique instance
function gui_open_files_via_unique(filenames)
{
var i;
length = filenames.length;
if (length != 0) {
for (i = 0; i < length; i++) {
open_file(filenames[i]);
}
}
}
function open_html(target) {
nw_open_html(target);
}
......@@ -845,34 +835,29 @@ function external_doc_open(url) {
exports.external_doc_open = external_doc_open;
function gui_build_filelist(file) {
startup_files.push(file);
function gui_set_cwd(dummy, cwd) {
post("cwd from secondary instance is " + cwd);
if (cwd !== ".") {
pwd = cwd;
}
}
// This doesn't work at the moment. Not sure how to feed the command line
// filelist to a single instance of node-webkit.
function gui_check_unique (unique) {
// global appname
return;
var final_filenames = new Array,
startup_dir = pwd,
filelist_length,
function gui_open_via_unique (secondary_pd_engine_id, unique, file_array) {
var startup_dir = pwd,
i,
file,
dir;
if (unique == 0) {
filelist_length = startup_files.length;
for (i = 0; i < filelist_length; i++) {
file = startup_files[i];
dir;
if (!pathIsAbsolute(file)) {
file = fs.join(pwd, file);
file;
if (unique == 0 && secondary_pd_engine_id !== pd_engine_id) {
for (i = 0; i < file_array.length; i++) {
file = file_array[i];
if (!path.isAbsolute(file)) {
file = path.join(pwd, file);
}
final_filenames.push(file);
open_file(file);
}
gui_open_files_via_unique(final_filenames);
quit_secondary_pd_instance(secondary_pd_engine_id);
}
// old tcl follows... see pd.tk for the original code
}
function gui_startup(version, fontname_from_pd, fontweight_from_pd,
......@@ -1209,6 +1194,85 @@ exports.set_port = function (port_no) {
PORT = port_no;
}
var secondary_pd_engines = {};
// This is an alarmingly complicated and brittle approach to opening
// files from a secondary instance of Pd in a currently running instance.
// It works something like this:
// 1. User is running an instance of Purr Data.
// 2. User runs another instance of Purr Data from the command line, specifying
// files to be opened as command line args. Or, they click on a file which
// in the desktop or file manager which triggers the same behavior.
// 2. A new Pd process starts-- let's call it a "secondary pd engine".
// 3. The secondary pd engine tries to run a new GUI.
// 4. The secondary GUI forwards an "open" message to the currently running GUI.
// 5. The secondary GUI exits (before spawning any windows).
// 6. The original GUI receives the "open" message, finds the port number
// for the secondary Pd engine, and opens a socket to it.
// 7. The original GUI receives a message to set the working directory to
// whatever the secondary Pd engine thinks it should be.
// 8. The original GUI sends a message to the secondary Pd instance, telling
// it to send a list of files to be opened.
// 9. The original GUI receives a message from the secondary Pd instance
// with the list of files.
// 10.For each file to be opened, the original GUI sends a message to the
// _original_ Pd engine to open the file.
// 11.Once these messages have been sent, the original GUI sends a message
// to the secondary Pd engine to quit.
// 12.The original Pd engine opens the files, and the secondary Pd instance
// quits.
function connect_as_client_to_secondary_instance(host, port, pd_engine_id) {
var client = new net.Socket(),
command_buffer = {
next_command: ""
};
client.setNoDelay(true);
client.connect(+port, host, function() {
console.log("CONNECTED TO: " + host + ":" + port);
secondary_pd_engines[pd_engine_id] = {
socket: client
}
client.write("pd forward_files_from_secondary_instance;");
});
client.on("data", function(data) {
// Terrible thing:
// We're parsing the data as it comes in-- possibly
// from multiple ancillary instances of the Pd engine.
// So to retain some semblance of sanity, we only let the
// parser evaluate commands that we list in the array below--
// anything else will be discarded. This is of course bad
// because it means simple changes to the code, e.g., changing
// the name of the function "gui_set_cwd" would cause a bug
// if you forget to come here and also change that name in the
// array below.
// Another terrible thing-- gui_set_cwd sets a single, global
// var for the working directory. So if the user does something
// weird like write a script to open files from random directories,
// there would be a race and it might not work.
// Yet another terrible thing-- now we're setting the current
// working directory both in the GUI, and from the secondary instances
// with "gui_set_cwd" below.
perfect_parser(data, command_buffer, [
"gui_set_cwd",
"gui_open_via_unique"
]);
});
client.on("close", function () {
// I guess somebody could script opening patches in an
// installation, so let's go ahead and delete the key here
// (The alternative is just setting it to undefined)
delete secondary_pd_engines[pd_engine_id];
});
}
function quit_secondary_pd_instance (pd_engine_id) {
secondary_pd_engines[pd_engine_id].socket.write("pd quit;");
}
// This is called when the running GUI receives an "open" event.
exports.connect_as_client_to_secondary_instance =
connect_as_client_to_secondary_instance;
function connect_as_client() {
var client = new net.Socket();
client.setNoDelay(true);
......@@ -1261,17 +1325,12 @@ exports.connect_as_server = connect_as_server;
// data parameter is what the server sent to this socket
// We're not receiving FUDI (i.e., Pd) messages. Right now we're just using
// an alarm bell '\a' to signal the end of a message. This is easier than
// checking for unescaped semicolons, since it only requires a check for a
// single byte. Of course this makes it more brittle, so it can be changed
// later if needed.
// the unit separator (ASCII 31) to signal the end of a message. This is
// easier than checking for unescaped semicolons, since it only requires a
// check for a single byte. Of course this makes it more brittle, so it can
// be changed later if needed.
function init_socket_events () {
var next_command = ""; // A not-quite-FUDI command: selector arg1,arg2,etc.
// These are formatted on the C side to be easy
// to parse here in javascript
var perfect_parser = function(data) {
function perfect_parser(data, cbuf, sel_array) {
var i, len, selector, args;
len = data.length;
for (i = 0; i < len; i++) {
......@@ -1280,24 +1339,33 @@ function init_socket_events () {
// decode next_command
try {
// This should work for all utf-8 content
next_command = decodeURIComponent(next_command);
cbuf.next_command =
decodeURIComponent(cbuf.next_command);
}
catch(err) {
// This should work for ISO-8859-1
next_command = unescape(next_command);
cbuf.next_command = unescape(cbuf.next_command);
}
// Turn newlines into backslash + "n" so
// eval will do the right thing with them
next_command = next_command.replace(/\n/g, "\\n");
next_command = next_command.replace(/\r/g, "\\r");
selector = next_command.slice(0, next_command.indexOf(" "));
args = next_command.slice(selector.length + 1);
next_command = "";
cbuf.next_command = cbuf.next_command.replace(/\n/g, "\\n");
cbuf.next_command = cbuf.next_command.replace(/\r/g, "\\r");
selector = cbuf.next_command.slice(0, cbuf.next_command.indexOf(" "));
args = cbuf.next_command.slice(selector.length + 1);
cbuf.next_command = "";
// Now evaluate it
//post("Evaling: " + selector + "(" + args + ");");
eval(selector + "(" + args + ");");
// For communicating with a secondary instance, we filter
// incoming messages. A better approach would be to make
// sure that the Pd engine only sends the gui_set_cwd message
// before "gui_startup". Then we could just check the
// Pd engine id in "gui_startup" and branch there, instead of
// fudging with the parser here.
if (!sel_array || sel_array.indexOf(selector) !== -1) {
eval(selector + "(" + args + ");");
}
} else {
next_command += "%" +
cbuf.next_command += "%" +
("0" // leading zero (for rare case of single digit)
+ data[i].toString(16)) // to hex
.slice(-2); // remove extra leading zero
......@@ -1305,31 +1373,15 @@ function init_socket_events () {
}
};
var fast_parser = function(data) {
var i, len, selector, args;
len = data.length;
for (i = 0; i < len; i++) {
// check for end of command:
if (data[i] === String.fromCharCode(7)) { // vertical tab
// we have the next_command...
// Turn newlines into backslash + "n" so
// eval will do the right thing
next_command = next_command.replace(/\n/g, "\\n");
selector = next_command.slice(0, next_command.indexOf(" "));
args = next_command.slice(selector.length + 1);
next_command = "";
// Now evaluate it-- unfortunately the V8 engine can't
// optimize this eval call.
//post("Evaling: " + selector + "(" + args + ");");
eval(selector + "(" + args + ");");
} else {
next_command += data[i];
}
}
function init_socket_events () {
// A not-quite-FUDI command: selector arg1,arg2,etc. These are
// formatted on the C side to be easy to parse here in javascript
var command_buffer = {
next_command: ""
};
connection.on("data", perfect_parser);
connection.on("data", function(data) {
perfect_parser(data, command_buffer);
});
connection.on("error", function(e) {
console.log("Socket error: " + e.code);
nw_app_quit();
......
......@@ -36,6 +36,7 @@ void glob_startup_dialog(t_pd *dummy, t_symbol *s, int argc, t_atom *argv);
void glob_ping(t_pd *dummy);
void glob_watchdog(t_pd *dummy);
void glob_savepreferences(t_pd *dummy);
void glob_forward_files_from_secondary_instance(void);
void alsa_resync( void);
......@@ -111,6 +112,9 @@ void glob_init(void)
CLASS_DEFAULT, A_NULL);
class_addmethod(glob_pdobject, (t_method)glob_initfromgui, gensym("init"),
A_GIMME, 0);
class_addmethod(glob_pdobject,
(t_method)glob_forward_files_from_secondary_instance,
gensym("forward_files_from_secondary_instance"), 0);
class_addmethod(glob_pdobject, (t_method)glob_setfilename,
gensym("filename"), A_SYMBOL, A_SYMBOL, 0);
class_addmethod(glob_pdobject, (t_method)glob_evalfile, gensym("open"),
......
......@@ -1134,12 +1134,22 @@ static int defaultfontshit[MAXFONTS] = {
int sys_startgui(const char *guidir)
{
pid_t childpid;
char cwd[FILENAME_MAX];
char cmdbuf[4*MAXPDSTRING];
struct sockaddr_in server = {0};
int len = sizeof(server);
int ntry = 0, portno = FIRSTPORTNUM;
int xsock = -1;
stderr_isatty = isatty(2);
#ifdef MSW
if (GetCurrentDirectory(FILENAME_MAX, cwd) == 0)
strcpy(cwd, ".");
#endif
#ifdef HAVE_UNISTD_H
if (!getcwd(cwd, FILENAME_MAX))
strcpy(cwd, ".");
#endif
#ifdef MSW
short version = MAKEWORD(2, 0);
WSADATA nobby;
......@@ -1178,15 +1188,7 @@ int sys_startgui(const char *guidir)
skip starting the GUI up. */
t_atom zz[NDEFAULTFONT+2];
int i;
#ifdef MSW
if (GetCurrentDirectory(MAXPDSTRING, cmdbuf) == 0)
strcpy(cmdbuf, ".");
#endif
#ifdef HAVE_UNISTD_H
if (!getcwd(cmdbuf, MAXPDSTRING))
strcpy(cmdbuf, ".");
#endif
strcpy(cmdbuf, cwd);
SETSYMBOL(zz, gensym(cmdbuf));
for (i = 0; i < (int)NDEFAULTFONT; i++)
SETFLOAT(zz+i+1, defaultfontshit[i]);
......@@ -1358,13 +1360,14 @@ fprintf(stderr, "guidir is %s\n", guidir);
we add it again as the last argument to make sure we can fetch
it on the GUI side. */
sprintf(cmdbuf,
"%s/nw/nw %s %d localhost %s %s\n",
"%s/nw/nw %s %d localhost %s %s x%.6lx",
guidir,
guidir,
// "/home/user/purr-data/pd/nw",
portno,
(sys_k12_mode ? "pd-l2ork-k12" : "pd-l2ork"),
guidir);
guidir,
(long unsigned int)pd_this);
#endif
sys_guicmd = cmdbuf;
......@@ -1596,6 +1599,7 @@ fprintf(stderr, "guidir is %s\n", guidir);
gui_end_array();
gui_end_vmess();
gui_vmess("gui_set_cwd", "xs", 0, cwd);
binbuf_free(aapis);
binbuf_free(mapis);
}
......
......@@ -259,6 +259,31 @@ static void pd_makeversion(void)
pd_version = strdup(foo);
}
/* send the openlist to the GUI before closing a secondary
instance of Pd. */
void glob_forward_files_from_secondary_instance(void)
{
/* check if we are unique, otherwise, just focus existing
instance, and if necessary open file inside it. This doesn't
yet work with the new GUI because we need to set it up to
allow multiple instances. */
gui_start_vmess("gui_open_via_unique", "xi", pd_this, sys_unique);
gui_start_array();
if (sys_openlist)
{
// send the files to be opened to the GUI. We send them as an
// array here so that we don't have to allocate anything here
// (as the previous API did)
t_namelist *nl;
for (nl = sys_openlist; nl; nl = nl->nl_next)
{
gui_s(nl->nl_string);
}
}
gui_end_array();
gui_end_vmess();
}
/* this is called from main() in s_entry.c */
int sys_main(int argc, char **argv)
{
......@@ -296,22 +321,6 @@ int sys_main(int argc, char **argv)
/* send the name of the gui preset */
gui_vmess("gui_set_gui_preset", "s", sys_gui_preset->s_name);
if (sys_openlist)
{
// send the files to be opened to the GUI. We send them one
// at a time and let the GUI accumulate them so that we don't
// have to allocate anything here (as the previous API did)
t_namelist *nl;
for (nl = sys_openlist; nl; nl = nl->nl_next)
{
gui_vmess("gui_build_filelist", "s", nl->nl_string);
}
}
/* check if we are unique, otherwise, just focus existing
instance, and if necessary open file inside it. This doesn't
yet work with the new GUI because we need to set it up to
allow multiple instances. */
gui_vmess("gui_check_unique", "i", sys_unique);
if (sys_externalschedlib)
return (sys_run_scheduler(sys_externalschedlibname,
sys_extraflagsstring));
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment