Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 48 additions & 1 deletion resources/main-window.ui
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@
<property name="can-focus">False</property>
<property name="icon-name">xsi-window-close-symbolic</property>
</object>
<object class="GtkImage" id="image4">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="icon-name">xsi-mail-send</property>
</object>
<object class="GtkImage" id="user_favorite_image">
<property name="visible">True</property>
<property name="can-focus">False</property>
Expand Down Expand Up @@ -700,9 +705,51 @@
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkBox" id="user_msg_box">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="spacing">6</property>
<child>
<object class="GtkScrolledWindow">
<property name="height-request">40</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="hscrollbar-policy">never</property>
<property name="vscrollbar-policy">external</property>
<property name="shadow-type">out</property>
<child>
<object class="GtkTextView" id="user_msg_entry">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="wrap-mode">word-char</property>
</object>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkButton" id="user_send_msg_button">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">False</property>
<property name="image">image4</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
Expand Down
47 changes: 47 additions & 0 deletions resources/op-item.ui
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,12 @@
<property name="halign">center</property>
<property name="icon_name">xsi-list-remove-symbolic</property>
</object>
<object class="GtkImage" id="image11">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">center</property>
<property name="icon_name">xsi-edit-copy-symbolic</property>
</object>
<object class="GtkBox" id="op_item">
<property name="visible">True</property>
<property name="can_focus">False</property>
Expand Down Expand Up @@ -230,6 +236,32 @@
<property name="position">2</property>
</packing>
</child>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkLabel" id="op_transfer_text_message">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="selectable">True</property>
<property name="wrap">True</property>
<property name="wrap-mode">word-char</property>
<property name="max_width_chars">80</property>
<property name="xalign">0</property>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
</object>
<packing>
<property name="name">text-message</property>
</packing>
</child>
</object>
<packing>
<property name="expand">True</property>
Expand Down Expand Up @@ -386,6 +418,21 @@
<property name="position">8</property>
</packing>
</child>
<child>
<object class="GtkButton" id="transfer_copy_message">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="tooltip_text" translatable="yes">Copy message</property>
<property name="valign">center</property>
<property name="image">image11</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">9</property>
</packing>
</child>
<style>
<class name="linked"/>
</style>
Expand Down
31 changes: 31 additions & 0 deletions src/notifications.py
Original file line number Diff line number Diff line change
Expand Up @@ -224,3 +224,34 @@ def _notification_response(self, action, variant, op):

app = Gio.Application.get_default()
app.lookup_action("notification-response").disconnect_by_func(self._notification_response)

class TextMessageNotification():
def __init__(self, op):
self.op = op
self.send_notification()

@misc._idle
def send_notification(self):
if prefs.get_show_notifications():
notification = Gio.Notification.new(_("New message from %s") % self.op.sender_name)
notification.set_body(self.op.message)
notification.set_icon(Gio.ThemedIcon(name="org.x.Warpinator-symbolic"))
notification.set_priority(Gio.NotificationPriority.URGENT)

notification.add_button(_("Copy"), "app.notification-response::copy")
notification.set_default_action("app.notification-response::focus")

app = Gio.Application.get_default()
app.lookup_action("notification-response").connect("activate", self._notification_response, self.op)
app.send_notification(self.op.sender, notification)

def _notification_response(self, action, variant, op):
response = variant.unpack()

if response == "copy":
op.copy_message()
else:
op.focus()

app = Gio.Application.get_default()
app.lookup_action("notification-response").disconnect_by_func(self._notification_response)
22 changes: 21 additions & 1 deletion src/ops.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import logging
from pathlib import Path

from gi.repository import GObject, GLib, Gio
from gi.repository import GObject, GLib, Gio, Gtk, Gdk

import grpc

Expand Down Expand Up @@ -283,3 +283,23 @@ def stop_transfer(self):
def remove_transfer(self):
self.emit("op-command", OpCommand.REMOVE_TRANSFER)

class TextMessageOp(CommonOp):
message = None

def __init__(self, direction, sender):
super(TextMessageOp, self).__init__(direction, sender)
self.gicon = Gio.ThemedIcon.new("xsi-mail-message-new-symbolic")
self.description = _("Text message")

def copy_message(self):
cb = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD)
cb.set_text(self.message, -1)

def send_notification(self):
notifications.TextMessageNotification(self)

def remove_transfer(self):
self.emit("op-command", OpCommand.REMOVE_TRANSFER)

def retry_transfer(self):
self.emit("op-command", OpCommand.RETRY_TRANSFER)
33 changes: 28 additions & 5 deletions src/remote.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@
import misc
import transfers
import auth
from ops import SendOp, ReceiveOp
from util import TransferDirection, OpStatus, OpCommand, RemoteStatus, ReceiveError
from ops import SendOp, ReceiveOp, TextMessageOp
from util import TransferDirection, OpStatus, OpCommand, RemoteStatus, ReceiveError, RemoteFeatures

_ = gettext.gettext

Expand Down Expand Up @@ -56,6 +56,7 @@ def __init__(self, ident, hostname, display_hostname, ip_info, port, local_ident
self.display_name = ""
self.favorite = prefs.get_is_favorite(self.ident)
self.recent_time = 0 # Keep monotonic time when visited on the user page
self.supports_messages = False

self.avatar_surface = None
self.transfer_ops = []
Expand Down Expand Up @@ -366,6 +367,8 @@ def get_info_finished(future):
info = future.result()
self.display_name = info.display_name
self.user_name = info.user_name
feature_flags = RemoteFeatures(info.feature_flags)
self.supports_messages = RemoteFeatures.TEXT_MESSAGES in feature_flags
self.favorite = prefs.get_is_favorite(self.ident)

valid = GLib.utf8_make_valid(self.display_name, -1)
Expand Down Expand Up @@ -590,6 +593,21 @@ def _send_files(uri_list):
util.add_to_recents_if_single_selection(uri_list)
self.rpc_call(_send_files, uri_list)

def send_text_message(self, message):
op = TextMessageOp(TransferDirection.TO_REMOTE_MACHINE, self.local_ident)
op.message = message
op.status = OpStatus.FINISHED
self.add_op(op)
self.rpc_call(self.do_send_text_message, op)

def do_send_text_message(self, op):
try:
self.stub.SendTextMessage(warp_pb2.TextMessage(ident=self.local_ident, timestamp=op.start_time, message=op.message))
except Exception as e:
logging.error("Sending message failed: %s" % e)
op.status = OpStatus.FAILED
op.emit_status_changed()

@misc._idle
def add_op(self, op):
if op not in self.transfer_ops:
Expand All @@ -600,7 +618,7 @@ def add_op(self, op):
if isinstance(op, SendOp):
op.connect("initial-setup-complete", self.notify_remote_machine_of_new_op)
self.emit("new-outgoing-op", op)
if isinstance(op, ReceiveOp):
if isinstance(op, (ReceiveOp, TextMessageOp)):
self.emit("new-incoming-op", op)

def set_busy():
Expand Down Expand Up @@ -662,8 +680,13 @@ def op_command_issued(self, op, command):
elif command == OpCommand.STOP_TRANSFER_BY_SENDER:
self.rpc_call(self.stop_transfer_op, op, by_sender=True)
elif command == OpCommand.RETRY_TRANSFER:
op.set_status(OpStatus.WAITING_PERMISSION)
self.rpc_call(self.send_transfer_op_request, op)
if isinstance(op, TextMessageOp):
op.status = OpStatus.FINISHED
op.emit_status_changed()
self.rpc_call(self.do_send_text_message, op)
else:
op.set_status(OpStatus.WAITING_PERMISSION)
self.rpc_call(self.send_transfer_op_request, op)
elif command == OpCommand.REMOVE_TRANSFER:
self.remove_op(op)
# receive
Expand Down
26 changes: 23 additions & 3 deletions src/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@
import util
import misc
import transfers
from ops import ReceiveOp
from util import TransferDirection, OpStatus, RemoteStatus
from ops import ReceiveOp, TextMessageOp
from util import TransferDirection, OpStatus, RemoteStatus, RemoteFeatures

import zeroconf
from zeroconf import ServiceInfo, Zeroconf, ServiceBrowser, IPVersion
Expand All @@ -41,6 +41,8 @@

SERVICE_TYPE = "_warpinator._tcp.local."

SERVER_FEATURES = RemoteFeatures.TEXT_MESSAGES

# server (this is on a separate thread from the ui, grpc isn't compatible with
# gmainloop)
class Server(threading.Thread, warp_pb2_grpc.WarpServicer, GObject.Object):
Expand Down Expand Up @@ -559,7 +561,8 @@ def GetRemoteMachineInfo(self, request, context):
logging.debug("Server RPC: GetRemoteMachineInfo from '%s'" % request.readable_name)

return warp_pb2.RemoteMachineInfo(display_name=GLib.get_real_name(),
user_name=GLib.get_user_name())
user_name=GLib.get_user_name(),
feature_flags=SERVER_FEATURES)

def GetRemoteMachineAvatar(self, request, context):
logging.debug("Server RPC: GetRemoteMachineAvatar from '%s'" % request.readable_name)
Expand Down Expand Up @@ -712,3 +715,20 @@ def StopTransfer(self, request, context):
op.set_status(OpStatus.FAILED)

return void

def SendTextMessage(self, request, context):
logging.debug("Server RPC: SendTextMessage from '%s'" % request.ident)
try:
remote_machine:remote.RemoteMachine = self.remote_machines[request.ident]
except KeyError as e:
logging.warning("Received text message from unknown remote: %s" % e)
return

op = TextMessageOp(TransferDirection.FROM_REMOTE_MACHINE, request.ident)
op.sender_name = remote_machine.display_name
op.message = request.message
op.status = OpStatus.FINISHED
remote_machine.add_op(op)
op.send_notification()

return void
5 changes: 4 additions & 1 deletion src/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ def shutdown(self, wait=True):
self._factory_thread.join()
logging.debug("NewThreadExecutor: Shutdown complete")

from enum import IntEnum
from enum import IntEnum, IntFlag
TransferDirection = IntEnum('TransferDirection', 'TO_REMOTE_MACHINE \
FROM_REMOTE_MACHINE')

Expand Down Expand Up @@ -192,6 +192,9 @@ def shutdown(self, wait=True):
CERT_UP_TO_DATE \
FAILURE')

class RemoteFeatures(IntFlag):
TEXT_MESSAGES = 1 << 0

class ReceiveError(Exception):
def __init__(self, message, fatal=True):
self.fatal = fatal
Expand Down
7 changes: 7 additions & 0 deletions src/warp.proto
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ service Warp {
rpc GetRemoteMachineAvatar(LookupName) returns (stream RemoteMachineAvatar) {}
rpc ProcessTransferOpRequest(TransferOpRequest) returns (VoidType) {}
rpc PauseTransferOp(OpInfo) returns (VoidType) {}
rpc SendTextMessage(TextMessage) returns (VoidType) {}

// Receiver methods
rpc StartTransfer(OpInfo) returns (stream FileChunk) {}
Expand All @@ -32,6 +33,7 @@ service Warp {
message RemoteMachineInfo {
string display_name = 1;
string user_name = 2;
uint32 feature_flags = 3;
}

message RemoteMachineAvatar {
Expand Down Expand Up @@ -114,3 +116,8 @@ message ServiceRegistration {
string ipv6 = 7;
}

message TextMessage {
string ident = 1;
uint64 timestamp = 2;
string message = 3;
}
Loading
Loading