1 #!/usr/bin/env python 2 # Copyright (c) 2012 The Chromium Authors. All rights reserved. 3 # Use of this source code is governed by a BSD-style license that can be 4 # found in the LICENSE file. 5 6 # A simple native messaging host. Shows a Tkinter dialog with incoming messages 7 # that also allows to send message back to the webapp. 8 9 import struct 10 import sys 11 import threading 12 import Queue 13 14 try: 15 import Tkinter 16 import tkMessageBox 17 except ImportError: 18 Tkinter = None 19 20 # Helper function that sends a message to the webapp. 21 def send_message(message): 22 # Write message size. 23 sys.stdout.write(struct.pack('I', len(message))) 24 # Write the message itself. 25 sys.stdout.write(message) 26 sys.stdout.flush() 27 28 # Thread that reads messages from the webapp. 29 def read_thread_func(queue): 30 message_number = 0 31 while 1: 32 # Read the message length (first 4 bytes). 33 text_length_bytes = sys.stdin.read(4) 34 35 if len(text_length_bytes) == 0: 36 if queue: 37 queue.put(None) 38 sys.exit(0) 39 40 # Unpack message length as 4 byte integer. 41 text_length = struct.unpack('i', text_length_bytes)[0] 42 43 # Read the text (JSON object) of the message. 44 text = sys.stdin.read(text_length).decode('utf-8') 45 46 if queue: 47 queue.put(text) 48 else: 49 # In headless mode just send an echo message back. 50 send_message('{"echo": %s}' % text) 51 52 if Tkinter: 53 class NativeMessagingWindow(Tkinter.Frame): 54 def __init__(self, queue): 55 self.queue = queue 56 57 Tkinter.Frame.__init__(self) 58 self.pack() 59 60 self.text = Tkinter.Text(self) 61 self.text.grid(row=0, column=0, padx=10, pady=10, columnspan=2) 62 self.text.config(state=Tkinter.DISABLED, height=10, width=40) 63 64 self.messageContent = Tkinter.StringVar() 65 self.sendEntry = Tkinter.Entry(self, textvariable=self.messageContent) 66 self.sendEntry.grid(row=1, column=0, padx=10, pady=10) 67 68 self.sendButton = Tkinter.Button(self, text="Send", command=self.onSend) 69 self.sendButton.grid(row=1, column=1, padx=10, pady=10) 70 71 self.after(100, self.processMessages) 72 73 def processMessages(self): 74 while not self.queue.empty(): 75 message = self.queue.get_nowait() 76 if message == None: 77 self.quit() 78 return 79 self.log("Received %s" % message) 80 81 self.after(100, self.processMessages) 82 83 def onSend(self): 84 text = '{"text": "' + self.messageContent.get() + '"}' 85 self.log('Sending %s' % text) 86 try: 87 send_message(text) 88 except IOError: 89 tkMessageBox.showinfo('Native Messaging Example', 90 'Failed to send message.') 91 sys.exit(1) 92 93 def log(self, message): 94 self.text.config(state=Tkinter.NORMAL) 95 self.text.insert(Tkinter.END, message + "\n") 96 self.text.config(state=Tkinter.DISABLED) 97 98 99 def Main(): 100 if not Tkinter: 101 send_message('"Tkinter python module wasn\'t found. Running in headless ' + 102 'mode. Please consider installing Tkinter."') 103 read_thread_func(None) 104 sys.exit(0) 105 106 queue = Queue.Queue() 107 108 main_window = NativeMessagingWindow(queue) 109 main_window.master.title('Native Messaging Example') 110 111 thread = threading.Thread(target=read_thread_func, args=(queue,)) 112 thread.daemon = True 113 thread.start() 114 115 main_window.mainloop() 116 117 sys.exit(0) 118 119 120 if __name__ == '__main__': 121 Main() 122