#!/usr/bin/env ruby require 'socket' require 'io/console' require 'optparse' PUBLIC_HOST = '0.0.0.0' PRIVATE_HOST = '127.0.0.1' DEFAULT_CLIENT_HOST = '127.0.0.1' options = { :port => 7570, :server => false, :host => DEFAULT_CLIENT_HOST, :server_host => PRIVATE_HOST, :rows => 24, :cols => 40, } OptionParser.new do |opt| opt.on('-pPORT', '--port PORT', Integer, 'Port to serve/listen on (defaults to 7570)') do |o| options[:port] = o end opt.on('-S', '--server', 'Make this instance function as a server.' ) do options[:server] = true end opt.on('-s', '--client', 'Make this instance function as a client' ) do options[:server] = false end opt.on('-HHOST', '--host HOST', String, '(client only) Host to connect to (defaults to `localhost`)') do |o| options[:host] = o end opt.on('-rROWS', '--rows ROWS', Integer, '(server only) The number of rows to have in the shared buffer' ) do |o| options[:rows] = o end opt.on('-cCOLS', '--cols COLS', Integer, '(server only) The number of columns to have in the shared buffer' ) do |o| options[:cols] = o end opt.on('-X', '--public', # X -- eXposed '(server only) Serve publicly, rather than privately' ) do options[:server_host] = PUBLIC_HOST end opt.on('-x', '--private', # x -- not visible '(server only) Serve privately, rather than publicly (default)' ) do options[:server_host] = PRIVATE_HOST end opt.on('-h', '--help', 'Get help with this command') do puts opt exit end end.parse!(into: options) HOST = options[:host] SRV_HOST = options[:server_host] PORT = options[:port] ROWS = options[:rows] COLS = options[:cols] VERSION = 0.01 def do_server server = TCPServer.new(SRV_HOST, PORT) sockets = [server] puts "PORT: #{PORT}" puts "GRID SIZE: #{ROWS}r#{COLS}c" puts 'Listening...' pre = "#{ROWS}r#{COLS}c >>> " buffer = pre.ljust(ROWS*COLS)[0...ROWS*COLS] windex = pre.length # Wipe your buffer clean with your write index windex %= buffer.length def new_buf_wi_out(buf, wi, newtext) buf = buf.dup out = '' nti = 0 # New Text Index while nti < newtext.length out << "#{VERSION}|" out << ((wi/COLS)+1).to_s + '|' out << ((wi%COLS)+1).to_s + '|' loop do out << newtext[nti] buf[wi] = newtext[nti] wi += 1 nti += 1 break if nti >= newtext.length break if wi%COLS == 0 end wi = 0 if wi >= (ROWS*COLS) wi %= ROWS*COLS out << "\n" end [buf, wi, out] # << Implicit return end loop do ready, = IO.select(sockets) newtext = '' ready.each do |sck| if sck == server client = server.accept sockets << client puts "Connection: #{client.peeraddr}" client.puts new_buf_wi_out(buffer,0,buffer)[2] if buffer.length > 1 client.puts new_buf_wi_out(buffer,windex-1,buffer[windex-1])[2] end next end begin msg = sck.gets unless msg puts "Disconnect: #{sck.peeraddr}" sck.close sockets.delete(sck) next end ver, msg = msg.chomp.split('|',2) next unless ver.to_f <= VERSION next unless msg != nil newtext << msg rescue Errno::ECONNRESET, Errno::EPIPE puts "Disconnect (err): #{sck.peeraddr}" sck.close sockets.delete(sck) end buffer, windex, out = new_buf_wi_out(buffer,windex,newtext) clients = sockets - [server] clients.each { |c| c.puts out } #if out != '' # puts "#{windex}: (#{(windex/COLS)+1},#{(windex%COLS)+1})" #end end end end def do_client socket = TCPSocket.new(HOST, PORT) puts "PORT: #{PORT}" puts "HOST: #{HOST}" begin print "\x1b[?1049h\x1b[2J\x1b[3J" # Open and clear alternate buffer #reader = Thread.new do Thread.new do loop do msg = socket.gets exit 0 unless msg ver, msg = msg.split('|',2) next unless ver.to_f <= VERSION next unless msg != nil # In message version 0.01 and lower, # all messages are in an assumed format... row,col,chars = msg.split('|',3) print "\x1b[#{row};#{col}H#{chars}\x1b[A" end end while (char = STDIN.noecho(&:getch)) exit unless char != "\x04" # ^D if char.ord < 0x20 # Non printing char = '^'+(char.ord + 0x40).chr elsif char.ord == 0x7F # DEL (Non print) char = '^?' end next unless socket.puts "#{VERSION}|#{char}" end ensure print "\x1b[?1049l" # Back to regular buffer end end if options[:server] do_server else do_client end