Skip to content

Commit

Permalink
Run on Python2 and 3; add debugging to file; fix double-json decoding…
Browse files Browse the repository at this point in the history
… issue
  • Loading branch information
DB committed Apr 5, 2018
1 parent 3728866 commit b91ef27
Show file tree
Hide file tree
Showing 2 changed files with 56 additions and 20 deletions.
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -183,10 +183,12 @@ The important parts are the **`name`**, which uniquely identifies the NM host, t

This NM manifest has to be copied or symlinked [to the correct location for your operating system](https://developer.mozilla.org/en-US/Add-ons/WebExtensions/Native_manifests), under Linux a suitable location is `~/.mozilla/native-messaging-hosts/<name>.json`, in our case thus `~/.mozilla/native-messaging-hosts/runwith.json`.

A Python NM program that works the way the addon expects (**`runwith.py`**) [is included in the repo](https://github.com/waldner/Firefox-RunWith/blob/master/runwith.py), so you should copy it somewhere and update its path in the NM manifest.
A Python NM program that works the way the addon expects (**`runwith.py`**) [is included in the repo](https://github.com/waldner/Firefox-RunWith/blob/master/runwith.py), so you should copy it somewhere and update its path in the NM manifest. It should work with Python 2 and Python 3.

**`runwith.py`** speaks the NM protocol, it expects to receive on stdin a JSON array with the command to run and its arguments, runs the command according to the user's shell/wait preferences, then writes back (to the extension) a brief summary of the execution (in case you're interested, it can be seen in the browser console, which can be opened with CTRL+SHIFT+J, along with other debugging messages output by the [**`background.js`**](https://github.com/waldner/Firefox-RunWith/blob/master/addon/background.js) script).

Inside `runwith.py`, you can set the **`enable_debug`** variable to `True` to dump the various incoming and outgoing messages it processes to a file (by default, `/tmp/runwith_debug.log`, see the `debug_file` variable).

#### What do "`shell`" and "`wait`" do exactly?

Let's assume you have a command like the following for an action in your configuration:
Expand Down
72 changes: 53 additions & 19 deletions runwith.py
Original file line number Diff line number Diff line change
@@ -1,40 +1,70 @@
#!/usr/bin/python2
#!/usr/bin/python

# Note that running python with the `-u` flag is required on Windows,
# in order to ensure that stdin and stdout are opened in binary, rather
# than text, mode.

import sys, json, struct, os
import subprocess

def debug(msg):
if enable_debug:
with open(debug_file, 'a') as outfile:
outfile.write(msg)
outfile.write("\n")

# Read a message from stdin and decode it.
def getMessage():
rawLength = sys.stdin.read(4)
rawLength = our_stdin.read(4)
if len(rawLength) == 0:
sys.exit(0)
messageLength = struct.unpack('@I', rawLength)[0]
message = sys.stdin.read(messageLength)
debug("getMessage: messageLength is %s" % messageLength)
message = our_stdin.read(messageLength).decode()
debug("getMessage: message is %s" % message)
return json.loads(message)

# Encode a message for transmission, given its content.
def encodeMessage(messageContent):
debug("encodeMessage: messageContent is %s" % messageContent)
encodedContent = json.dumps(messageContent)
debug("encodeMessage: encodedContent is %s\n" % encodedContent)
encodedLength = struct.pack('@I', len(encodedContent))
return {'length': encodedLength, 'content': encodedContent}
debug("encodeMessage: encodedLength is %s" % encodedLength)
return {'length': encodedLength, 'content': encodedContent.encode()}

# Send an encoded message to stdout.
def sendMessage(encodedMessage):
sys.stdout.write(encodedMessage['length'])
sys.stdout.write(encodedMessage['content'])
sys.stdout.flush()

debug("sendMessage: encodedMessage is %s" % encodedMessage)
debug("sendMessage: encodedMessage['length'] is %s" % encodedMessage['length'])
debug("sendMessage: encodedMessage['content'] is %s" % encodedMessage['content'])

our_stdout.write(encodedMessage['length'])
our_stdout.write(encodedMessage['content'])
our_stdout.flush()

# determine Python version
python_version = sys.version_info[0] # should be 2 or 3

if python_version == 2:
our_stdin = sys.stdin
our_stdout = sys.stdout
else:
our_stdin = sys.stdin.buffer
our_stdout = sys.stdout.buffer

receivedMessage = getMessage()

msg = json.loads(receivedMessage)
# set this to true to dump things to the debug_file
enable_debug = False
debug_file = "/tmp/runwith_debug.log"

cmd = msg['cmd']
shell = msg['shell']
wait = msg['wait']
receivedMessage = getMessage()
debug("main: receivedMessage is %s" % receivedMessage)

cmd = receivedMessage['cmd']
shell = receivedMessage['shell']
wait = receivedMessage['wait']

use_shell = False
command = cmd
Expand All @@ -43,21 +73,25 @@ def sendMessage(encodedMessage):
command = ' '.join(cmd)

devnull = open(os.devnull, 'w')
stdout = devnull
stderr = devnull
child_stdout = devnull
child_stderr = devnull
close_fds = True
if wait:
stdout = subprocess.PIPE
stderr = subprocess.PIPE
child_stdout = subprocess.PIPE
child_stderr = subprocess.PIPE
close_fds = False

proc = subprocess.Popen(command, stdout = stdout, stderr = stderr, shell = use_shell, close_fds = close_fds)
proc = subprocess.Popen(command, stdout = child_stdout, stderr = child_stderr, shell = use_shell, close_fds = close_fds)

msg = { 'pid': proc.pid, 'shell': use_shell, 'wait': wait }

if wait:
stdout, stderr = proc.communicate()
exit_code = proc.returncode
msg = "command ran, exit status: %s, stderr: %s" % (exit_code, stderr)
msg['exit-status'] = exit_code
msg['stderr'] = stderr.decode()
else:
msg = "async process launched, PID: %s" % proc.pid
msg['exit-status'] = 'N/A'
msg['stderr'] = 'N/A'

sendMessage(encodeMessage(msg))

0 comments on commit b91ef27

Please sign in to comment.