2018-04-17 18:28:11 +01:00
|
|
|
#!/usr/bin/env python3
|
|
|
|
# -*- coding: utf-8 -*-
|
|
|
|
|
|
|
|
import sys
|
|
|
|
import os
|
|
|
|
import json
|
|
|
|
import struct
|
2018-04-17 18:45:54 +01:00
|
|
|
import subprocess
|
2018-04-24 22:36:38 +01:00
|
|
|
import tempfile
|
2018-04-17 18:28:11 +01:00
|
|
|
|
2018-05-10 21:22:14 +01:00
|
|
|
VERSION = "0.1.3"
|
2018-04-17 18:28:11 +01:00
|
|
|
|
|
|
|
class NoConnectionError(Exception):
|
|
|
|
""" Exception thrown when stdin cannot be read """
|
|
|
|
|
|
|
|
|
|
|
|
def eprint(*args, **kwargs):
|
|
|
|
""" Print to stderr, which gets echoed in the browser console when run
|
|
|
|
by Firefox
|
|
|
|
"""
|
|
|
|
print(*args, file=sys.stderr, flush=True, **kwargs)
|
|
|
|
|
|
|
|
|
2018-05-09 22:09:10 +02:00
|
|
|
def getenv(variable, default):
|
2018-04-17 18:28:11 +01:00
|
|
|
""" Get an environment variable value, or use the default provided """
|
|
|
|
return os.environ.get(variable) or default
|
|
|
|
|
|
|
|
def getMessage():
|
2018-04-18 21:49:33 +01:00
|
|
|
"""Read a message from stdin and decode it.
|
|
|
|
|
|
|
|
"Each message is serialized using JSON, UTF-8 encoded and is preceded with
|
|
|
|
a 32-bit value containing the message length in native byte order."
|
|
|
|
|
|
|
|
https://developer.mozilla.org/en-US/Add-ons/WebExtensions/Native_messaging#App_side
|
|
|
|
|
|
|
|
"""
|
2018-04-17 18:28:11 +01:00
|
|
|
rawLength = sys.stdin.buffer.read(4)
|
|
|
|
if len(rawLength) == 0:
|
|
|
|
sys.exit(0)
|
|
|
|
messageLength = struct.unpack('@I', rawLength)[0]
|
|
|
|
message = sys.stdin.buffer.read(messageLength).decode('utf-8')
|
|
|
|
return json.loads(message)
|
|
|
|
|
|
|
|
|
|
|
|
# Encode a message for transmission,
|
|
|
|
# given its content.
|
|
|
|
def encodeMessage(messageContent):
|
|
|
|
""" Encode a message for transmission, given its content."""
|
|
|
|
encodedContent = json.dumps(messageContent).encode('utf-8')
|
|
|
|
encodedLength = struct.pack('@I', len(encodedContent))
|
|
|
|
return {'length': encodedLength, 'content': encodedContent}
|
|
|
|
|
|
|
|
|
|
|
|
# Send an encoded message to stdout
|
|
|
|
def sendMessage(encodedMessage):
|
|
|
|
""" Send an encoded message to stdout."""
|
|
|
|
sys.stdout.buffer.write(encodedMessage['length'])
|
|
|
|
sys.stdout.buffer.write(encodedMessage['content'])
|
2018-04-26 12:33:01 +01:00
|
|
|
try:
|
|
|
|
sys.stdout.buffer.write(encodedMessage['code'])
|
|
|
|
except KeyError:
|
|
|
|
pass
|
|
|
|
|
2018-04-17 18:28:11 +01:00
|
|
|
sys.stdout.buffer.flush()
|
|
|
|
|
|
|
|
|
2018-05-10 21:22:14 +01:00
|
|
|
def findUserConfigFile():
|
|
|
|
""" Find a user config file, if it exists. Return the file path, or None
|
|
|
|
if not found
|
|
|
|
"""
|
|
|
|
home = os.path.expanduser('~')
|
|
|
|
config_dir = getenv('XDG_CONFIG_HOME', os.path.expanduser('~/.config'))
|
|
|
|
|
|
|
|
# Will search for files in this order
|
|
|
|
candidate_files = [
|
|
|
|
os.path.join(config_dir, "tridactyl", "tridactylrc"),
|
|
|
|
os.path.join(home, '.tridactylrc')
|
|
|
|
]
|
|
|
|
|
|
|
|
config_path = None
|
|
|
|
|
|
|
|
# find the first path in the list that exists
|
|
|
|
for path in candidate_files:
|
|
|
|
if os.path.isfile(path):
|
|
|
|
config_path = path
|
|
|
|
break
|
|
|
|
|
|
|
|
return config_path
|
|
|
|
|
|
|
|
|
|
|
|
def getUserConfig():
|
|
|
|
# look it up freshly each time - the user could have moved or killed it
|
|
|
|
cfg_file = findUserConfigFile()
|
|
|
|
|
|
|
|
# no file, return
|
|
|
|
if not cfg_file:
|
|
|
|
return None
|
|
|
|
|
|
|
|
# for now, this is a simple file read, but if the files can
|
|
|
|
# include other files, that will need more work
|
|
|
|
return open(cfg_file, 'r').read()
|
|
|
|
|
|
|
|
|
2018-04-17 18:28:11 +01:00
|
|
|
def handleMessage(message):
|
|
|
|
""" Generate reply from incoming message. """
|
|
|
|
cmd = message["cmd"]
|
|
|
|
reply = {'cmd': cmd}
|
|
|
|
|
|
|
|
if cmd == 'version':
|
|
|
|
reply = {'version': VERSION}
|
|
|
|
|
2018-05-10 21:22:14 +01:00
|
|
|
elif cmd == 'getconfig':
|
|
|
|
file_content = getUserConfig()
|
|
|
|
if file_content:
|
|
|
|
reply['content'] = file_content
|
|
|
|
else:
|
|
|
|
reply['code'] = 'File not found'
|
|
|
|
|
2018-04-17 18:45:54 +01:00
|
|
|
elif cmd == 'run':
|
2018-04-26 12:33:01 +01:00
|
|
|
commands = message["command"]
|
|
|
|
|
|
|
|
try:
|
|
|
|
p = subprocess.check_output(commands, shell=True)
|
|
|
|
reply["content"] = p.decode("utf-8")
|
|
|
|
reply["code"] = 0
|
|
|
|
|
|
|
|
except subprocess.CalledProcessError as process:
|
|
|
|
reply["code"] = process.returncode
|
|
|
|
reply["content"] = process.output.decode("utf-8")
|
2018-04-17 18:45:54 +01:00
|
|
|
|
2018-04-18 21:49:33 +01:00
|
|
|
elif cmd == 'eval':
|
|
|
|
output = eval(message["command"])
|
|
|
|
reply['content'] = output
|
|
|
|
|
2018-04-17 18:45:54 +01:00
|
|
|
elif cmd == 'read':
|
2018-04-28 00:08:39 +01:00
|
|
|
try:
|
2018-05-10 21:37:13 +01:00
|
|
|
with open(os.path.expandvars(os.path.expanduser(message["file"])), "r") as file:
|
2018-04-28 00:08:39 +01:00
|
|
|
reply['content'] = file.read()
|
|
|
|
reply['code'] = 0
|
|
|
|
except FileNotFoundError:
|
|
|
|
reply['content'] = ""
|
|
|
|
reply['code'] = 2
|
|
|
|
|
|
|
|
elif cmd == 'mkdir':
|
|
|
|
os.makedirs(
|
|
|
|
os.path.relpath(message["dir"]), exist_ok=message["exist_ok"]
|
|
|
|
)
|
|
|
|
reply['content'] = ""
|
|
|
|
reply['code'] = 0
|
2018-04-17 18:45:54 +01:00
|
|
|
|
|
|
|
elif cmd == 'write':
|
2018-04-26 12:33:01 +01:00
|
|
|
with open(message["file"], "w") as file:
|
2018-04-17 18:45:54 +01:00
|
|
|
file.write(message["content"])
|
|
|
|
|
2018-04-24 22:36:38 +01:00
|
|
|
elif cmd == 'temp':
|
2018-04-26 12:33:01 +01:00
|
|
|
(handle, filepath) = tempfile.mkstemp()
|
2018-05-11 09:49:40 -04:00
|
|
|
with os.fdopen(handle, "w") as file:
|
2018-04-24 22:36:38 +01:00
|
|
|
file.write(message["content"])
|
|
|
|
reply['content'] = filepath
|
|
|
|
|
2018-05-09 22:09:10 +02:00
|
|
|
elif cmd == 'env':
|
|
|
|
reply['content'] = getenv(message["var"], "")
|
|
|
|
|
2018-04-17 18:28:11 +01:00
|
|
|
else:
|
|
|
|
reply = {'cmd': 'error', 'error': 'Unhandled message'}
|
|
|
|
eprint('Unhandled message: {}'.format(message))
|
|
|
|
|
|
|
|
return reply
|
|
|
|
|
|
|
|
|
|
|
|
while True:
|
|
|
|
message = getMessage()
|
|
|
|
reply = handleMessage(message)
|
2018-04-17 18:45:54 +01:00
|
|
|
sendMessage(encodeMessage(reply))
|