Created
March 8, 2012 05:16
-
-
Save penguin2716/1998867 to your computer and use it in GitHub Desktop.
MikutterSocketServerが動いてるmikutterにツイートを飛ばすmikutter大陸的なやつ
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env ruby | |
#-*- coding: utf-8 -*- | |
require 'gtk2' | |
require 'socket' | |
# mikutter_server.rb の "localhost" を "0.0.0.0" に変えると別のマシンからもアクセスできるよん | |
# ユーザ設定の読み込み | |
CONFIG = "#{ENV['HOME']}/.mikutterworldrc" | |
VALID_CONFIG = ['host', 'port', 'footer'] | |
$host = 'localhost' | |
$port = 3939 | |
$footer = '' | |
def loadUserConfig | |
if File.exists?(CONFIG) | |
f = open(CONFIG, "r") | |
f.each do |line| | |
VALID_CONFIG.each do |t| | |
if line =~ /^#{t}\s*=\s*'(.*)'/ | |
case t | |
when 'host' | |
$host = $1 | |
when 'port' | |
$port = $1.to_i | |
when 'footer' | |
$footer = $1 | |
end | |
break | |
end | |
end | |
end | |
f.close | |
end | |
end | |
def setUserConfig | |
if File.exists?(CONFIG) | |
config = "#-*- coding: utf-8 -*-\n" | |
config += "host = '#{$host}'\n" | |
config += "port = '#{$port}'\n" | |
config += "footer = '#{$footer}'\n" | |
f = open(CONFIG, "w") | |
f.write(config) | |
f.close | |
end | |
end | |
module Gtk | |
class EmacsLikeTextView < Gtk::TextView | |
# @@hist_limit : 履歴スタックの最大保存数 | |
# @@control_targetkey : Ctrlで装飾してEmacsっぽいキーバインドにするキー. | |
# 元から割り当てられていた機能は呼ばない. | |
# @@control_unselectkey : 選択トグルを自動的にOFFにするキー. | |
# @@post_history : ポスト履歴を保存するグローバルスタック | |
# @@post_history_ptr : ポスト履歴のスタックポインタ | |
# @select : 選択トグルのON/OFFを格納 | |
# @history_stack : 履歴スタック | |
# @stack_ptr : 履歴スタックポインタ | |
# @isundo : undoによる変更かどうかの確認 | |
@@hist_limit = 8000 | |
@@control_targetkey = ['A', 'space', 'g', 'f', 'b', 'n', 'p', 'a', | |
'e', 'd', 'h', 'w', 'k', 'y', 'slash', 'z'] | |
@@control_unselectkey = ['g', 'd', 'h', 'w', 'k', 'y', 'slash', 'z'] | |
@@mod1_targetkey = ['f', 'b', 'a', 'e', 'w', 'd', 'h', 'n', 'p'] | |
@@mod1_unselectkey = ['w', 'd', 'h', 'n', 'p'] | |
@@post_history = [] | |
@@post_history_ptr = 0 | |
def initialize | |
super | |
@select = false | |
@history_stack = [] | |
@history_stack.push(self.buffer.text) | |
@stack_ptr = 0 | |
@isundo = false | |
# バッファが変更されたら自動的に履歴スタックに積む | |
self.buffer.signal_connect('changed') { | |
if not @isundo then | |
@history_stack += @history_stack[@stack_ptr..-2].reverse | |
self.push_buffer | |
@stack_ptr = @history_stack.length - 1 | |
end | |
} | |
# キーバインドの追加 | |
self.signal_connect('key_press_event') { |w, e| | |
if Gdk::Window::ModifierType::CONTROL_MASK == | |
e.state & Gdk::Window::CONTROL_MASK and | |
Gdk::Keyval.to_name(e.keyval) == 'slash' then | |
@isundo = true | |
else | |
@isundo = false | |
end | |
# Mod1 による装飾 | |
if Gdk::Window::ModifierType::MOD1_MASK == | |
e.state & Gdk::Window::MOD1_MASK then | |
key = Gdk::Keyval.to_name(e.keyval) | |
# 選択トグルの解除 | |
if @@mod1_unselectkey.select{|k| k == key}.length > 0 then | |
@select = false | |
end | |
case key | |
when 'f' | |
self.move_cursor(Gtk::MOVEMENT_WORDS, 1, @select) | |
when 'b' | |
self.move_cursor(Gtk::MOVEMENT_WORDS, -1, @select) | |
when 'a' | |
self.move_cursor(Gtk::MOVEMENT_BUFFER_ENDS, -1, @select ) | |
when 'e' | |
self.move_cursor(Gtk::MOVEMENT_BUFFER_ENDS, 1, @select ) | |
when 'd' | |
delete_from_cursor(Gtk::DELETE_WORD_ENDS, 1) | |
when 'h' | |
delete_from_cursor(Gtk::DELETE_WORD_ENDS, -1) | |
when 'w' | |
self.copy_clipboard | |
self.select_all(false) | |
when 'n' | |
self.redoGlobalStack | |
when 'p' | |
self.undoGlobalStack | |
end | |
# Emacsっぽいキーバインドとして実行したら,もとから割り当てられていた機能は呼ばない | |
if @@mod1_targetkey.select{|k| k == key}.length > 0 then | |
true | |
else | |
false | |
end | |
# Control による装飾 | |
elsif Gdk::Window::ModifierType::CONTROL_MASK == | |
e.state & Gdk::Window::CONTROL_MASK then | |
key = Gdk::Keyval.to_name(e.keyval) | |
# 選択トグルの解除 | |
if @@control_unselectkey.select{|k| k == key}.length > 0 then | |
@select = false | |
end | |
case key | |
when 'A' # 全選択 | |
self.select_all(true) | |
when 'space' # 選択トグルのON/OFF | |
if @select then | |
@select = false | |
else | |
@select = true | |
end | |
when 'g' # 選択解除 | |
self.select_all(false) | |
when 'f' # 右に移動 | |
self.move_cursor(Gtk::MOVEMENT_VISUAL_POSITIONS, 1, @select) | |
when 'b' # 左に移動 | |
self.move_cursor(Gtk::MOVEMENT_VISUAL_POSITIONS, -1, @select) | |
when 'n' # 次の行に移動 | |
self.move_cursor(Gtk::MOVEMENT_DISPLAY_LINES, 1, @select) | |
when 'p' # 前の行に移動 | |
self.move_cursor(Gtk::MOVEMENT_DISPLAY_LINES, -1, @select) | |
when 'a' # 行頭へ移動 | |
self.move_cursor(Gtk::MOVEMENT_PARAGRAPH_ENDS, -1, @select) | |
when 'e' # 行末へ移動 | |
self.move_cursor(Gtk::MOVEMENT_PARAGRAPH_ENDS, 1, @select) | |
when 'd' # Deleteの挙動 | |
self.delete_from_cursor(Gtk::DELETE_CHARS, 1) | |
when 'h' # BackSpaceの挙動 | |
self.delete_from_cursor(Gtk::DELETE_CHARS, -1) | |
when 'w' # カット | |
self.cut_clipboard | |
when 'k' # 現在位置から行末までカット.行末の場合はDeleteの挙動になる | |
before = self.buffer.text | |
self.move_cursor(Gtk::MOVEMENT_PARAGRAPH_ENDS, 1, true) | |
self.cut_clipboard | |
if before == self.buffer.text then | |
self.delete_from_cursor(Gtk::DELETE_CHARS, 1) | |
end | |
when 'y' # 現在位置に貼り付け | |
self.paste_clipboard | |
when 'slash', 'z' # undoの挙動 | |
self.undo | |
end | |
# Emacsっぽいキーバインドとして実行したら,もとから割り当てられていた機能は呼ばない | |
if @@control_targetkey.select{|k| k == key}.length > 0 then | |
true | |
else | |
false | |
end | |
end | |
} | |
end | |
# 現在のバッファと最新の履歴が異なっていればスタックに現在の状態を追加 | |
def push_buffer | |
if @history_stack == nil then | |
@history_stack = [''] | |
end | |
if self.buffer.text != '' then | |
if @history_stack[-1] != self.buffer.text then | |
@history_stack.push(self.buffer.text) | |
end | |
if @history_stack.length > @@hist_limit then | |
@history_stack = @history_stack[(@history_stack.length - @@hist_limit)..-1] | |
end | |
end | |
end | |
# undoの実装.バッファの内容を変更すると自動的に履歴スタックに追加されるので, | |
# 履歴スタックに追加したら最新の履歴を捨てる | |
def undo | |
top = @history_stack[@stack_ptr] | |
if top != nil then | |
if top == self.buffer.text then | |
# 最新履歴が現在の状態と同じなら,2番目の履歴を参照 | |
decStackPtr | |
second = @history_stack[@stack_ptr] | |
if second != nil then | |
self.buffer.set_text(second) | |
else # 上から2番目が空 | |
self.buffer.set_text('') | |
end | |
else | |
self.buffer.set_text(top) | |
end | |
else # 履歴スタックが空 | |
self.buffer.set_text('') | |
end | |
end | |
def incStackPtr | |
if @history_stack.length > @stack_ptr + 1 then | |
@stack_ptr += 1 | |
end | |
end | |
def decStackPtr | |
if @stack_ptr > 0 then | |
@stack_ptr -= 1 | |
end | |
end | |
# 初期状態にリセットする.現在は使っていない | |
def reset | |
@history_stack = [] | |
@select = false | |
end | |
def pushGlobalStack | |
@@post_history_ptr = @@post_history.length | |
@@post_history.push(self.buffer.text) | |
end | |
def undoGlobalStack | |
if @@post_history != [] | |
self.buffer.set_text(@@post_history[@@post_history_ptr]) | |
@@post_history_ptr = (@@post_history_ptr - 1) % @@post_history.length | |
end | |
end | |
def redoGlobalStack | |
if @@post_history != [] | |
self.buffer.set_text(@@post_history[@@post_history_ptr]) | |
@@post_history_ptr = (@@post_history_ptr + 1) % @@post_history.length | |
end | |
end | |
end | |
end | |
loadUserConfig | |
dialog = Gtk::Dialog.new("mikutter大陸") | |
$etv = Gtk::EmacsLikeTextView.new | |
$etv.set_wrap_mode(Gtk::TextTag::WRAP_CHAR) | |
$footer_entry = Gtk::Entry.new | |
$host_entry = Gtk::Entry.new | |
$host_entry.set_width_chars(16) | |
$port_entry = Gtk::Entry.new | |
$port_entry.set_width_chars(6) | |
countLabel = Gtk::Label.new("140") | |
$etv.buffer.signal_connect('changed') { | |
countLabel.set_text( (140 - $etv.buffer.text.size - $footer_entry.text.size).to_s ) | |
} | |
$footer_entry.signal_connect('changed') { | |
countLabel.set_text( (140 - $etv.buffer.text.size - $footer_entry.text.size).to_s ) | |
} | |
$footer_entry.set_text($footer) | |
$host_entry.set_text($host) | |
$port_entry.set_text($port.to_s) | |
def saveConfig | |
$host = $host_entry.text | |
$port = $port_entry.text.to_i | |
$footer = $footer_entry.text | |
setUserConfig | |
end | |
def post(inputstr) | |
if(inputstr.length > 140) | |
inputstr = inputstr[0..139] | |
end | |
s = Socket.new(Socket::AF_INET, Socket::SOCK_STREAM, 0) | |
sockaddr = Socket.sockaddr_in($port_entry.text.to_i, $host_entry.text) | |
begin | |
s.connect(sockaddr) | |
s.write inputstr | |
s.close | |
saveConfig | |
Gtk.main_quit | |
rescue | |
$etv.buffer.set_text("error: Connection failed.\nType C-/ to recover message.") | |
end | |
end | |
dialog.signal_connect('key_press_event') { |w, e| | |
if Gdk::Keyval.to_name(e.keyval) == 'q' or | |
Gdk::Keyval.to_name(e.keyval) == 'Escape' | |
Gtk.main_quit | |
true | |
elsif Gdk::Window::ModifierType::CONTROL_MASK == | |
e.state & Gdk::Window::CONTROL_MASK and | |
Gdk::Keyval.to_name(e.keyval) == 'Return' | |
inputstr = $etv.buffer.text + $footer_entry.text | |
post(inputstr) | |
else | |
false | |
end | |
} | |
dialog.signal_connect('destroy') { | |
Gtk.main_quit | |
} | |
send_button = Gtk::Button.new("Post") | |
save_button = Gtk::Button.new("Save") | |
send_button.signal_connect('clicked') do | |
inputstr = $etv.buffer.text + $footer_entry.text | |
post(inputstr) | |
end | |
save_button.signal_connect('clicked') do | |
saveConfig | |
end | |
hbox = Gtk::HBox.new(false, 5)\ | |
.add(\ | |
Gtk::VBox.new(false, 5)\ | |
.add($etv)\ | |
.pack_start(\ | |
Gtk::HBox.new(false, 5)\ | |
.pack_start(Gtk::Label.new("footer:"), false)\ | |
.add($footer_entry), false)\ | |
.pack_start(\ | |
Gtk::HBox.new(false, 5)\ | |
.pack_start(Gtk::Label.new("host:"), false)\ | |
.add($host_entry)\ | |
.pack_start(Gtk::Label.new("port:"), false)\ | |
.add($port_entry)\ | |
.pack_start(save_button, false)\ | |
.pack_start(send_button, false)\ | |
, false)\ | |
)\ | |
.pack_start(countLabel, false) | |
dialog.vbox.add(hbox) | |
dialog.vbox.show_all | |
dialog.show | |
dialog.set_size_request(250,150) | |
Gtk.main |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment