Send files from one computer to another! A graphical interface for magic-wormhole
https://magic-wormhole.readthedocs.io
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
170 lines
5.1 KiB
170 lines
5.1 KiB
"""Magic-Wormhole bundled up inside a PyGtk GUI."""
|
|
|
|
import asyncio
|
|
import logging
|
|
import os
|
|
import subprocess
|
|
from pathlib import Path
|
|
from signal import SIGINT, SIGTERM
|
|
|
|
import asyncio_glib
|
|
import gi
|
|
|
|
gi.require_version("Gtk", "3.0")
|
|
gi.require_version("Gdk", "3.0")
|
|
|
|
from gi.repository import Gdk as gdk
|
|
from gi.repository import GLib as glib
|
|
from gi.repository import Gtk as gtk
|
|
|
|
asyncio.set_event_loop_policy(asyncio_glib.GLibEventLoopPolicy())
|
|
logging.basicConfig(level=os.environ.get("LOGLEVEL", "INFO"))
|
|
|
|
log = logging.getLogger("dropship")
|
|
loop = asyncio.get_event_loop()
|
|
|
|
|
|
class PendingTransfer:
|
|
"""A wormhole send waiting for a wormhole receive."""
|
|
|
|
def __init__(self, code):
|
|
"""Object initialisation."""
|
|
self.code = code
|
|
|
|
|
|
class DropShip:
|
|
"""Drag it, drop it, ship it."""
|
|
|
|
def __init__(self):
|
|
"""Object initialisation."""
|
|
self.GLADE_FILE = "dropship.glade"
|
|
self.CSS_FILE = "dropship.css"
|
|
|
|
self.DOWNLOAD_DIR = os.path.expanduser("~")
|
|
|
|
self._running = loop.create_future()
|
|
self._pending = []
|
|
|
|
self.init_glade()
|
|
self.init_css()
|
|
self.init_drop_box()
|
|
self.init_window()
|
|
|
|
def init_glade(self):
|
|
"""Initialise the GUI from Glade file."""
|
|
self.builder = gtk.Builder()
|
|
self.builder.add_from_file(self.GLADE_FILE)
|
|
self.builder.connect_signals(self)
|
|
|
|
def init_css(self):
|
|
"""Initialise CSS injection."""
|
|
self.screen = gdk.Screen.get_default()
|
|
self.provider = gtk.CssProvider()
|
|
self.provider.load_from_path(self.CSS_FILE)
|
|
gtk.StyleContext.add_provider_for_screen(
|
|
self.screen, self.provider, gtk.STYLE_PROVIDER_PRIORITY_APPLICATION
|
|
)
|
|
|
|
def init_window(self):
|
|
"""Initialise the GUI window."""
|
|
self.main_window_id = "mainWindow"
|
|
self.window = self.builder.get_object(self.main_window_id)
|
|
self.window.connect("delete-event", self.on_quit)
|
|
self.window.show()
|
|
|
|
def init_drop_box(self):
|
|
"""Initialise the drag & drop box."""
|
|
self.files_to_send = ""
|
|
|
|
# TODO(rra): check the target flags
|
|
# https://lazka.github.io/pgi-docs/Gtk-3.0/flags.html#Gtk.TargetFlags
|
|
self.enforce_target = gtk.TargetEntry.new("text/plain", gtk.TargetFlags(4), 129)
|
|
|
|
self.drop_box_id = "dropBox"
|
|
self.drop_box_label = "dropLabel"
|
|
|
|
self.drop_box = self.builder.get_object(self.drop_box_id)
|
|
self.drop_box.drag_dest_set(
|
|
gtk.DestDefaults.ALL, [self.enforce_target], gdk.DragAction.COPY
|
|
)
|
|
self.drop_box.connect("drag-data-received", self.on_drop)
|
|
self.drop_label = self.builder.get_object(self.drop_box_label)
|
|
|
|
def on_drop(self, widget, drag_context, x, y, data, info, time):
|
|
files = data.get_text().split()
|
|
self.files_to_send = files
|
|
if len(files) == 1:
|
|
fpath = Path(files[0].replace("file://", ""))
|
|
self.schedule(self.wormhole_send(self, fpath))
|
|
self.drop_label.set_text("Sending..")
|
|
else:
|
|
log.info("Multiple file sending coming soon ™")
|
|
|
|
def on_quit(self, *args, **kwargs):
|
|
"""Quit the program."""
|
|
self.window.close()
|
|
self._running.set_result(None)
|
|
|
|
def schedule(self, function):
|
|
"""Schedule a task on the event loop."""
|
|
loop.call_soon_threadsafe(asyncio.ensure_future, function)
|
|
|
|
async def read_lines(self, stream, pattern):
|
|
"""Read stdout from a command and match lines."""
|
|
# TODO(decentral1se): if pattern doesnt match, trapped forever
|
|
while True:
|
|
line = await stream.readline()
|
|
decoded = line.decode("utf-8").strip()
|
|
if pattern in decoded:
|
|
return decoded
|
|
|
|
async def wormhole_send(self, widget, fpath):
|
|
"""Run `wormhole send` on a local file path."""
|
|
process = await asyncio.create_subprocess_exec(
|
|
"wormhole",
|
|
"send",
|
|
fpath,
|
|
stdout=asyncio.subprocess.PIPE,
|
|
stderr=asyncio.subprocess.PIPE,
|
|
)
|
|
|
|
line = await self.read_lines(process.stderr, "wormhole receive")
|
|
code = line.split()[-1]
|
|
self.drop_label.set_text(code)
|
|
|
|
self._pending.append(PendingTransfer(code))
|
|
|
|
await process.wait()
|
|
|
|
|
|
async def wormhole_recv(self, widget, code):
|
|
"""Run `wormhole receive` with a pending transfer code."""
|
|
process = await asyncio.create_subprocess_exec(
|
|
"wormhole",
|
|
"receive",
|
|
"--accept-file",
|
|
"--hide-progress",
|
|
code,
|
|
stdout=asyncio.subprocess.PIPE,
|
|
stderr=asyncio.subprocess.PIPE,
|
|
)
|
|
await process.wait()
|
|
|
|
|
|
async def main():
|
|
"""The application entrypoint."""
|
|
try:
|
|
dropship = DropShip()
|
|
await dropship._running
|
|
except asyncio.CancelledError:
|
|
pass
|
|
|
|
|
|
if __name__ == "__main__":
|
|
try:
|
|
main_task = asyncio.ensure_future(main())
|
|
loop.add_signal_handler(SIGINT, main_task.cancel)
|
|
loop.add_signal_handler(SIGTERM, main_task.cancel)
|
|
loop.run_until_complete(main_task)
|
|
finally:
|
|
loop.close()
|
|
|