15821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#!/usr/bin/env python
25821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# Copyright (c) 2012 The Chromium Authors. All rights reserved.
35821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# Use of this source code is governed by a BSD-style license that can be
45821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# found in the LICENSE file.
55821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
64e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)# A simple native messaging host. Shows a Tkinter dialog with incoming messages
74e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)# that also allows to send message back to the webapp.
85821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
95821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import struct
104e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)import sys
114e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)import threading
124e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)import Queue
135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
144e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)try:
154e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)  import Tkinter
164e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)  import tkMessageBox
174e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)except ImportError:
184e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)  Tkinter = None
194e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)
204e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)# Helper function that sends a message to the webapp.
214e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)def send_message(message):
224e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)   # Write message size.
234e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)  sys.stdout.write(struct.pack('I', len(message)))
244e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)  # Write the message itself.
254e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)  sys.stdout.write(message)
264e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)  sys.stdout.flush()
275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
284e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)# Thread that reads messages from the webapp.
294e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)def read_thread_func(queue):
304e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)  message_number = 0
315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  while 1:
325d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    # Read the message length (first 4 bytes).
332a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    text_length_bytes = sys.stdin.read(4)
345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
352a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    if len(text_length_bytes) == 0:
364e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)      if queue:
374e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)        queue.put(None)
384e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)      sys.exit(0)
395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
405d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    # Unpack message length as 4 byte integer.
412a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    text_length = struct.unpack('i', text_length_bytes)[0]
425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # Read the text (JSON object) of the message.
445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    text = sys.stdin.read(text_length).decode('utf-8')
455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
464e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)    if queue:
474e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)      queue.put(text)
484e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)    else:
494e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)      # In headless mode just send an echo message back.
504e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)      send_message('{"echo": %s}' % text)
514e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)
524e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)if Tkinter:
534e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)  class NativeMessagingWindow(Tkinter.Frame):
544e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)    def __init__(self, queue):
554e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)      self.queue = queue
564e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)
574e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)      Tkinter.Frame.__init__(self)
584e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)      self.pack()
594e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)
604e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)      self.text = Tkinter.Text(self)
614e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)      self.text.grid(row=0, column=0, padx=10, pady=10, columnspan=2)
624e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)      self.text.config(state=Tkinter.DISABLED, height=10, width=40)
634e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)
644e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)      self.messageContent = Tkinter.StringVar()
654e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)      self.sendEntry = Tkinter.Entry(self, textvariable=self.messageContent)
664e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)      self.sendEntry.grid(row=1, column=0, padx=10, pady=10)
674e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)
684e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)      self.sendButton = Tkinter.Button(self, text="Send", command=self.onSend)
694e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)      self.sendButton.grid(row=1, column=1, padx=10, pady=10)
704e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)
714e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)      self.after(100, self.processMessages)
724e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)
734e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)    def processMessages(self):
744e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)      while not self.queue.empty():
754e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)        message = self.queue.get_nowait()
764e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)        if message == None:
774e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)          self.quit()
784e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)          return
794e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)        self.log("Received %s" % message)
804e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)
814e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)      self.after(100, self.processMessages)
824e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)
834e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)    def onSend(self):
844e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)      text = '{"text": "' + self.messageContent.get() + '"}'
854e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)      self.log('Sending %s' % text)
864e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)      try:
874e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)        send_message(text)
884e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)      except IOError:
894e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)        tkMessageBox.showinfo('Native Messaging Example',
904e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)                              'Failed to send message.')
914e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)        sys.exit(1)
924e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)
934e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)    def log(self, message):
944e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)      self.text.config(state=Tkinter.NORMAL)
954e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)      self.text.insert(Tkinter.END, message + "\n")
964e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)      self.text.config(state=Tkinter.DISABLED)
974e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)
984e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)
994e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)def Main():
1004e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)  if not Tkinter:
1014e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)    send_message('"Tkinter python module wasn\'t found. Running in headless ' +
1024e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)                 'mode. Please consider installing Tkinter."')
1034e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)    read_thread_func(None)
1044e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)    sys.exit(0)
1054e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)
1064e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)  queue = Queue.Queue()
1074e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)
1084e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)  main_window = NativeMessagingWindow(queue)
1094e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)  main_window.master.title('Native Messaging Example')
1104e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)
1114e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)  thread = threading.Thread(target=read_thread_func, args=(queue,))
1124e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)  thread.daemon = True
1134e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)  thread.start()
1144e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)
1154e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)  main_window.mainloop()
1165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1174e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)  sys.exit(0)
1185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)if __name__ == '__main__':
1215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  Main()
122