Aug
24th
Tue
24th
それっぽく表示改(Windows対応)
去年書いたやつがWindowsで動かないからGTK+捨ててQtで書き直したよ。Ruby-GTK2おっくれてるぅー (注: Windows対応のやる気の無さがパない。もうすぐGTK+3が出ようかというのにいまだに3年半も前のGTK+2.10ベースのパッケージが最新ってのではさすがに言い訳できない。wxRubyですら同様の機能は実現可能であると調査済みなので、単にRuby-GTK2が時代から遅れている)
あと「STDINから一行づつ読んで」の部分はWindowsでは最強に実現不可能(STDINがselect()できない)なので、しょうがないのでTwitterのStreamから読む感じにしてみた。IRCからとかにしたい人は自力で弄ってね
# Copyright(c) 2010 Urabe Shyouhei. All rights Reserved.
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the code without restriction, including without limitation the rights to
# use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
# of the software, and to permit persons to whom the software is furnished to
# do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHOR OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
require 'net/http'
require 'uri'
require 'rubygems'
require 'Qt'
require 'json'
a = Qt::Application.new ARGV
FPS = 30
Font = Qt::Font.new 'Meiryo', 32, Qt::Font::Bold
Color = Qt::Color.new 255, 255, 32, 200 # rgba
Metrics = Qt::FontMetrics.new Font
Codec = Qt::TextCodec.codecForName 'UTF-8' # JSON is UTF-8.
class TwitterFilter
def initialize filter, user, pass
@chunked = false
@buf = ''
case filter
when Array
q = filter.join ','
when String
q = filter
else
q = filter.to_s
end
k = ["#{user}:#{pass}"].pack "m*"
@sock = TCPSocket.new 'stream.twitter.com', 80
str = <<-"end".gsub %r/^\t+/, ''
POST /1/statuses/filter.json?track=#{URI.escape q} HTTP/1.1
Host: stream.twitter.com
Authorization: Basic #{k}
end
@sock.write str
status = @sock.gets
ok = /200 OK/in.match status
while l = @sock.gets
status << l
case l
when /^Transfer-Encoding: *chunked/in
@chunked = true
when "\r\n"
if ok
return
else
raise status
end
end
end
end
def gets
str = if @chunked then
get_a_chunk
else
@sock.read_nonblock 32768
end
return nil unless str
return nil if str == "\r\n"
@buf << str
buf2 = Array.new
loop do
begin
obj = JSON.parse @buf
@buf.replace '' # consume
return obj
rescue JSON::ParserError
if @buf.empty?
return nil
else
@buf, tmp = @buf.split(/(?=\{[^{]*\z)/, 2)
if tmp.nil?
return nil
else
buf2 << tmp
end
end
end
end
ensure
if buf2 and not buf2.empty?
buf2.reverse!
@buf.replace buf2.join
end
end
private
def get_a_chunk
# at the beginning of this method a @sock should be in chunk head.
return nil unless IO.select [@sock], nil, nil, 0
case l = @sock.gets when /\A[0-9a-f]+\r\n\z/ni
len = $&.to_s.to_i 16
str = @sock.read len
@sock.read 2 # read empty line
return str
else
raise "not chunked #{l}"
end
end
end
class Comment < Qt::Label
def initialize str, parent
str.gsub! %r/[\r\n\s]+/um, ' '
dec = Codec.makeDecoder
qstr = dec.to_unicode str, str.length
super qstr, parent
hide
p = palette
p.set_color Qt::Palette::Foreground, Color
set_font Font
set_palette p
set_text_format Qt::PlainText
set_text_interaction_flags Qt::NoTextInteraction
set_word_wrap false
set_indent 0
set_margin 0
set_focus_policy Qt::NoFocus
set_attribute Qt::WA_DeleteOnClose
tmp = parent.geometry
h = Metrics.height
r = Metrics.bounding_rect str
# note that, for fonts such as Apple Chancety, a bounding rect can
# extend below (0, 0)
w = r.width - r.x
resize w, h
@w, @h = w, h
end
attr_reader :w, :h
def moeve h, t
# nicovideo is square, 512pixcels width in 4 secs
speed = (h / 3.0 * 4.0 + width) / 4.0 * t
newx = x - speed
move newx.to_i, y
if newx < -width
destroy true, true
true
end
end
end
class Toplevel < Qt::Widget
def initialize
super
@comments = Array.new
@lines = Array.new
@tw = TwitterFilter.new $filter, $user, $password
set_window_flags Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint
set_attribute Qt::WA_TranslucentBackground
set_attribute Qt::WA_MSWindowsUseDirect3D
set_attribute Qt::WA_TransparentForMouseEvents
start_timer 1000/FPS
show_maximized
end
def timerEvent ev
@t ||= Time.now
t1 = Time.now
t2 = t1 - @t
@t = t1
@comments.reject! {|i| i.moeve height, t2 }
@lines.map! do |i|
next nil unless i
next nil if i.x <= -i.width
i
end
while obj = @tw.gets
if obj['text']
push obj['text']
else
push obj.inspect
p obj.inspect
end
end
end
def push str
c = Comment.new str, self
h = c.height
y = height - 20 # 20 for taskbar
i = -1
n = y / h
n.times do |j|
if (not k = @lines[j]) or \
(k.y + k.width < width and \
k.width > c.width)
then
i = j
break
end
end
@comments.push c
y = if i == -1
rand height
else
@lines[i] = c
i * h
end
c.move geometry.width, y
c.show
end
end
a.set_font Font
b = Toplevel.new
a.exec
# Local Variables:
# mode: ruby
# coding: utf-8
# indent-tabs-mode: t
# tab-width: 3
# ruby-indent-level: 3
# fill-column: 79
# default-justification: full
# End:
# vi: ts=3 sw=3