ninix-aya-5.0.9/0000755000175000017500000000000013416507466011626 5ustar shyshyninix-aya-5.0.9/lib/0000755000175000017500000000000013416507430012363 5ustar shyshyninix-aya-5.0.9/lib/ninix_main.rb0000644000175000017500000014235013416507430015046 0ustar shyshy# -*- coding: utf-8 -*- # # Copyright (C) 2001, 2002 by Tamito KAJIYAMA # Copyright (C) 2002, 2003 by MATSUMURA Namihiko # Copyright (C) 2002-2019 by Shyouzou Sugitani # Copyright (C) 2003-2005 by Shun-ichi TAHARA # # This program is free software; you can redistribute it and/or modify it # under the terms of the GNU General Public License (version 2) as # published by the Free Software Foundation. It is distributed in the # hope that it will be useful, but WITHOUT ANY WARRANTY; without even the # implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR # PURPOSE. See the GNU General Public License for more details. # require 'optparse' require 'uri' require 'gettext' require "gtk3" require_relative "ninix/pix" require_relative "ninix/home" require_relative "ninix/prefs" require_relative "ninix/sakura" require_relative "ninix/sstp" require_relative "ninix/communicate" require_relative "ninix/ngm" require_relative "ninix/lock" require_relative "ninix/install" require_relative "ninix/nekodorif" require_relative "ninix/kinoko" require_relative "ninix/menu" require_relative "ninix/metamagic" require_relative "ninix/logging" module Ninix_Main include GetText bindtextdomain("ninix-aya") def self.handleException(exception) message = ("Uncaught exception (#{exception.class})\n" + exception.backtrace.join("\n")) Logging::Logging.error(message) response_id = 1 dialog = Gtk::MessageDialog.new( :parent => nil, :flags => 0, :type => Gtk::MessageType::ERROR, :buttons => Gtk::ButtonsType::NONE, :message => _("A ninix-aya error has been detected.")) dialog.set_title(_("Bug Detected")) dialog.set_window_position(Gtk::WindowPosition::CENTER) dialog.gravity = Gdk::Gravity::CENTER button = dialog.add_button(_("Show Details"), response_id) dialog.add_button("_Close", Gtk::ResponseType::CLOSE) textview = Gtk::TextView.new textview.set_editable(false) scrn = Gdk::Screen.default width = (scrn.width / 2).to_i height = (scrn.height / 4).to_i textview.set_size_request(width, height) textview.show() sw = Gtk::ScrolledWindow.new sw.show() sw.set_policy(Gtk::PolicyType::AUTOMATIC, Gtk::PolicyType::AUTOMATIC) sw.add(textview) frame = Gtk::Frame.new frame.set_shadow_type(Gtk::ShadowType::IN) frame.add(sw) frame.set_border_width(7) frame.set_size_request(480, 320) # XXX content_area = dialog.content_area content_area.add(frame) textbuffer = textview.buffer textbuffer.set_text(message) while true break unless dialog.run() == response_id # close button frame.show() button.set_sensitive(false) end dialog.destroy() fail SystemExit end class SSTPControler < MetaMagic::Holon def initialize(sstp_port) super("") ## FIXME @sstp_port = sstp_port @sstp_servers = [] @__sstp_queue = [] @__sstp_flag = false @__current_sender = nil end def handle_request(event_type, event, *arglist) fail "assert" unless ['GET', 'NOTIFY'].include?(event_type) handlers = {} unless handlers.include?(event) if SSTPControler.method_defined?(event) result = method(event).call(*arglist) else result = @parent.handle_request( event_type, event, *arglist) end else result = method(handlers[event]).call(*arglist) end return result if event_type == 'GET' end def enqueue_request(event, script_odict, sender, handle, address, show_sstp_marker, use_translator, entry_db, request_handler) @__sstp_queue << [event, script_odict, sender, handle, address, show_sstp_marker, use_translator, entry_db, request_handler] end def check_request_queue(sender) count = 0 for request in @__sstp_queue if request[2].split(' / ', 2)[0] == sender.split(' / ', 2)[0] count += 1 end end if @__sstp_flag and \ @__current_sender.split(' / ', 2)[0] == sender.split(' / ', 2)[0] count += 1 end return count.to_s, @__sstp_queue.length.to_s end def set_sstp_flag(sender) @__sstp_flag = true @__current_sender = sender end def reset_sstp_flag @__sstp_flag = false @__current_sender = nil end def handle_sstp_queue return if @__sstp_flag or @__sstp_queue.empty? event, script_odict, sender, handle, address, \ show_sstp_marker, use_translator, \ entry_db, request_handler = @__sstp_queue.shift working = (not event.nil?) break_flag = false for if_ghost in script_odict.keys() if not if_ghost.empty? and @parent.handle_request('GET', 'if_ghost', if_ghost, :working => working) @parent.handle_request('NOTIFY', 'select_current_sakura', :ifghost => if_ghost) default_script = script_odict[if_ghost] break_flag = true break end end unless break_flag if @parent.handle_request('GET', 'get_preference', 'allowembryo').zero? if event.nil? request_handler.send_response(420) unless request_handler.nil? # Refuse return else default_script = nil end else if script_odict.include?('') # XXX default_script = script_odict[''] else default_script = script_odict.values[0] end end end unless event.nil? script = @parent.handle_request('GET', 'get_event_response', event) else script = nil end if script.nil? script = default_script end if script.nil? request_handler.send_response(204) unless request_handler.nil? # No Content return end set_sstp_flag(sender) @parent.handle_request( 'NOTIFY', 'enqueue_script', event, script, sender, handle, address, show_sstp_marker, use_translator, :db => entry_db, :request_handler => request_handler, :temp_mode => true) end def receive_sstp_request for sstp_server in @sstp_servers begin socket = sstp_server.accept_nonblock rescue next end begin handler = SSTP::SSTPRequestHandler.new(sstp_server, socket) buffer = socket.gets handler.handle(buffer) rescue SocketError => e Logging::Logging.error("socket.error: #{e.message}") rescue SystemCallError => e Logging::Logging.error("socket.error: #{e.message} (#{e.errno})") rescue # may happen when ninix is terminated return end end end def get_sstp_port return nil if @sstp_servers.empty? return @sstp_servers[0].server_address[1] end def quit for server in @sstp_servers server.close() end end def start_servers for port in @sstp_port begin server = SSTP::SSTPServer.new(port) rescue SystemCallError => e Logging::Logging.warning("Port #{port}: #{e.message} (ignored)") next end server.set_responsible(self) @sstp_servers << server Logging::Logging.info("Serving SSTP on port #{port}") end end end class BalloonMeme < MetaMagic::Meme def create_menuitem(data) desc, balloon = data subdir = balloon['balloon_dir'][0] name = desc.get('name', :default => subdir) home_dir = Home.get_ninix_home() thumbnail_path = File.join(home_dir, 'balloon', subdir, 'thumbnail.png') thumbnail_path = nil unless File.exists?(thumbnail_path) return handle_request( 'GET', 'create_balloon_menuitem', name, @key, thumbnail_path) end def delete_by_myself handle_request('NOTIFY', 'delete_balloon', @key) end end class Ghost < MetaMagic::Holon def create_menuitem(data) @parent.handle_request('GET', 'create_menuitem', @key, data) end def delete_by_myself @parent.handle_request('NOTIFY', 'delete_ghost', @key) end def create_instance(data) @parent.handle_request('GET', 'create_ghost', data) end end class Application def initialize(lockfile, sstp_port: [9801, 11000]) @lockfile = lockfile @abend = nil @loaded = false @confirmed = false @console = Console.new(self) Logging::Logging.info("loading...") # create preference dialog @prefs = Prefs::PreferenceDialog.new @prefs.set_responsible(self) @sstp_controler = SSTPControler.new(sstp_port) @sstp_controler.set_responsible(self) # create usage dialog @usage_dialog = UsageDialog.new @communicate = Communicate::Communicate.new # create ghost manager @__ngm = NGM::NGM.new @__ngm.set_responsible(self) @current_sakura = nil # create installer @installer = Install::Installer.new # create popup menu @__menu = Menu::Menu.new @__menu.set_responsible(self) @__menu_owner = nil @ghosts = {} # Ordered Hash odict_baseinfo = Home.search_ghosts() for key, value in odict_baseinfo holon = Ghost.new(key) holon.set_responsible(self) @ghosts[key] = holon holon.baseinfo = value end @balloons = {} # Ordered Hash odict_baseinfo = Home.search_balloons() for key, value in odict_baseinfo meme = BalloonMeme.new(key) meme.set_responsible(self) @balloons[key] = meme meme.baseinfo = value end @balloon_menu = create_balloon_menu() @nekoninni = Home.search_nekoninni() @katochan = Home.search_katochan() @kinoko = Home.search_kinoko() Logging::Logging.info("done.") end def edit_preferences(*arglist) @prefs.edit_preferences(*arglist) end def prefs_get(*arglist) @prefs.get(*arglist) end def get_otherghostname(*arglist) @communicate.get_otherghostname(*arglist) end def rebuild_ghostdb(*arglist) @communicate.rebuild_ghostdb(*arglist) end def notify_other(*arglist) @communicate.notify_other(*arglist) end def reset_sstp_flag(*arglist) @sstp_controler.reset_sstp_flag(*arglist) end def get_sstp_port(*arglist) @sstp_controler.get_sstp_port(*arglist) end def handle_request(event_type, event, *arglist) fail "assert" unless ['GET', 'NOTIFY'].include?(event_type) handlers = { 'close_all' => 'close_all_ghosts', 'edit_preferences' => 'edit_preferences', 'get_preference' => 'prefs_get', 'get_otherghostname' => 'get_otherghostname', 'rebuild_ghostdb' => 'rebuild_ghostdb', 'notify_other' => 'notify_other', 'reset_sstp_flag' => 'reset_sstp_flag', 'get_sstp_port' => 'get_sstp_port', 'get_prefix' => 'get_sakura_prefix', 'get_workarea' => 'get_workarea' } unless handlers.include?(event) if Application.method_defined?(event) result = method(event).call(*arglist) else result = nil end else result = method(handlers[event]).call(*arglist) end return result if event_type == 'GET' end def set_collisionmode(flag, rect: false) @prefs.check_collision_button.set_active(flag) @prefs.check_collision_name_button.set_active((not rect)) @prefs.update(:commit => true) # XXX notify_preference_changed() end def do_install(filename) @communicate.notify_all('OnInstallBegin', []) begin filetype, target_dirs, names, errno = @installer.install( filename, Home.get_ninix_home()) rescue target_dirs = nil end unless errno.zero? error_reason = { 1 => 'extraction', 2 => 'invalid type', 3 => 'artificial', 4 => 'unsupported', } if error_reason.include?(errno) @communicate.notify_all('OnInstallFailure', [error_reason[errno]]) else @communicate.notify_all('OnInstallFailure', ['unknown']) end # XXX: ninix-ayaでは発生しない. ##@communicate.notify_all('OnInstallRefuse', []) else ##@communicate.notify_all('OnInstallCompleteEx', []) # FIXME if filetype != 'kinoko' if filetype == 'ghost' unless target_dirs[1].nil? id = 'ghost with balloon' name2 = names[1] else id = filetype name2 = nil end name = names[0] else id = filetype name2 = nil name = names end @communicate.notify_all('OnInstallComplete', [id, name, name2]) end end unless target_dirs.nil? or target_dirs.empty? case filetype when 'ghost' add_sakura(target_dirs[0]) Sakura::ReadmeDialog.new.show( target_dirs[0], File.join(Home.get_ninix_home(), 'ghost', target_dirs[0])) unless target_dirs[1].nil? add_balloon(target_dirs[1]) Sakura::ReadmeDialog.new.show( target_dirs[1], File.join(Home.get_ninix_home(), 'balloon', target_dirs[1])) end when 'supplement' add_sakura(target_dirs) # XXX: reload when 'balloon' add_balloon(target_dirs) Sakura::ReadmeDialog.new.show( target_dirs, File.join(Home.get_ninix_home(), 'balloon', target_dirs)) when 'nekoninni' @nekoninni = Home.search_nekoninni() when 'katochan' @katochan = Home.search_katochan() when 'kinoko' @kinoko = Home.search_kinoko() @communicate.notify_all('OnKinokoObjectInstalled', names) end end end def notify_installedghostname(key: nil) installed = [] for value in @ghosts.values() sakura = value.instance next if sakura.nil? installed << sakura.get_name(:default => '') end unless key.nil? if @ghosts.include?(key) sakura = @ghosts[key].instance sakura.notify_event('installedghostname', *installed) end else for sakura in get_working_ghost sakura.notify_event('installedghostname', *installed) end end end def notify_installedballoonname(key: nil) installed = [] for value in @balloons.values() desc, balloon = value.baseinfo subdir = balloon['balloon_dir'][0] installed << desc.get('name', :default => subdir) end unless key.nil? if @ghosts.include?(key) sakura = @ghosts[key].instance sakura.notify_event('installedballoonname', *installed) end else for sakura in get_working_ghost sakura.notify_event('installedballoonname', *installed) end end end def current_sakura_instance @ghosts[@current_sakura].instance end def create_ghost(data) ghost = Sakura::Sakura.new ghost.set_responsible(self) ghost.new_(*data) return ghost end def get_sakura_cantalk current_sakura_instance.cantalk end def get_event_response(event, *arglist) ## FIXME current_sakura_instance.get_event_response(*event) end def keep_silence(quiet) current_sakura_instance.keep_silence(quiet) end def get_ghost_name sakura = current_sakura_instance return sakura.get_ifghost() end def enqueue_event(event, *arglist) current_sakura_instance.enqueue_event(event, *arglist) end def enqueue_script(event, script, sender, handle, host, show_sstp_marker, use_translator, db: nil, request_handler: nil, temp_mode: false) sakura = current_sakura_instance if temp_mode sakura.enter_temp_mode() end sakura.enqueue_script(event, script, sender, handle, host, show_sstp_marker, use_translator, :db => db, :request_handler => request_handler) end def get_working_ghost(cantalk: false) ghosts = [] for value in @ghosts.values() sakura = value.instance next if sakura.nil? next unless sakura.is_running() next if cantalk and not sakura.cantalk ghosts << sakura end return ghosts end def get_sakura_prefix @__menu_owner.get_prefix() end def getstring(name) @__menu_owner.getstring(name) end def stick_window stick = @__menu.get_stick() @__menu_owner.stick_window(stick) end def toggle_bind(args) @__menu_owner.toggle_bind(args) end def select_shell(key) @__menu_owner.select_shell(key) end def select_balloon(key) desc, balloon = get_balloon_description(key) @__menu_owner.select_balloon(key, desc, balloon) end def get_current_balloon_directory @__menu_owner.get_current_balloon_directory() end def start_sakura_cb(key, caller: nil) sakura_name = @ghosts[key].instance.get_selfname(:default => '') name = @ghosts[key].instance.get_name(:default => '') if caller.nil? caller = @__menu_owner end caller.notify_event('OnGhostCalling', sakura_name, 'manual', name, key) start_sakura(key, :init => true) # XXX end def select_sakura(key) if @__menu_owner.busy() Gdk.beep() return end change_sakura(@__menu_owner, key, 'manual') end def notify_site_selection(*args) @__menu_owner.notify_site_selection(args) end def close_sakura @__menu_owner.close() end def about @__menu_owner.about() end def vanish @__menu_owner.vanish() end def network_update @__menu_owner.network_update() end def open_popup_menu(sakura, side) @__menu_owner = sakura path_background, path_sidebar, path_foreground, \ align_background, align_sidebar, align_foreground = \ @__menu_owner.get_menu_pixmap() @__menu.set_pixmap( path_background, path_sidebar, path_foreground, align_background, align_sidebar, align_foreground) background, foreground = @__menu_owner.get_menu_fontcolor() @__menu.set_fontcolor(background, foreground) mayuna_menu = @__menu_owner.get_mayuna_menu() @__menu.create_mayuna_menu(mayuna_menu) @__menu.popup(side) end def get_ghost_menus menus = [] for value in @ghosts.values() menus << value.menuitem end return menus end def get_shell_menu @__menu_owner.get_shell_menu() end def get_balloon_menu current_key = get_current_balloon_directory() for key in @balloons.keys menuitem = @balloons[key].menuitem menuitem.set_sensitive(key != current_key) # not working end return @balloon_menu end def create_balloon_menuitem(balloon_name, balloon_key, thumbnail) @__menu.create_meme_menuitem( balloon_name, balloon_key, lambda {|v| select_balloon(v) }, thumbnail) end def create_balloon_menu balloon_menuitems = {} # Ordered Hash for key in @balloons.keys balloon_menuitems[key] = @balloons[key].menuitem end return @__menu.create_meme_menu(balloon_menuitems) end def create_shell_menu(menuitems) @__menu.create_meme_menu(menuitems) end def create_shell_menuitem(shell_name, shell_key, thumbnail) @__menu.create_meme_menuitem( shell_name, shell_key, lambda {|key| select_shell(key) }, thumbnail) end def create_menuitem(key, baseinfo) desc = baseinfo[0] shiori_dir = baseinfo[1] icon = desc.get('icon', :default => nil) unless icon.nil? icon_path = File.join(shiori_dir, icon) unless File.exists?(icon_path) icon_path = nil end else icon_path = nil end name = desc.get('name') thumbnail_path = File.join(shiori_dir, 'thumbnail.png') unless File.exists?(thumbnail_path) thumbnail_path = nil end start_menuitem = @__menu.create_ghost_menuitem( name, icon_path, key, lambda {|key| start_sakura_cb(key) }, # XXX thumbnail_path) select_menuitem = @__menu.create_ghost_menuitem( name, icon_path, key, lambda {|key| select_sakura(key) }, thumbnail_path) menuitem = { 'Summon' => start_menuitem, 'Change' => select_menuitem, } return menuitem end def delete_ghost(key) fail "assert" unless @ghosts.include?(key) @ghosts.delete(key) end def get_balloon_list balloon_list = [] for key in @balloons.keys desc, balloon = @balloons[key].baseinfo subdir = balloon['balloon_dir'][0] name = desc.get('name', :default => subdir) balloon_list << [name, subdir] end return balloon_list end def get_nekodorif_list nekodorif_list = [] nekoninni = @nekoninni for nekoninni_name, nekoninni_dir in nekoninni next if nekoninni_name.nil? or nekoninni_name.empty? item = {} item['name'] = nekoninni_name item['dir'] = nekoninni_dir nekodorif_list << item end return nekodorif_list end def get_kinoko_list @kinoko end def load # load user preferences @prefs.load() # choose default ghost/shell directory = @prefs.get('sakura_dir') name = @prefs.get('sakura_name') # XXX: backward compat default_sakura = find_ghost_by_dir(directory) if default_sakura.nil? default_sakura = find_ghost_by_name(name) end if default_sakura.nil? default_sakura = choose_default_sakura() end # load ghost @current_sakura = default_sakura ##for i, name in enumerate(get_ghost_names()) ## Logging::Logging.info("GHOST(#{i}): #{name}") ##end start_sakura(@current_sakura, :init => true, :abend => @abend) end def find_ghost_by_dir(directory) if @ghosts.include?(directory) return directory else return nil end end def find_ghost_by_name(name) for key in @ghosts.keys sakura = @ghosts[key].instance begin if sakura.get_name(:default => nil) == name return key end rescue # old preferences(EUC-JP) #pass end end return nil end def choose_default_sakura return @ghosts.keys[0] end def find_balloon_by_name(name) for key in @balloons.keys desc, balloon = @balloons[key].baseinfo begin if desc.get('name') == name return key end if balloon['balloon_dir'][0] == Home.get_normalized_path(name) # XXX return key end rescue # old preferences(EUC-JP) #pass end end return nil end def find_balloon_by_subdir(subdir) for key in @balloons.keys desc, balloon = @balloons[key].baseinfo begin if balloon['balloon_dir'][0] == subdir return key end if Home.get_normalized_path(desc.get('name')) == subdir # XXX return key end rescue # old preferences(EUC-JP) #pass end end return nil end def run(abend, app_window) @abend = abend @app_window = app_window if RbConfig::CONFIG['host_os'] =~ /mswin|mingw|cygwin/ # The SIGTERM signal is not generated under Windows NT. # Win32::Console::API.SetConsoleCtrlHandler will probably not be implemented. else Signal.trap(:TERM) {|signo| close_all_ghosts(:reason => 'shutdown') } end @timeout_id = GLib::Timeout.add(100) { do_idle_tasks } # 100[ms] Gtk.main() end def get_ghost_names for value in @ghosts.values() unless value.instance.nil? yield value.instance.get_selfname() ## FIXME end end end def if_ghost(if_ghost, working: false) instance_list = [] for value in @ghosts.values() unless value.instance.nil? instance_list << value.instance end end for sakura in instance_list if working next unless sakura.is_running() and sakura.cantalk end return true if sakura.ifghost(if_ghost) end return false end def update_sakura(name, sender) key = find_ghost_by_name(name) return if key.nil? sakura = @ghosts[key].instance unless sakura.is_running() start_sakura(key, :init => true) end sakura.enqueue_script(nil, '\![updatebymyself]\e', sender, nil, nil, false, false, :db => nil) end def select_current_sakura(ifghost: nil) unless ifghost.nil? break_flag = false for value in @ghosts.values() sakura = value.instance next if sakura.nil? if sakura.ifghost(ifghost) unless sakura.is_running() @current_sakura = value.key start_sakura(@current_sakura, :init => true, :temp => 1) else @current_sakura = sakura.key end break else #pass end end return unless break_flag else working_list = get_working_ghost(:cantalk => true) unless working_list.empty? @current_sakura = working_list.sample.key else return end end end def set_menu_sensitive(key, flag) menuitems = @ghosts[key].menuitem for item in menuitems.values() item.set_sensitive(flag) end end def close_ghost(sakura) if get_working_ghost.empty? @prefs.set_current_sakura(sakura.key) quit() elsif @current_sakura == sakura.key select_current_sakura() end end def close_all_ghosts(reason: 'user') for sakura in get_working_ghost sakura.notify_event('OnCloseAll', reason) end end def quit GLib::Source.remove(@timeout_id) @usage_dialog.close() @sstp_controler.quit() ## FIXME save_preferences() Gtk.main_quit() end def save_preferences begin @prefs.save() rescue # IOError, SystemCallError Logging::Logging.error('Cannot write preferences to file (ignored).') rescue #pass ## FIXME end end def select_ghost(sakura, sequential, event: 1, vanished: false) keys = @ghosts.keys return if keys.length < 2 # select another ghost if sequential key = keys[(keys.index(sakura.key) + 1) % keys.length] else keys.delete(sakura.key) key = keys.sample end change_sakura(sakura, key, 'automatic', :event => event, :vanished => vanished) end def select_ghost_by_name(sakura, name, event: 1, vanished: false) key = find_ghost_by_name(name) return if key.nil? change_sakura(sakura, key, 'automatic', :event => event, :vanished => vanished) end def change_sakura(sakura, key, method, event: 1, vanished: false) return if sakura.key == key # XXX: needs reloading? proc_obj = lambda { stop_sakura( sakura, lambda {|key, prev| start_sakura(key, :prev => prev) }, key, sakura.key) } if vanished sakura.finalize() start_sakura(key, :prev => sakura.key, :vanished => vanished) close_ghost(sakura) elsif event.zero? proc_obj.call() else sakura_name = @ghosts[key].instance.get_selfname(:default => '') name = @ghosts[key].instance.get_name(:default => '') sakura.enqueue_event( 'OnGhostChanging', sakura_name, method, name, key, :proc_obj => proc_obj) end end def stop_sakura(sakura, starter=nil, *args) sakura.finalize() unless starter.nil? starter.call(*args) end set_menu_sensitive(sakura.key, true) close_ghost(sakura) end def start_sakura(key, prev: nil, vanished: false, init: false, temp: 0, abend: nil) sakura = @ghosts[key].instance fail "assert" if sakura.nil? unless prev.nil? fail "assert" unless @ghosts.include?(prev) ## FIXME: vanish case? fail "assert" if @ghosts[prev].instance.nil? end if init ghost_changed = false else fail "assert" if prev.nil? ## FIXME if prev == key ghost_changed = false else ghost_changed = true end end if ghost_changed self_name = @ghosts[prev].instance.get_selfname() name = @ghosts[prev].instance.get_name() shell = @ghosts[prev].instance.get_current_shell_name() last_script = @ghosts[prev].instance.last_script else self_name = nil name = nil shell = nil last_script = nil end sakura.notify_preference_changed() sakura.start(key, init, temp, vanished, ghost_changed, self_name, name, shell, last_script, abend) notify_installedghostname(:key => key) notify_installedballoonname(:key => key) sakura.notify_installedshellname() set_menu_sensitive(key, false) end def update_working(ghost_name) @lockfile.truncate(0) @lockfile.seek(0) @lockfile.write(ghost_name) @lockfile.flush() end def notify_preference_changed for sakura in get_working_ghost sakura.notify_preference_changed() end end def get_balloon_description(subdir) key = find_balloon_by_subdir(subdir) if key.nil? ##Logging::Logging.warning('Balloon ' + subdir + ' not found.') default_balloon = @prefs.get('default_balloon') key = find_balloon_by_subdir(default_balloon) end if key.nil? key = @balloons.keys[0] end return @balloons[key].baseinfo end def reload_current_sakura(sakura) save_preferences() key = sakura.key ghost_dir = File.split(sakura.get_prefix())[1] # XXX ghost_conf = Home.search_ghosts(:target => [ghost_dir]) unless ghost_conf.nil? @ghosts[key].baseinfo = ghost_conf[key] else close_ghost(sakura) ## FIXME @ghosts.delete(key) return ## FIXME end start_sakura(key, :prev => key, :init => true) end def add_sakura(ghost_dir) if @ghosts.include?(ghost_dir) exists = true Logging::Logging.warning('INSTALLED GHOST CHANGED: ' + ghost_dir) else exists = false Logging::Logging.info('NEW GHOST INSTALLED: ' + ghost_dir) end ghost_conf = Home.search_ghosts(:target => [ghost_dir]) unless ghost_conf.empty? if exists sakura = @ghosts[ghost_dir].instance if sakura.is_running() # restart if working key = sakura.key proc_obj = lambda { @ghosts[ghost_dir].baseinfo = ghost_conf[ghost_dir] Logging::Logging.info('restarting....') start_sakura(key, :prev => key, :init => true) Logging::Logging.info('done.') } stop_sakura(sakura, proc_obj) end else holon = Ghost.new(ghost_dir) holon.set_responsible(self) @ghosts[ghost_dir] = holon holon.baseinfo = ghost_conf[ghost_dir] end else if exists sakura = @ghosts[ghost_dir].instance if sakura.is_running() # stop if working stop_sakura(sakura) end @ghosts.delete(ghost_dir) end end notify_installedghostname() end def add_balloon(balloon_dir) if @balloons.include?(balloon_dir) exists = true Logging::Logging.warning('INSTALLED BALLOON CHANGED: ' + balloon_dir) else exists = false Logging::Logging.info('NEW BALLOON INSTALLED: ' + balloon_dir) end balloon_conf = Home.search_balloons(:target => [balloon_dir]) unless balloon_conf.empty? if exists @balloons[balloon_dir].baseinfo = balloon_conf[balloon_dir] else meme = BalloonMeme.new(balloon_dir) meme.set_responsible(self) @balloons[balloon_dir] = meme meme.baseinfo = balloon_conf[balloon_dir] end else if exists @balloons.delete(balloon_dir) end end @balloon_menu = create_balloon_menu() notify_installedballoonname() end def vanish_sakura(sakura, next_ghost) # remove ghost prefix = sakura.get_prefix() Dir.foreach(prefix) { |filename| next if /\A\.+\z/ =~ filename if File.file?(File.join(prefix, filename)) if filename != 'HISTORY' begin File.delete(File.join(prefix, filename)) rescue Logging::Logging.error( '*** REMOVE FAILED *** : ' + filename) end end else # dir begin FileUtils.remove_entry_secure(File.join(prefix, filename)) rescue Logging::Logging.error( '*** REMOVE FAILED *** : ' + filename) end end } unless next_ghost.nil? select_ghost_by_name(sakura, next_ghost, :vanished => true) else select_ghost(sakura, false, :vanished => true) end @ghosts.delete(sakura.key) end def select_nekodorif(nekodorif_dir) target = @__menu_owner Nekodorif::Nekoninni.new.load(nekodorif_dir, @katochan, target) end def select_kinoko(data) target = @__menu_owner Kinoko::Kinoko.new(@kinoko).load(data, target) end def open_console @console.open() end def open_ghost_manager @__ngm.show_dialog() end def show_usage for sakura in get_working_ghost sakura.save_history() end history = {} for key in @ghosts.keys sakura = @ghosts[key].instance name = sakura.get_name(:default => key) ghost_time = 0 prefix = sakura.get_prefix() path = File.join(prefix, 'HISTORY') if File.exists?(path) begin f = open(path, 'r') for line in f next unless line.include?(',') key, value = line.split(',', 2) key = key.strip() if key == 'time' begin ghost_time = Integer(value.strip()) rescue #pass end end end rescue # IOError => e Logging::Logging.error('cannot read ' + path) end end ai_list = [] Dir.foreach(File.join(prefix, 'shell')) { |subdir| next if /\A\.+\z/ =~ subdir path = File.join(prefix, 'shell', subdir, 'ai.png') if File.exists?(path) ai_list << path end } history[name] = [ghost_time, ai_list] end @usage_dialog.open(history) end def confirmed @confirmed end def search_ghosts balloons = @balloons ghosts = @ghosts if ghosts.length > 0 and balloons.length > 0 @confirmed = true end return ghosts.length, balloons.length end def do_idle_tasks unless @confirmed @console.open() else unless @loaded load() # start SSTP server @sstp_controler.start_servers() @loaded = true else @sstp_controler.handle_sstp_queue() @sstp_controler.receive_sstp_request() end end return true end def get_workarea [0, 0, *@app_window.size] end end class Console include GetText attr_writer :level def initialize(app) @app = app @dialog = Gtk::Dialog.new(:parent => nil) @dialog.signal_connect('delete_event') do |w, e| next true # XXX end @level = Logger::WARN # XXX Logging::Logging.add_logger(self) @sw = Gtk::ScrolledWindow.new @sw.set_policy(Gtk::PolicyType::NEVER, Gtk::PolicyType::ALWAYS) @sw.show() @tv = Gtk::TextView.new @tv.set_wrap_mode(Gtk::WrapMode::CHAR) @tv.override_background_color( Gtk::StateFlags::NORMAL, Gdk::RGBA.new(0, 0, 0, 255)) @tv.set_cursor_visible(true) @tv.set_editable(true) # important @tb = @tv.buffer @tag_critical = @tb.create_tag(nil, 'foreground' => 'red') @tag_error = @tb.create_tag(nil, 'foreground' => 'red') @tag_warning = @tb.create_tag(nil, 'foreground' => 'orange') @tag_info = @tb.create_tag(nil, 'foreground' => 'green') @tag_debug = @tb.create_tag(nil, 'foreground' => 'yellow') @tag_notset = @tb.create_tag(nil, 'foreground' => 'blue') ### DnD data types ##dnd_targets = [['text/uri-list', 0, 0]] ##@tv.drag_dest_set(Gtk::DestDefaults::ALL, dnd_targets, ## Gdk::DragAction::COPY) @tv.drag_dest_set_target_list(nil) # important @tv.drag_dest_add_uri_targets() @tv.signal_connect('drag_data_received') do |widget, context, x, y, data, info, time| drag_data_received(widget, context, x, y, data, info, time) next true end @tv.show() @sw.add(@tv) @tv.set_size_request(400, 250) @sw.set_size_request(400, 250) content_area = @dialog.content_area content_area.pack_start(@sw, :expand => true, :fill => true, :padding => 0) @dialog.add_button('Install', 1) @dialog.add_button("_Close", Gtk::ResponseType::CLOSE) @dialog.signal_connect('response') do |w, e| next response(w, e) end @file_chooser = Gtk::FileChooserDialog.new( :title => "Install..", :action => Gtk::FileChooserAction::OPEN, :buttons => [["_Open", Gtk::ResponseType::OK], ["_Cancel", Gtk::ResponseType::CANCEL]]) @file_chooser.set_default_response(Gtk::ResponseType::CANCEL) filter = Gtk::FileFilter.new filter.set_name("All files") filter.add_pattern("*") @file_chooser.add_filter(filter) filter = Gtk::FileFilter.new filter.set_name("nar/zip") filter.add_mime_type("application/zip") filter.add_pattern("*.nar") filter.add_pattern("*.zip") @file_chooser.add_filter(filter) @opened = false end def message_with_tag(message, tag) it = @tb.end_iter @tb.insert(it, [Logger::SEV_LABEL[@level], ':', message, "\n"].join(""), :tags => [tag]) it = @tb.end_iter # scroll_to_iter may not have the desired effect. mark = @tb.create_mark("end", it, false) @tv.scroll_to_mark(mark, 0.0, false, 0.5, 0.5) end def info(message) return if @level > Logger::INFO tag = @tag_info message_with_tag(message, tag) end def debug(message) return if @level > Logger::DEBUG tag = @tag_debug message_with_tag(message, tag) end def fatal(message) return if @level > Logger::FATAL tag = @tag_critical message_with_tag(message, tag) end def error(message) return if @level > Logger::ERROR tag = @tag_error message_with_tag(message, tag) end def warn(message) return if @level > Logger::WARN tag = @tag_warning message_with_tag(message, tag) end def unknown(message) return if @level > Logger::UNKNOWN tag = @tag_notset message_with_tag(message, tag) end def update ghosts, balloons = @app.search_ghosts() # XXX if ghosts > 0 and balloons > 0 @dialog.set_title(_('Console')) Logging::Logging.info('Ghosts: ' + ghosts.to_s) Logging::Logging.info('Balloons: ' + balloons.to_s) else @dialog.set_title(_("Nanntokashitekudasai.")) if ghosts > 0 Logging::Logging.info('Ghosts: ' + ghosts.to_s) else Logging::Logging.warning('Ghosts: ' + ghosts.to_s) end if balloons > 0 Logging::Logging.info('Balloons: ' + balloons.to_s) else Logging::Logging.warning('Balloons: ' + balloons.to_s) end end end def open return if @opened update() @dialog.show() @opened = true end def close @dialog.hide() @opened = false unless @app.confirmed @app.quit() end return true end def response(widget, response) func = {1 => 'open_file_chooser', Gtk::ResponseType::CLOSE.to_i => 'close', Gtk::ResponseType::DELETE_EVENT.to_i => 'close', } method(func[response]).call() return true end def open_file_chooser response = @file_chooser.run() case response when Gtk::ResponseType::OK filename = @file_chooser.filename @app.do_install(filename) update() when Gtk::ResponseType::CANCEL #pass end @file_chooser.hide() end def drag_data_received(widget, context, x, y, data, info, time) filelist = [] for uri in data.uris uri_parsed = URI.parse(uri) pathname = URI.unescape(uri_parsed.path) if uri_parsed.scheme == 'file' and File.exists?(pathname) filelist << pathname elsif uri_parsed.scheme == 'http' or uri_parsed.scheme == 'ftp' filelist << uri end end unless filelist.empty? for filename in filelist @app.do_install(filename) end update() end end end class UsageDialog def initialize @dialog = Gtk::Dialog.new @dialog.set_title('Usage') @dialog.signal_connect('delete_event') do |w, e| next true # XXX end @darea = Gtk::DrawingArea.new @darea.set_events(Gdk::EventMask::EXPOSURE_MASK) @size = [550, 330] @darea.set_size_request(*@size) @darea.signal_connect('configure_event') do |w, e| configure(w, e) next true end @darea.signal_connect('draw') do |w, e| redraw(w, e) next true end content_area = @dialog.content_area content_area.pack_start(@darea, :expand => true, :fill => true, :padding => 0) @darea.show() @dialog.add_button("_Close", Gtk::ResponseType::CLOSE) @dialog.signal_connect('response') do |w, e| next response(w, e) end @opened = false end def open(history) return if @opened @history = history @items = [] for item in @history name = item[0] clock = item[1][0] path = item[1][1] @items << [name, clock, path] end @items.sort_by! {|item| item[1] } @items.reverse! ai_list = @items[0][2] unless ai_list.empty? path = ai_list.sample fail "assert" unless File.exists?(path) @pixbuf = Pix.create_pixbuf_from_file(path) @pixbuf.saturate_and_pixelate(1.0, true) else @pixbuf = nil end @dialog.show() @opened = true end def close @dialog.hide() @opened = false return true end def response(widget, response) func = {Gtk::ResponseType::CLOSE.to_i => 'close', Gtk::ResponseType::DELETE_EVENT.to_i => 'close', } method(func[response]).call() return true end def configure(darea, event) alloc = darea.allocation @size = [alloc.width, alloc.height] end def redraw(widget, cr) if @items.empty? return # should not reach here end total = 0.0 for name, clock, path in @items total += clock end layout = Pango::Layout.new(widget.pango_context) font_desc = Pango::FontDescription.new() font_desc.set_size(9 * Pango::SCALE) font_desc.set_family('Sans') # FIXME layout.set_font_description(font_desc) # redraw graph w, h = @size cr.set_source_rgb(1.0, 1.0, 1.0) # white cr.paint() # ai.png unless @pixbuf.nil? cr.set_source_pixbuf(@pixbuf, 16, 32) # XXX cr.paint() end w3 = w4 = 0 rows = [] for name, clock, path in @items[0..13] layout.set_text(name) name_w, name_h = layout.pixel_size rate = sprintf("%.1f%%", clock / total * 100) layout.set_text(rate) rate_w, rate_h = layout.pixel_size w3 = [rate_w, w3].max time = sprintf("%d:%02d", *(clock / 60).to_i.divmod(60)) layout.set_text(time) time_w, time_h = layout.pixel_size w4 = [time_w, w4].max rows << [clock, name, name_w, name_h, rate, rate_w, rate_h, time, time_w, time_h] end w1 = 280 w2 = (w - w1 - w3 - w4 - 70) x = 20 y = 15 x += (w1 + 10) label = 'name' layout.set_text(label) label_name_w, label_name_h = layout.pixel_size cr.set_source_rgb(0.8, 0.8, 0.8) # gray cr.move_to(x, y) cr.show_pango_layout(layout) x = (x + w2 + 10) label = 'rate' layout.set_text(label) label_rate_w, label_rate_h = layout.pixel_size cr.set_source_rgb(0.8, 0.8, 0.8) # gray cr.move_to(x + w3 - label_rate_w, y) cr.show_pango_layout(layout) x += (w3 + 10) label = 'time' layout.set_text(label) label_time_w, label_time_h = layout.pixel_size cr.set_source_rgb(0.8, 0.8, 0.8) # gray cr.move_to(x + w4 - label_time_w, y) cr.show_pango_layout(layout) y += ([label_name_h, label_rate_h, label_time_h].max + 4) for clock, name, name_w, name_h, rate, rate_w, rate_h, time, time_w, \ time_h in rows x = 20 bw = (clock / total * w1).to_i bh = ([name_h, rate_h, time_h].max - 1) cr.set_source_rgb(0.8, 0.8, 0.8) # gray cr.rectangle(x + 1, y + 1, bw, bh) cr.stroke() cr.set_source_rgb(1.0, 1.0, 1.0) # white cr.rectangle(x, y, bw, bh) cr.stroke() cr.set_source_rgb(0.0, 0.0, 0.0) # black cr.rectangle(x, y, bw, bh) cr.stroke() x += (w1 + 10) layout.set_text(name) end_ = name.length while end_ > 0 w, h = layout.pixel_size if w > 168 end_ -= 1 layout.set_text([name[0..end_-1], '...'].join('')) else break end end cr.set_source_rgb(0.0, 0.0, 0.0) # black cr.move_to(x, y) cr.show_pango_layout(layout) x += (w2 + 10) layout.set_text(rate) cr.set_source_rgb(0.0, 0.0, 0.0) # black cr.move_to(x + w3 - rate_w, y) cr.show_pango_layout(layout) x += (w3 + 10) layout.set_text(time) cr.set_source_rgb(0.0, 0.0, 0.0) # black cr.move_to(x + w4 - time_w, y) cr.show_pango_layout(layout) y += ([name_h, rate_h, time_h].max + 4) end end end end Logging::Logging.set_level(Logger::INFO) gtk_app = Gtk::Application.new('net.osdn.ninix-aya', :flags_none) gtk_app.signal_connect 'activate' do |application| # parse command line arguments opt = OptionParser.new option = {} opt.on('--sstp-port sstp_port', 'additional port for listening SSTP requests') {|v| option[:sstp_port] = v} opt.on('--debug', 'debug') {|v| option[:debug] = v} opt.on('--logfile logfile_name', 'logfile name') {|v| option[:logfile] = v} opt.parse!(ARGV) Logging::Logging.add_logger(Logger.new(option[:logfile])) unless option[:logfile].nil? # TCP 7743:伺か(未使用)(IANA Registered Port for SSTP) # UDP 7743:伺か(未使用)(IANA Registered Port for SSTP) # TCP 9801:伺か (IANA Registered Port for SSTP) # UDP 9801:伺か(未使用)(IANA Registered Port for SSTP) # TCP 9821:SSP # TCP 11000:伺か(廃止) (IANA Registered Port for IRISA) sstp_port = [9801] # parse command line arguments unless option[:sstp_port].nil? if option[:sstp_port].to_i < 1024 Logging::Logging.warning("Invalid --sstp-port number (ignored)") else sstp_port << option[:sstp_port].to_i end end Logging::Logging.set_level(Logger::DEBUG) unless option[:debug].nil? home_dir = Home.get_ninix_home() unless File.exists?(home_dir) begin FileUtils.mkdir_p(home_dir) rescue raise SystemExit("Cannot create Home directory (abort)\n") end end lockfile_path = File.join(Home.get_ninix_home(), ".lock") if File.exists?(lockfile_path) f = open(lockfile_path, 'r') abend = f.gets else abend = nil end # aquire Inter Process Mutex (not Global Mutex) f = open(lockfile_path, 'w') begin Lock.lockfile(f) rescue raise SystemExit("ninix-aya is already running") end app_window = Pix::TransparentApplicationWindow.new(application) app_window.set_title("Ninix-aya") app_window.show_all # start app = Ninix_Main::Application.new(f, :sstp_port => sstp_port) app.run(abend, app_window) # end f.truncate(0) begin Lock.unlockfile(f) rescue #pass end app_window.destroy end begin gtk_app.run rescue => e # should never rescue Exception Ninix_Main.handleException(e) end ninix-aya-5.0.9/lib/ninix/0000755000175000017500000000000013416507430013510 5ustar shyshyninix-aya-5.0.9/lib/ninix/sstplib.rb0000644000175000017500000000706713416507430015527 0ustar shyshy# -*- coding: utf-8 -*- # # sstplib.rb - an SSTP library module in Ruby # Copyright (C) 2001, 2002 by Tamito KAJIYAMA # Copyright (C) 2002, 2003 by MATSUMURA Namihiko # Copyright (C) 2002-2019 by Shyouzou Sugitani # # This program is free software; you can redistribute it and/or modify it # under the terms of the GNU General Public License (version 2) as # published by the Free Software Foundation. It is distributed in the # hope that it will be useful, but WITHOUT ANY WARRANTY; without even the # implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR # PURPOSE. See the GNU General Public License for more details. # require_relative "logging" module SSTPLib class BaseSSTPRequestHandler RESPONSES = { 200 => 'OK', 204 => 'No Content', 210 => 'Break', 400 => 'Bad Request', 408 => 'Request Timeout', 409 => 'Conflict', 420 => 'Refuse', 501 => 'Not Implemented', 503 => 'Service Unavailable', 510 => 'Not Local IP', 511 => 'In Black List', 512 => 'Invisible', } def initialize(server, fp) @server = server @fp = fp end def parse_headers() return if @fp.nil? message = [] while line = @fp.gets break if line.strip.empty? line = line.chomp next unless line.include?(":") key, value = line.split(":", 2) message << [key, value.strip] end charset = message.reverse.assoc("Charset")&.at(1) || "Shift_JIS" # XXX message.each {|k, v| v.force_encoding(charset).encode!("UTF-8", :invalid => :replace, :undef => :replace) } end def parse_request(requestline) requestline = requestline.encode('Shift_JIS', :invalid => :replace, :undef => :replace) requestline = requestline.chomp @requestline = requestline re_requestsyntax = Regexp.new('\A([A-Z]+) SSTP/([0-9]\\.[0-9])\z') match = re_requestsyntax.match(requestline) if match.nil? @equestline = '-' send_error(400, :message => "Bad Request #{requestline}") return false end @command, @version = match[1, 2] @headers = parse_headers return true end def handle(line) @error = @version = nil return unless parse_request(line) name = ("do_#{@command}_#{@version[0]}_#{@version[2]}") begin method(name).call rescue send_error( 501, :message => "Not Implemented (#{@command}/#{@version})") return end end def send_error(code, message: nil) @error = code log_error((message or RESPONSES[code])) send_response(code, :message => RESPONSES[code]) end def send_response(code, message: nil) log_request(code, :message => message) @fp.write("SSTP/#{(@version or "1.0")} #{code} #{RESPONSES[code]}\r\n\r\n") end def log_error(message) Logging::Logging.error("[#{timestamp}] #{message}\n") end def log_request(code, message: nil) if @requestline == '-' request = @requestline else request = "\"#{@requestline}\"" end Logging::Logging.info("#{client_hostname} [#{timestamp}] #{request} #{code} #{(message or RESPONSES[code])}\n") end def client_hostname begin sock_domain, remote_port, remote_hostname, remote_ip = @fp.peeraddr return remote_hostname rescue return 'localhost' end end def timestamp Time.now.localtime.strftime("%d/%b/%Y:%H:%M:%S %z") end end end ninix-aya-5.0.9/lib/ninix/surface.rb0000644000175000017500000014333313416507430015474 0ustar shyshy# -*- coding: utf-8 -*- # # Copyright (C) 2001, 2002 by Tamito KAJIYAMA # Copyright (C) 2002, 2003 by MATSUMURA Namihiko # Copyright (C) 2002-2019 by Shyouzou Sugitani # Copyright (C) 2003 by Shun-ichi TAHARA # # This program is free software; you can redistribute it and/or modify it # under the terms of the GNU General Public License (version 2) as # published by the Free Software Foundation. It is distributed in the # hope that it will be useful, but WITHOUT ANY WARRANTY; without even the # implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR # PURPOSE. See the GNU General Public License for more details. # require "gtk3" require_relative "keymap" require_relative "pix" require_relative "seriko" require_relative "metamagic" require_relative "logging" module Surface class Surface < MetaMagic::Holon attr_reader :name, :prefix, :window def initialize super("") # FIXME @window = [] @desc = nil @mikire = 0 @kasanari = 0 @key_press_count = 0 @handlers = { 'stick_window' => 'window_stick', } end def finalize for surface_window in @window surface_window.destroy end @window = [] end def create_gtk_window(title, skip_taskbar) window = Pix::TransparentWindow.new() window.set_title(title) if skip_taskbar window.set_skip_taskbar_hint(true) end window.signal_connect('delete_event') do |w, e| next delete(w, e) end window.signal_connect('key_press_event') do |w, e| next key_press(w, e) end window.signal_connect('key_release_event') do |w, e| next key_press(w, e) end window.signal_connect('window_state_event') do |w, e| window_state(w, e) next true end window.set_events(Gdk::EventMask::KEY_PRESS_MASK| Gdk::EventMask::KEY_RELEASE_MASK) window.realize() return window end def identify_window(win) for surface_window in @window return true if win == surface_window.get_window.window end return false end def window_stayontop(flag) for surface_window in @window gtk_window = surface_window.get_window gtk_window.set_keep_above(flag) end end def window_iconify(flag) gtk_window = @window[0].window iconified = (gtk_window.window.state & \ Gdk::WindowState::ICONIFIED).nonzero? if flag and not iconified gtk_window.iconify() elsif not flag and iconified gtk_window.deiconify() end end def window_state(window, event) return unless @parent.handle_request('GET', 'is_running') return if (event.changed_mask & Gdk::WindowState::ICONIFIED).zero? if (event.new_window_state & Gdk::WindowState::ICONIFIED).nonzero? if window == @window[0].get_window @parent.handle_request('NOTIFY', 'notify_iconified') end for surface_window in @window gtk_window = surface_window.get_window if gtk_window != window and \ (gtk_window.window.state & \ Gdk::WindowState::ICONIFIED).nonzero? gtk_window.iconify() end end else for surface_window in @window gtk_window = surface_window.get_window if gtk_window != window and \ (gtk_window.window.state & \ Gdk::WindowState::ICONIFIED).nonzero? gtk_window.deiconify() end end if window == @window[0].window @parent.handle_request('NOTIFY', 'notify_deiconified') end end return end def delete(window, event) return true end def key_press(window, event) name = Keymap::Keymap_old[event.keyval] keycode = Keymap::Keymap_new[event.keyval] if event.event_type == Gdk::EventType::KEY_RELEASE @key_press_count = 0 return true end return false unless (event.event_type == Gdk::EventType::KEY_PRESS) @key_press_count += 1 if (event.state & \ (Gdk::ModifierType::CONTROL_MASK | \ Gdk::ModifierType::SHIFT_MASK)).nonzero? if name == 'f12' Logging::Logging.info('reset surface position') reset_position() end if name == 'f10' Logging::Logging.info('reset balloon offset') for side in 0..@window.length-1 set_balloon_offset(side, nil) end end end unless name.nil? and keycode.nil? @parent.handle_request( 'NOTIFY', 'notify_event', 'OnKeyPress', name, keycode, @key_press_count) end return true end def window_stick(stick) for window in @window if stick window.get_window.stick() else window.get_window.unstick() end end end RE_SURFACE_ID = Regexp.new('\Asurface([0-9]+)\z') def get_seriko(surface) seriko = {} for basename in surface.keys path, config = surface[basename] match = RE_SURFACE_ID.match(basename) next if match.nil? key = match[1] # define animation patterns version = 1 # default: SERIKO/1.x if @seriko_descript['version'] == '1' version = 2 # SERIKO/2.0 end seriko[key] = Seriko.get_actors(config, :version => version) end return seriko end def new_(desc, surface_alias, surface, name, prefix, tooltips, seriko_descript, default_sakura, default_kero) @desc = desc @__tooltips = tooltips @seriko_descript = seriko_descript @name = name @prefix = prefix # load surface surfaces = {} elements = {} begin maxwidth = Integer(@seriko_descript.get('maxwidth', :default => '0')) rescue maxwidth = 0 end maxheight = 0 for basename in surface.keys path, config = surface[basename] next if path.nil? unless File.exists?(path) name = File.basename(path, ".*") ext = File.extname(path) dgp_path = [name, '.dgp'].join('') unless File.exists?(dgp_path) ddp_path = [name, '.ddp'].join('') unless File.exists?(ddp_path) Logging::Logging.error( path + ': file not found (ignored)') next else path = ddp_path end else path = dgp_path end end elements[basename] = [path] w, h = Pix.get_png_size(path) maxwidth = [maxwidth, w].max maxheight = [maxheight, h].max match = RE_SURFACE_ID.match(basename) next if match.nil? key = match[1] surfaces[key] = elements[basename] end # compose surface elements composite_surface = {} for basename in surface.keys path, config = surface[basename] match = RE_SURFACE_ID.match(basename) next if match.nil? key = match[1] if config.include?('element0') Logging::Logging.debug('surface ' + key) composite_surface[key] = compose_elements(elements, config) end end surfaces.update(composite_surface) # check if necessary surfaces have been loaded for key in [default_sakura, default_kero] unless surfaces.include?(key.to_s) fail RuntimeError, "cannot load default surface ##{key} (abort)\n" end end @__surfaces = surfaces # arrange surface configurations region = {} for basename in surface.keys path, config = surface[basename] match = RE_SURFACE_ID.match(basename) next if match.nil? key = match[1] # define collision areas buf = [] for n in 0..255 # "redo" syntax rect = config.get(['collision', n.to_s].join('')) next if rect.nil? values = rect.split(',', 0) next if values.length != 5 begin x1 = Integer(values[0]) y1 = Integer(values[1]) x2 = Integer(values[2]) y2 = Integer(values[3]) rescue next end buf << [values[4].strip(), x1, y1, x2, y2] end for part in ['head', 'face', 'bust'] # "inverse" syntax rect = config.get(['collision.', part].join('')) next if rect.nil? begin values = rect.split(',', 0) x1 = Integer(values[0]) y1 = Integer(values[1]) x2 = Integer(values[2]) y2 = Integer(values[3]) rescue #pass end buf << [part.capitalize(), x1, y1, x2, y2] end region[key] = buf end @__region = region # MAYUNA mayuna = {} for basename in surface.keys path, config = surface[basename] match = RE_SURFACE_ID.match(basename) next if match.nil? key = match[1] # define animation patterns mayuna[key] = Seriko.get_mayuna(config) end @mayuna = {} # create surface windows for surface_window in @window surface_window.destroy() end @window = [] @__surface = surface @maxsize = [maxwidth, maxheight] add_window(0, default_sakura, :config_alias => surface_alias, :mayuna => mayuna) add_window(1, default_kero, :config_alias => surface_alias, :mayuna => mayuna) end def get_menu_pixmap top_dir = @prefix name = @desc.get('menu.background.bitmap.filename') unless name.nil? name = name.gsub('\\', '/') path_background = File.join(top_dir, name) else path_background = nil end name = @desc.get('menu.sidebar.bitmap.filename') unless name.nil? name = name.gsub('\\', '/') path_sidebar = File.join(top_dir, name) else path_sidebar = nil end name = @desc.get('menu.foreground.bitmap.filename') unless name.nil? name = name.gsub('\\', '/') path_foreground = File.join(top_dir, name) else path_foreground = nil end align_background = @desc.get('menu.background.alignment') align_sidebar = @desc.get('menu.sidebar.alignment') align_foreground = @desc.get('menu.foreground.alignment') return path_background, path_sidebar, path_foreground, \ align_background, align_sidebar, align_foreground end def get_menu_fontcolor fontcolor_r = @desc.get('menu.background.font.color.r', :default => 0).to_i fontcolor_g = @desc.get('menu.background.font.color.g', :default => 0).to_i fontcolor_b = @desc.get('menu.background.font.color.b', :default => 0).to_i fontcolor_r = [0, [255, fontcolor_r].min].max fontcolor_g = [0, [255, fontcolor_g].min].max fontcolor_b = [0, [255, fontcolor_b].min].max background = [fontcolor_r, fontcolor_g, fontcolor_b] fontcolor_r = @desc.get('menu.foreground.font.color.r', :default => 0).to_i fontcolor_g = @desc.get('menu.foreground.font.color.g', :default => 0).to_i fontcolor_b = @desc.get('menu.foreground.font.color.b', :default => 0).to_i fontcolor_r = [0, [255, fontcolor_r].min].max fontcolor_g = [0, [255, fontcolor_g].min].max fontcolor_b = [0, [255, fontcolor_b].min].max foreground = [fontcolor_r, fontcolor_g, fontcolor_b] return background, foreground end def add_window(side, default_id, config_alias: nil, mayuna: {}) fail "assert" unless @window.length == side case side when 0 name = 'sakura' title = @parent.handle_request('GET', 'get_selfname') or \ "surface.#{name}" when 1 name = 'kero' title = @parent.handle_request('GET', 'get_keroname') or \ "surface.#{name}" else name = ("char#{side}") title = "surface.#{name}" end if config_alias.nil? surface_alias = nil else surface_alias = config_alias.get("#{name}.surface.alias") end # MAYUNA bind = {} for index in 0..127 group = @desc.get( "#{name}.bindgroup#{index}.name", :default => nil) default = @desc.get( "#{name}.bindgroup#{index}.default", :default => '0') bind[index] = [group, (default != '0')] unless group.nil? end @mayuna[name] = [] for index in 0..127 key = @desc.get("#{name}.menuitem#{index}", :default => nil) if key == '-' @mayuna[name] << [key, nil, 0] else begin key = Integer(key) rescue #pass else if bind.include?(key) group = bind[key][0].split(',', 2) @mayuna[name] << [key, group[1], bind[key][1]] end end end end skip_taskbar = (side >= 1) gtk_window = create_gtk_window(title, skip_taskbar) seriko = get_seriko(@__surface) tooltips = {} if @__tooltips.include?(name) tooltips = @__tooltips[name] end surface_window = SurfaceWindow.new( gtk_window, side, @desc, surface_alias, @__surface, tooltips, @__surfaces, seriko, @__region, mayuna, bind, default_id, @maxsize) surface_window.set_responsible(self) @window << surface_window end def get_mayuna_menu for side, index in [['sakura', 0], ['kero', 1]] for menu in @mayuna[side] if menu[0] != '-' menu[2] = @window[index].bind[menu[0]][1] end end end return @mayuna end def compose_elements(elements, config) error = nil for n in 0..255 key = ['element', n.to_s].join('') break unless config.include?(key) spec = [] for value in config[key].split(',', 0) spec << value.strip() end begin method, filename, x, y = spec x = Integer(x) y = Integer(y) rescue error = ('invalid element spec for ' + key + ': ' + config[key]) break end basename = File.basename(filename, ".*") ext = File.extname(filename) ext = ext.downcase unless ['.png', '.dgp', '.ddp'].include?(ext) error = ('unsupported file format for ' + key + ': ' + filename) break end basename = basename.downcase unless elements.include?(basename) error = (key + ' file not found: ' + filename) break end surface = elements[basename][0] if n.zero? # base surface surface_list = [surface] elsif ['overlay', 'overlayfast', 'interpolate', 'reduce', 'replace', 'asis'].include?(method) surface_list << [surface, x, y, method] elsif method == 'base' surface_list << [surface, x, y, method] else error = ('unknown method for ' + key + ': ' + method) break end Logging::Logging.debug(key + ': ' + method + ' ' + filename + ', x=' + x.to_i.to_s + ', y=' + y.to_i.to_s) end unless error.nil? Logging::Logging.error(error) surface_list = [] end return surface_list end def get_window(side) if @window.length > side return @window[side].get_window else return nil end end def reset_surface @window.map {|window| window.reset_surface } end def set_surface_default(side) if side.nil? for side in 0..@window.length-1 @window[side].set_surface_default() end elsif 0 <= side and side < @window.length @window[side].set_surface_default() end end def set_surface(side, surface_id) if @window.length > side @window[side].set_surface(surface_id) end end def get_surface(side) if @window.length > side return @window[side].get_surface() else return 0 end end def get_max_size(side) if @window.length > side return @window[side].get_max_size() else return @window[0].get_max_size() # XXX end end def get_surface_size(side) if @window.length > side return @window[side].get_surface_size() else return 0, 0 end end def get_surface_offset(side) if @window.length > side return @window[side].get_surface_offset() else return 0, 0 end end def get_touched_region(side, x, y) if @window.length > side return @window[side].get_touched_region(x, y) else return '' end end def get_center(side) if @window.length > side return @window[side].get_center() else return nil, nil end end def get_kinoko_center(side) if @window.length > side return @window[side].get_kinoko_center() else return nil, nil end end def reset_balloon_position for side in 0..@window.length-1 x, y = get_position(side) direction = @window[side].direction ox, oy = get_balloon_offset(side) @parent.handle_request( 'NOTIFY', 'set_balloon_direction', side, direction) if direction.zero? # left base_x = (x + ox) else w, h = get_surface_size(side) base_x = (x + w - ox) end base_y = (y + oy) @parent.handle_request( 'NOTIFY', 'set_balloon_position', side, base_x, base_y) end end def reset_position left, top, scrn_w, scrn_h = @parent.handle_request('GET', 'get_workarea') s0x, s0y, s0w, s0h = 0, 0, 0, 0 # XXX for side in 0..@window.length-1 align = get_alignment(side) w, h = get_max_size(side) if side.zero? # sakura x = (left + scrn_w - w) else b0w, b0h = @parent.handle_request( 'GET', 'get_balloon_size', side - 1) b1w, b1h = @parent.handle_request( 'GET', 'get_balloon_size', side) bpx, bpy = @parent.handle_request( 'GET', 'get_balloon_windowposition', side) o0x, o0y = get_balloon_offset(side - 1) o1x, o1y = get_balloon_offset(side) offset = [0, b1w - (b0w - o0x)].max if ((s0x + o0x - b0w) - offset - w + o1x) < left x = left else x = ((s0x + o0x - b0w) - offset - w + o1x) end end if align == 1 # top y = top else y = (top + scrn_h - h) end set_position(side, x, y) s0x, s0y, s0w, s0h = x, y, w, h # for next loop end end def set_position(side, x, y) if @window.length > side @window[side].set_position(x, y) end end def get_position(side) if @window.length > side return @window[side].get_position() else return 0, 0 end end def set_alignment_current for side in 0..@window.length-1 @window[side].set_alignment_current() end end def set_alignment(side, align) if @window.length > side @window[side].set_alignment(align) end end def get_alignment(side) if @window.length > side return @window[side].get_alignment() else return 0 end end def reset_alignment if @desc.get('seriko.alignmenttodesktop') == 'free' align = 2 else align = 0 end for side in 0..@window.length-1 set_alignment(side, align) end end def is_shown(side) if @window.length > side return @window[side].is_shown() else return false end end def show(side) if @window.length > side @window[side].show() end end def hide_all for side in 0..@window.length-1 @window[side].hide() end end def hide(side) if @window.length > side @window[side].hide() end end def raise_all for side in 0..@window.length-1 @window[side].raise end end def raise(side) if @window.length > side @window[side].raise end end def lower_all for side in 0..@window.length-1 @window[side].lower() end end def lower(side) if @window.length > side @window[side].lower() end end def invoke(side, actor_id) if @window.length > side @window[side].invoke(actor_id) end end def invoke_yen_e(side, surface_id) if @window.length > side @window[side].invoke_yen_e(surface_id) end end def invoke_talk(side, surface_id, count) if @window.length > side return @window[side].invoke_talk(surface_id, count) else return false end end def set_icon(path) return if path.nil? or not File.exists?(path) for window in @window window.get_window.set_icon(path) # XXX end end def get_mikire @mikire end def get_kasanari @kasanari end def get_name @name end def get_username if @desc.nil? return nil else return @desc.get('user.defaultname') end end def get_selfname if @desc.nil? return nil else return @desc.get('sakura.name') end end def get_selfname2 if @desc.nil? return nil else return @desc.get('sakura.name2') end end def get_keroname if @desc.nil? return nil else return @desc.get('kero.name') end end def get_friendname if @desc.nil? return nil else return @desc.get('sakura.friend.name') end end def get_balloon_offset(side) if @window.length > side x, y = @window[side].get_balloon_offset scale = @window[side].get_scale x = (x * scale / 100).to_i y = (y * scale / 100).to_i return x, y end return 0, 0 end def set_balloon_offset(side, offset) if @window.length > side @window[side].balloon_offset = offset end end def toggle_bind(args) side, bind_id = args @window[side].toggle_bind(bind_id) end def get_collision_area(side, part) if @window.length > side return @window[side].get_collision_area(part) end return nil end def check_mikire_kasanari unless is_shown(0) @mikire = @kasanari = 0 return end left, top, scrn_w, scrn_h = @parent.handle_request('GET', 'get_workarea') x0, y0 = get_position(0) s0w, s0h = get_surface_size(0) if (x0 + s0w / 3) < left or (x0 + s0w * 2 / 3) > (left + scrn_w) or \ (y0 + s0h / 3) < top or (y0 + s0h * 2 / 3) > (top + scrn_h) @mikire = 1 else @mikire = 0 end unless is_shown(1) @kasanari = 0 return end x1, y1 = get_position(1) s1w, s1h = get_surface_size(1) if (x0 < (x1 + s1w / 2) and (x1 + s1w / 2) < (x0 + s0w) and y0 < (y1 + s1h / 2) and (y1 + s1h / 2) < (y0 + s0h)) or (x1 < (x0 + s0w / 2) and (x0 + s0w / 2) < (x1 + s1w) and y1 < (y0 + s0h / 2) and (y0 + s0h / 2) < (y1 + s1h)) @kasanari = 1 else @kasanari = 0 end end end class SurfaceWindow < MetaMagic::Holon attr_reader :bind def initialize(window, side, desc, surface_alias, surface_info, tooltips, surfaces, seriko, region, mayuna, bind, default_id, maxsize) super("") # FIXME @handlers = {} @window = window @maxsize = maxsize @side = side @desc = desc @alias = surface_alias @tooltips = tooltips @align = 0 @__current_part = '' unless @alias.nil? or @alias[default_id].nil? default_id = @alias[default_id][0] end @surface_info = surface_info @surface_id = default_id @surfaces = surfaces @image_surface = nil # XXX @seriko = Seriko::Controller.new(seriko) @seriko.set_responsible(self) @region = region @mayuna = mayuna @bind = bind @default_id = default_id @__shown = false @window_offset = [0, 0] @position = [0, 0] @__direction = 0 @dragged = false @x_root = nil @y_root = nil @click_count = 0 @__balloon_offset = nil @reshape = true @window.signal_connect('leave_notify_event') do |w, e| next window_leave_notify(w, e) # XXX end @window.signal_connect('enter_notify_event') do |w, e| window_enter_notify(w, e) # XXX next true end @darea = @window.darea @darea.set_events(Gdk::EventMask::EXPOSURE_MASK| Gdk::EventMask::BUTTON_PRESS_MASK| Gdk::EventMask::BUTTON_RELEASE_MASK| Gdk::EventMask::POINTER_MOTION_MASK| Gdk::EventMask::POINTER_MOTION_HINT_MASK| Gdk::EventMask::SCROLL_MASK) @darea.signal_connect('draw') do |w, e| redraw(w, e) next true end @darea.signal_connect('button_press_event') do |w, e| next button_press(w, e) end @darea.signal_connect('button_release_event') do |w, e| next button_release(w, e) end @darea.signal_connect('motion_notify_event') do |w, e| next motion_notify(w, e) end @darea.signal_connect('drag_data_received') do |widget, context, x, y, data, info, time| drag_data_received(widget, context, x, y, data, info, time) next true end @darea.signal_connect('scroll_event') do |w, e| next scroll(w, e) end if @side.zero? screen = @window.screen screen.signal_connect('size-changed') do |scr| display_changed(scr) next true end end # DnD data types dnd_targets = [['text/uri-list', 0, 0]] @darea.drag_dest_set(Gtk::DestDefaults::ALL, dnd_targets, Gdk::DragAction::COPY) @darea.drag_dest_add_uri_targets() end def get_seriko @seriko end def get_window @window end def get_surface_id @surface_id end def display_changed(screen) return unless @side.zero? @reshape = true # XXX @parent.handle_request('NOTIFY', 'reset_position') # XXX left, top, scrn_w, scrn_h = @parent.handle_request('GET', 'get_workarea') @parent.handle_request( 'NOTIFY', 'notify_event', 'OnDisplayChange', Gdk.Visual.get_best_depth(), scrn_w, scrn_h) end def direction @__direction end def direction=(value) @__direction = value # 0: left, 1: right @parent.handle_request( 'NOTIFY', 'set_balloon_direction', @side, value) end def get_scale @parent.handle_request('GET', 'get_preference', 'surface_scale') end def get_balloon_offset if @__balloon_offset.nil? path, config = @surface_info[['surface', @surface_id].join('')] side = @side case side when 0 name = 'sakura' x = config.get(name + '.balloon.offsetx').to_i y = config.get(name + '.balloon.offsety').to_i when 1 name = 'kero' x = config.get(name + '.balloon.offsetx').to_i y = config.get(name + '.balloon.offsety').to_i else name = ('char' + side.to_i.to_s) x, y = nil, nil # XXX end if x.nil? x = @desc.get(name + '.balloon.offsetx') if x.nil? x = 0 else x = x.to_i end end if y.nil? y = @desc.get(name + '.balloon.offsety') if y.nil? y = 0 else y = y.to_i end end else x, y = @__balloon_offset end return x, y end def balloon_offset=(offset) @__balloon_offset = offset # (x, y) @parent.handle_request('NOTIFY', 'reset_balloon_position') end def drag_data_received(widget, context, x, y, data, info, time) filelist = [] for uri in data.uris uri_parsed = URI.parse(uri) pathname = URI.unescape(uri_parsed.path) if uri_parsed.scheme == 'file' and File.exists?(pathname) filelist << pathname end end unless filelist.empty? @parent.handle_request( 'NOTIFY', 'enqueue_event', 'OnFileDrop2', filelist.join(1.chr), @side) end end def append_actor(frame, actor) @seriko.append_actor(frame, actor) end def invoke(actor_id, update: 0) @seriko.invoke(self, actor_id, :update => update) end def invoke_yen_e(surface_id) @seriko.invoke_yen_e(self, surface_id) end def invoke_talk(surface_id, count) @seriko.invoke_talk(self, surface_id, count) end def reset_surface surface_id = get_surface() set_surface(surface_id) end def set_surface_default set_surface(@default_id) end def set_surface(surface_id) prev_id = @surface_id if not @alias.nil? and @alias.include?(surface_id) aliases = @alias[surface_id] unless aliases.empty? surface_id = aliases.sample end end if surface_id == '-2' @seriko.terminate(self) end if ['-1', '-2'].include?(surface_id) #pass elsif not @surfaces.include?(surface_id) @surface_id = @default_id else @surface_id = surface_id end @reshape = true @seriko.reset(self, surface_id) # define collision areas @collisions = @region[@surface_id] # update window offset x, y = @position # XXX: without window_offset w, h = get_surface_size(:surface_id => @surface_id) dw, dh = get_max_size() xoffset = ((dw - w) / 2) case get_alignment() when 0 yoffset = (dh - h) left, top, scrn_w, scrn_h = @parent.handle_request('GET', 'get_workarea') y = (top + scrn_h - dh) when 1 yoffset = 0 else yoffset = ((dh - h) / 2) end @window_offset = [xoffset, yoffset] @seriko.start(self) # relocate window unless @dragged # XXX set_position(x, y) end if @side < 2 @parent.handle_request('NOTIFY', 'notify_observer', 'set surface') end w, h = get_surface_size(:surface_id => @surface_id) new_x, new_y = get_position() @parent.handle_request( 'NOTIFY', 'notify_event', 'OnSurfaceChange', @parent.handle_request('GET', 'get_surface_id', 0), @parent.handle_request('GET', 'get_surface_id', 1), [@side, @surface_id, w, h].join(','), prev_id.to_s, [new_x, new_y, new_x + w, new_y + h].join(',')) update_frame_buffer() #XXX end def iter_mayuna(surface_width, surface_height, mayuna, done) mayuna_list = [] # XXX: FIXME for surface_id, interval, method, args in mayuna.get_patterns case method when 'bind', 'add' if @surfaces.include?(surface_id) dest_x, dest_y = args mayuna_list << [method, surface_id, dest_x, dest_y] end when 'reduce' if @surfaces.include?(surface_id) dest_x, dest_y = args mayuna_list << [method, surface_id, dest_x, dest_y] end when 'insert' index = args[0] for actor in @mayuna[@surface_id] actor_id = actor.get_id() if actor_id == index if @bind.include?(actor_id) and @bind[actor_id][1] and \ not done.include?(actor_id) done << actor_id for result in iter_mayuna(surface_width, surface_height, actor, done) mayuna_list << result end else break end end end else fail RuntimeError('should not reach here') end end return mayuna_list end def create_surface_from_file(surface_id, is_asis: false) fail "assert" unless @surfaces.include?(surface_id) if is_asis use_pna = false is_pnr = false else use_pna = (not @parent.handle_request('GET', 'get_preference', 'use_pna').zero?) is_pnr = true end begin surface = Pix.create_surface_from_file( @surfaces[surface_id][0], :is_pnr => is_pnr, :use_pna => use_pna) rescue Logging::Logging.debug('cannot load surface #' + surface_id.to_s) return Pix.create_blank_surface(100, 100) end for element, x, y, method in @surfaces[surface_id][1, @surfaces[surface_id].length - 1] begin if method == 'asis' is_pnr = false use_pna = false else is_pnr = true use_pna = (not @parent.handle_request( 'GET', 'get_preference', 'use_pna').zero?) end overlay = Pix.create_surface_from_file( element, :is_pnr => is_pnr, :use_pna => use_pna) rescue next end cr = Cairo::Context.new(surface) op = { 'base' => Cairo::OPERATOR_SOURCE, # XXX 'overlay' => Cairo::OPERATOR_OVER, 'overlayfast' => Cairo::OPERATOR_ATOP, 'interpolate' => Cairo::OPERATOR_SATURATE, 'reduce' => Cairo::OPERATOR_DEST_IN, 'replace' => Cairo::OPERATOR_SOURCE, 'asis' => Cairo::OPERATOR_OVER, }[method] cr.set_operator(op) cr.set_source(overlay, x, y) if ['overlay', 'overlayfast'].include?(method) cr.mask(overlay, x, y) else cr.paint() end end return surface end def get_image_surface(surface_id, is_asis: false) unless @surfaces.include?(surface_id) Logging::Logging.debug('cannot load surface #' + surface_id.to_s) return Pix.create_blank_surface(100, 100) end return create_surface_from_file(surface_id, :is_asis => is_asis) end def draw_region(cr) return if @collisions.nil? cr.save() # translate the user-space origin cr.translate(*@window.get_draw_offset) # XXX scale = get_scale cr.scale(scale / 100.0, scale / 100.0) for part, x1, y1, x2, y2 in @collisions unless @parent.handle_request('GET', 'get_preference', 'check_collision_name').zero? cr.set_operator(Cairo::OPERATOR_SOURCE) cr.set_source_rgba(0.4, 0.0, 0.0, 1.0) # XXX cr.move_to(x1 + 2, y1) font_desc = Pango::FontDescription.new font_desc.set_size(8 * Pango::SCALE) layout = cr.create_pango_layout layout.set_font_description(font_desc) layout.set_wrap(:char) # XXX layout.set_text(part) cr.show_pango_layout(layout) end cr.set_operator(Cairo::OPERATOR_ATOP) cr.set_source_rgba(0.2, 0.0, 0.0, 0.4) # XXX cr.rectangle(x1, y1, x2 - x1, y2 - y1) cr.fill_preserve() cr.set_operator(Cairo::OPERATOR_SOURCE) cr.set_source_rgba(0.4, 0.0, 0.0, 0.8) # XXX cr.stroke() end cr.restore() end def create_image_surface(surface_id) if surface_id.nil? surface_id = @surface_id end if @mayuna.include?(surface_id) and @mayuna[surface_id] surface = get_image_surface(surface_id) surface_width = surface.width surface_height = surface.height done = [] for actor in @mayuna[surface_id] actor_id = actor.get_id() if @bind.include?(actor_id) and @bind[actor_id][1] and \ not done.include?(actor_id) done << actor_id for method, mayuna_id, dest_x, dest_y in iter_mayuna(surface_width, surface_height, actor, done) mayuna_surface = get_image_surface(mayuna_id) cr = Cairo::Context.new(surface) if ['bind', 'add'].include?(method) cr.set_source(mayuna_surface, dest_x, dest_y) cr.mask(mayuna_surface, dest_x, dest_y) elsif method == 'reduce' cr.set_operator(Cairo::OPERATOR_DEST_IN) cr.set_source(mayuna_surface, dest_x, dest_y) cr.paint() else fail RuntimeError('should not reach here') end end end end else surface = get_image_surface(surface_id) end return surface end def update_frame_buffer return if @parent.handle_request('GET', 'lock_repaint') @reshape = true # FIXME: depends on Seriko new_surface = create_image_surface(@seriko.get_base_id) fail "assert" if new_surface.nil? # update collision areas @collisions = @region[@seriko.get_base_id] # draw overlays for surface_id, x, y, method in @seriko.iter_overlays() begin overlay_surface = get_image_surface( surface_id, :is_asis => (method == 'asis')) rescue next end # overlay surface cr = Cairo::Context.new(new_surface) op = { 'base' => Cairo::OPERATOR_SOURCE, # XXX 'overlay' => Cairo::OPERATOR_OVER, 'overlayfast' => Cairo::OPERATOR_ATOP, 'interpolate' => Cairo::OPERATOR_SATURATE, 'reduce' => Cairo::OPERATOR_DEST_IN, 'replace' => Cairo::OPERATOR_SOURCE, 'asis' => Cairo::OPERATOR_OVER, }[method] cr.set_operator(op) cr.set_source(overlay_surface, x, y) if ['overlay', 'overlayfast'].include?(method) cr.mask(overlay_surface, x, y) else cr.paint() end end @image_surface = new_surface @darea.queue_draw() end def redraw(darea, cr) return if @image_surface.nil? # XXX @window.set_surface(cr, @image_surface, get_scale) unless @parent.handle_request('GET', 'get_preference', 'check_collision').zero? draw_region(cr) end @window.set_shape(cr, @reshape) @reshape = false end def remove_overlay(actor) @seriko.remove_overlay(actor) end def add_overlay(actor, surface_id, x, y, method) @seriko.add_overlay(self, actor, surface_id, x, y, method) end def move_surface(xoffset, yoffset) return if @parent.handle_request('GET', 'lock_repaint') x, y = get_position() @window.move(x + xoffset, y + yoffset) if @side < 2 args = [@side, xoffset, yoffset] @parent.handle_request( 'NOTIFY', 'notify_observer', 'move surface', :args => args) # animation end end def get_collision_area(part) for p, x1, y1, x2, y2 in @collisions if p == part scale = get_scale x1 = (x1 * scale / 100).to_i x2 = (x2 * scale / 100).to_i y1 = (y1 * scale / 100).to_i y2 = (y2 * scale / 100).to_i return x1, y1, x2, y2 end end return nil end def get_surface @surface_id end def get_max_size left, top, scrn_w, scrn_h = @parent.handle_request('GET', 'get_workarea') w, h = @maxsize scale = get_scale w = [scrn_w, [8, (w * scale / 100).to_i].max].min h = [scrn_h, [8, (h * scale / 100).to_i].max].min return w, h end def get_surface_size(surface_id: nil) if surface_id.nil? surface_id = @surface_id end unless @surfaces.include?(surface_id) w, h = 100, 100 # XXX else w, h = Pix.get_png_size(@surfaces[surface_id][0]) end scale = get_scale w = [8, (w * scale / 100).to_i].max h = [8, (h * scale / 100).to_i].max return w, h end def get_surface_offset @window_offset end def get_touched_region(x, y) return '' if @collisions.nil? for part, x1, y1, x2, y2 in @collisions if x1 <= x and x <= x2 and y1 <= y and y <= y2 Logging::Logging.debug(part + ' touched') return part end end return '' end def __get_with_scaling(name) basename = ['surface', @surface_id].join('') path, config = @surface_info[basename] value = config.get(name) unless value.nil? scale = get_scale value = (value.to_f * scale / 100) end return value end def get_center centerx = __get_with_scaling('point.centerx') centery = __get_with_scaling('point.centery') unless centerx.nil? centerx = centerx.to_i end unless centery.nil? centery = centery.to_i end return centerx, centery end def get_kinoko_center centerx = __get_with_scaling('point.kinoko.centerx') centery = __get_with_scaling('point.kinoko.centery') unless centerx.nil? centerx = centerx.to_i end unless centery.nil? centery = centery.to_i end return centerx, centery end def set_position(x, y) return if @parent.handle_request('GET', 'lock_repaint') @position = [x, y] new_x, new_y = get_position() @window.move(new_x, new_y) left, top, scrn_w, scrn_h = @parent.handle_request('GET', 'get_workarea') if x > (left + scrn_w / 2) new_direction = 0 else new_direction = 1 end @__direction = new_direction ox, oy = get_balloon_offset # without scaling scale = get_scale ox = (ox * scale / 100).to_i oy = (oy * scale / 100).to_i if new_direction.zero? # left base_x = (new_x + ox) else w, h = get_surface_size() base_x = (new_x + w - ox) end base_y = (new_y + oy) @parent.handle_request( 'NOTIFY', 'set_balloon_position', @side, base_x, base_y) @parent.handle_request('NOTIFY', 'notify_observer', 'set position') @parent.handle_request('NOTIFY', 'check_mikire_kasanari') end def get_position ## FIXME: position with offset(property) @position.zip(@window_offset).map {|x, y| x + y } end def set_alignment_current set_alignment(get_alignment()) end def set_alignment(align) @align = align if [0, 1, 2].include?(align) return if @dragged # XXX: position will be reset after button release event case align when 0 left, top, scrn_w, scrn_h = @parent.handle_request('GET', 'get_workarea') sw, sh = get_max_size() sx, sy = @position # XXX: without window_offset sy = (top + scrn_h - sh) set_position(sx, sy) when 1 left, top, scrn_w, scrn_h = @parent.handle_request('GET', 'get_workarea') sx, sy = @position # XXX: without window_offset sy = top set_position(sx, sy) else # free #pass end end def get_alignment @align end def destroy @seriko.destroy() @window.destroy() end def is_shown @__shown end def show return if @parent.handle_request('GET', 'lock_repaint') return if @__shown @reshape = true @__shown = true x, y = get_position() @window.move(x, y) # XXX: call before showing the window @window.show() @parent.handle_request('NOTIFY', 'notify_observer', 'show', :args => [@side]) @parent.handle_request('NOTIFY', 'notify_observer', 'raise', :args => [@side]) end def hide return unless @__shown @window.hide() @__shown = false @parent.handle_request( 'NOTIFY', 'notify_observer', 'hide', :args => [@side]) end def raise @window.window.raise @parent.handle_request('NOTIFY', 'notify_observer', 'raise', :args => [@side]) end def lower @window.window.lower() @parent.handle_request('NOTIFY', 'notify_observer', 'lower', :args => [@side]) end def button_press(window, event) @parent.handle_request('NOTIFY', 'reset_idle_time') x, y = @window.winpos_to_surfacepos( event.x.to_i, event.y.to_i, get_scale) @x_root = event.x_root @y_root = event.y_root # automagical raise @parent.handle_request('NOTIFY', 'notify_observer', 'raise', :args => [@side]) if event.event_type == Gdk::EventType::BUTTON2_PRESS @click_count = 2 else # XXX @click_count = 1 end if [1, 2, 3].include?(event.button) num_button = [0, 2, 1][event.button - 1] @parent.handle_request('NOTIFY', 'notify_event', 'OnMouseDown', x, y, 0, @side, @__current_part, num_button, 'mouse') # FIXME end if [2, 8, 9].include?(event.button) ex_button = { 2 => 'middle', 8 => 'xbutton1', 9 => 'xbutton2' }[event.button] @parent.handle_request('NOTIFY', 'notify_event', 'OnMouseDownEx', x, y, 0, @side, @__current_part, ex_button, 'mouse') # FIXME end return true end CURSOR_HAND1 = Gdk::Cursor.new(Gdk::CursorType::HAND1) def button_release(window, event) x, y = @window.winpos_to_surfacepos( event.x.to_i, event.y.to_i, get_scale) if @dragged @dragged = false set_alignment_current() @parent.handle_request( 'NOTIFY', 'notify_event', 'OnMouseDragEnd', x, y, '', @side, @__current_part, '') end @x_root = nil @y_root = nil if @click_count > 0 @parent.handle_request('NOTIFY', 'notify_surface_click', event.button, @click_count, @side, x, y) @click_count = 0 end return true end def motion_notify(darea, event) x, y, state = event.x, event.y, event.state x, y = @window.winpos_to_surfacepos(x, y, get_scale) part = get_touched_region(x, y) if part != @__current_part if part == '' @window.set_tooltip_text('') @darea.window.set_cursor(nil) @parent.handle_request( 'NOTIFY', 'notify_event', 'OnMouseLeave', x, y, '', @side, @__current_part) else if @tooltips.include?(part) tooltip = @tooltips[part] @window.set_tooltip_text(tooltip) else @window.set_tooltip_text('') end @darea.window.set_cursor(CURSOR_HAND1) @parent.handle_request( 'NOTIFY', 'notify_event', 'OnMouseEnter', x, y, '', @side, part) end end @__current_part = part unless @parent.handle_request('GET', 'busy') if (state & Gdk::ModifierType::BUTTON1_MASK).nonzero? unless @x_root.nil? or @y_root.nil? unless @dragged @parent.handle_request( 'NOTIFY', 'notify_event', 'OnMouseDragStart', x, y, '', @side, @__current_part, '') end @dragged = true x_delta = (event.x_root - @x_root).to_i y_delta = (event.y_root - @y_root).to_i x, y = @position # XXX: without window_offset set_position(x + x_delta, y + y_delta) @x_root = event.x_root @y_root = event.y_root end elsif (state & Gdk::ModifierType::BUTTON2_MASK).nonzero? or \ (state & Gdk::ModifierType::BUTTON3_MASK).nonzero? #pass else @parent.handle_request('NOTIFY', 'notify_surface_mouse_motion', @side, x, y, part) end end Gdk::Event.request_motions(event) if event.is_hint == 1 return true end def scroll(darea, event) x, y = @window.winpos_to_surfacepos( event.x.to_i, event.y.to_i, get_scale) case event.direction when Gdk::ScrollDirection::UP count = 1 when Gdk::ScrollDirection::DOWN count = -1 else count = 0 end unless count.zero? part = get_touched_region(x, y) @parent.handle_request('NOTIFY', 'notify_event', 'OnMouseWheel', x, y, count, @side, part) end return true end def toggle_bind(bind_id) if @bind.include?(bind_id) current = @bind[bind_id][1] @bind[bind_id][1] = (not current) group = @bind[bind_id][0].split(',', 2) if @bind[bind_id][1] @parent.handle_request('NOTIFY', 'notify_event', 'OnDressupChanged', @side, group[1], 1, group[0]) else @parent.handle_request('NOTIFY', 'notify_event', 'OnDressupChanged', @side, group[1], 0, group[0]) end reset_surface() end end def window_enter_notify(window, event) x, y, state = event.x, event.y, event.state x, y = @window.winpos_to_surfacepos(x, y, get_scale) @parent.handle_request('NOTIFY', 'notify_event', 'OnMouseEnterAll', x, y, '', @side, '') end def window_leave_notify(window, event) x, y, state = event.x, event.y, event.state x, y = @window.winpos_to_surfacepos(x, y, get_scale) if @__current_part != '' # XXX @parent.handle_request( 'NOTIFY', 'notify_event', 'OnMouseLeave', x, y, '', @side, @__current_part) @__current_part = '' end @parent.handle_request( 'NOTIFY', 'notify_event', 'OnMouseLeaveAll', x, y, '', @side, '') return true end end end ninix-aya-5.0.9/lib/ninix/sstp.rb0000644000175000017500000002716013416507430015034 0ustar shyshy# -*- coding: utf-8 -*- # # Copyright (C) 2001, 2002 by Tamito KAJIYAMA # Copyright (C) 2002, 2003 by MATSUMURA Namihiko # Copyright (C) 2002-2019 by Shyouzou Sugitani # # This program is free software; you can redistribute it and/or modify it # under the terms of the GNU General Public License (version 2) as # published by the Free Software Foundation. It is distributed in the # hope that it will be useful, but WITHOUT ANY WARRANTY; without even the # implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR # PURPOSE. See the GNU General Public License for more details. # require "socket" require_relative "entry_db" require_relative "script" require_relative "version" require_relative "sstplib" require_relative "logging" module SSTP class SSTPServer < TCPServer attr_reader :socket def initialize(address) @parent = nil super(address) setsockopt(Socket::SOL_SOCKET, Socket::SO_REUSEADDR, true) @request_handler = nil end def set_responsible(parent) @parent = parent end def handle_request(event_type, event, *arglist) @parent&.handle_request(event_type, event, *arglist) end def set_request_handler(handler) ## FIXME @request_handler = handler end def has_request_handler not @request_handler.nil? end def send_response(code, data: nil) begin @request_handler.send_response(code) @request_handler.write(data) unless data.nil? # FIXME @request_handler.shutdown(Socket::SHUT_WR) # XXX rescue #pass end @request_handler = nil end def send_answer(value) charset = @request_handler.get_charset answer = "#{value.encode(charset, :invalid => :replace, :undef => :replace)}\r\n\r\n" send_response(200, :data => answer) # OK end def send_no_content send_response(204) # No Content end def send_sstp_break send_response(210) # Break end def send_timeout send_response(408) # Request Timeout end def close # NOP end end class SSTPRequestHandler < SSTPLib::BaseSSTPRequestHandler def handle(line) unless @server.handle_request('GET', 'get_sakura_cantalk') @error = nil @version = nil return unless parse_request(line) send_error(512) else super(line) end end # SEND def do_SEND_1_0 handle_send(1.0) end def do_SEND_1_1 handle_send(1.1) end def do_SEND_1_2 handle_send(1.2) end def do_SEND_1_3 handle_send(1.3) end def do_SEND_1_4 handle_send(1.4) end def handle_send(version) return unless check_decoder() sender = get_sender() return if sender.nil? case version when 1.3 handle = get_handle() return if handle.nil? else handle = nil end script_odict = get_script_odict() return if script_odict.nil? case version when 1.0, 1.1 entry_db = nil when 1.2, 1.3, 1.4 entry_db = get_entry_db() return if entry_db.nil? end enqueue_request(sender, nil, handle, script_odict, entry_db) end # NOTIFY def do_NOTIFY_1_0 handle_notify(1.0) end def do_NOTIFY_1_1 handle_notify(1.1) end def handle_notify(version) script_odict = {} return unless check_decoder() sender = get_sender() return if sender.nil? event = get_event() return if event.nil? case version when 1.0 entry_db = nil when 1.1 script_odict = get_script_odict() return if script_odict.nil? entry_db = get_entry_db() return if entry_db.nil? end enqueue_request(sender, event, nil, script_odict, entry_db) end def enqueue_request(sender, event, handle, script_odict, entry_db) sock_domain, remote_port, remote_hostname, remote_ip = @fp.peeraddr address = remote_hostname # XXX if entry_db.nil? or entry_db.is_empty() send_response(200) # OK show_sstp_marker, use_translator = get_options() @server.handle_request( 'NOTIFY', 'enqueue_request', event, script_odict, sender, handle, address, show_sstp_marker, use_translator, entry_db, nil) elsif @server.has_request_handler send_response(409) # Conflict else show_sstp_marker, use_translator = get_options() @server.handle_request( 'NOTIFY', 'enqueue_request', event, script_odict, sender, handle, address, show_sstp_marker, use_translator, entry_db, @server) @server.set_request_handler(self) # keep alive end end PROHIBITED_TAGS = ['\j', '\-', '\+', '\_+', '\!', '\8', '\_v', '\C'] def check_script(script) unless local_request() parser = Script::Parser.new nodes = [] while true begin nodes.concat(parser.parse(script)) rescue Script::ParserError => e done, script = e.get_item nodes.concat(done) else break end end nodes.each do |node| next unless node[0] == Script::SCRIPT_TAG if PROHIBITED_TAGS.include?(node[1]) send_response(400) # Bad Request log_error("Script: tag #{node[1]} not allowed") return true end end end return false end def get_script_odict script_odict = {} # Ordered Hash if_ghost = nil @headers.each do |name, value| case name when 'IfGhost' if_ghost = value when 'Script' # nop else if_ghost = nil next end script = value.to_s return if check_script(script) script_odict[if_ghost || ''] = script if_ghost = nil end return script_odict end def get_entry_db entry_db = EntryDB::EntryDatabase.new @headers.each do |key, value| next unless key == "Entry" entry = value.split(',', 2) if entry.length != 2 send_response(400) # Bad Request return nil end entry_db.add(entry[0].strip(), entry[1].strip()) end return entry_db end def get_event event = @headers.reverse.assoc("Event")&.at(1) if event.nil? send_response(400) # Bad Request log_error('Event: header field not found') return nil end buf = [event] (0..7).each do |i| key = "Reference#{i}" value = @headers.reverse.assoc(key)&.at(1) buf << value end return buf end def get_sender sender = @headers.reverse.assoc('Sender')&.at(1) if sender.nil? send_response(400) # Bad Request log_error('Sender: header field not found') return nil end return sender end def get_handle path = @headers.assoc("HWnd")&.at(1) if path.nil? send_response(400) # Bad Request log_error('HWnd: header field not found') return nil end handle = Socket.new(Socket::AF_UNIX, Socket::SOCK_STREAM) begin handle.connect(path) rescue SystemCallError handle = nil # discard socket object Logging::Logging.error('cannot open Unix socket: ' + path) end if handle.nil? send_response(400) # Bad Request log_error('Invalid HWnd: header field') return nil end return handle end def get_charset @headers.reverse.assoc('Charset')&.at(1) || 'Shift_JIS' # XXX end def check_decoder charset = get_charset return true if Encoding.name_list.include?(charset) send_response(420, :data => 'Refuse (unsupported charset)') log_error("Unsupported charset #{charset}") return false end def get_options show_sstp_marker = use_translator = true options = (@headers.reverse.assoc("Option")&.at(1) || "").split(",", 0) options.each do |option| option = option.strip() case option when 'nodescript' show_sstp_marker = false if local_request() when 'notranslate' use_translator = false end end return show_sstp_marker, use_translator end def local_request sock_domain, remote_port, remote_hostname, remote_ip = @fp.peeraddr remote_ip == "127.0.0.1" end # EXECUTE def do_EXECUTE_1_0 handle_command() end def do_EXECUTE_1_2 handle_command() end def do_EXECUTE_1_3 unless local_request() sock_domain, remote_port, remote_hostname, remote_ip = @fp.peeraddr send_response(420) log_error("Unauthorized EXECUTE/1.3 request from #{remote_hostname}") return end handle_command() end def shutdown(how) @fp.shutdown(how) end def write(data) @fp.write(data) end def handle_command return unless check_decoder() sender = get_sender() return if sender.nil? command = get_command() charset = get_charset charset = charset.to_s case command when nil return when 'getname' send_response(200) name = @server.handle_request('GET', 'get_ghost_name') @fp.write(name.encode( charset, :invalid => :replace, :undef => :replace)) @fp.write("\r\n") @fp.write("\r\n") when 'getversion' send_response(200) @fp.write("ninix-aya ") @fp.write(Version.VERSION.encode( charset, :invalid => :replace, :undef => :replace)) @fp.write("\r\n") @fp.write("\r\n") when 'quiet' send_response(200) @server.handle_request('NOTIFY', 'keep_silence', true) when 'restore' send_response(200) @server.handle_request('NOTIFY', 'keep_silence', false) when 'getnames' send_response(200) for name in @server.handle_request('GET', 'get_ghost_names') @fp.write(name.encode( charset, :invalid => :replace, :undef => :replace)) @fp.write("\r\n") end @fp.write("\r\n") when 'checkqueue' send_response(200) count, total = @server.handle_request( 'GET', 'check_request_queue', sender) @fp.write(count.to_s.encode( charset, :invalid => :replace, :undef => :replace)) @fp.write("\r\n") @fp.write(total.to_s.encode( charset, :invalid => :replace, :undef => :replace)) @fp.write("\r\n") @fp.write("\r\n") else send_response(501) # Not Implemented log_error("Not Implemented (#{command})") end end def get_command command = @headers.reverse.assoc('Command')&.at(1) if command.nil? send_response(400) # Bad Request log_error('Command: header field not found') return nil end return command.downcase end def do_COMMUNICATE_1_1 return unless check_decoder() sender = get_sender() return if sender.nil? sentence = get_sentence() return if sentence.nil? send_response(200) # OK @server.handle_request( 'NOTIFY', 'enqueue_event', 'OnCommunicate', sender, sentence) return end def get_sentence sentence = @headers.reverse.assoc("Sentence")&.at(1) if sentence.nil? send_response(400) # Bad Request log_error('Sentence: header field not found') return nil end return sentence end end end ninix-aya-5.0.9/lib/ninix/home.rb0000644000175000017500000006015413416507430014773 0ustar shyshy# -*- coding: utf-8 -*- # # Copyright (C) 2001, 2002 by Tamito KAJIYAMA # Copyright (C) 2002, 2003 by MATSUMURA Namihiko # Copyright (C) 2002-2019 by Shyouzou Sugitani # # This program is free software; you can redistribute it and/or modify it # under the terms of the GNU General Public License (version 2) as # published by the Free Software Foundation. It is distributed in the # hope that it will be useful, but WITHOUT ANY WARRANTY; without even the # implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR # PURPOSE. See the GNU General Public License for more details. # require_relative "config" require_relative "alias" require_relative "dll" require_relative "logging" module Home def self.get_ninix_home() File.join(File.expand_path('~'), '.ninix') end def self.get_archive_dir() File.join(get_ninix_home, 'archive') end def self.get_pango_fontrc() File.join(get_ninix_home, 'pango_fontrc') end def self.get_preferences() File.join(get_ninix_home, 'preferences') end def self.get_normalized_path(path) path = path.gsub("\\", '/') path = path.downcase if File.absolute_path(path) != path #FIXME: expand_path is NOT equivalent for os.path.normpath #return os.path.normpath(os.fsencode(path)) return path end def self.load_config() return nil unless File.exists?(get_ninix_home()) ghosts = search_ghosts balloons = search_balloons nekoninni = search_nekoninni katochan = search_katochan kinoko = search_kinoko return ghosts, balloons, nekoninni, katochan, kinoko end def self.get_shiori() table = {} shiori_lib = DLL::Library.new('shiori', :saori_lib => nil) path = DLL.get_path Dir.foreach(path, :encoding => 'UTF-8') do |filename| next if filename == '..' or filename == '.' if File.readable_real?(File.join(path, filename)) name = nil basename = File.basename(filename, ".*") ext = File.extname(filename) ext = ext.downcase name = basename if ['.rb'].include?(ext) unless name.nil? or table.include?(name) shiori = shiori_lib.request(['', name]) table[name] = shiori unless shiori.nil? end end end return table end def self.search_ghosts(target: nil, check_shiori: true) home_dir = get_ninix_home() ghosts = {} unless target.nil? dirlist = [] dirlist += target else begin dirlist = [] Dir.foreach(File.join(home_dir, 'ghost'), :encoding => 'UTF-8') do |file| next if file == '..' or file == '.' dirlist << file end rescue SystemCallError dirlist = [] end end shiori_table = get_shiori() for subdir in dirlist prefix = File.join(home_dir, 'ghost', subdir) ghost_dir = File.join(prefix, 'ghost', 'master') desc = read_descript_txt(ghost_dir) desc = NConfig.null_config() if desc.nil? shiori_dll = desc.get('shiori') # find a pseudo AI, shells, and a built-in balloon candidate = { 'name' => '', 'score' => 0 } # SHIORI compatible modules for name, shiori in shiori_table.each_entry score = shiori.find(ghost_dir, shiori_dll).to_i if score > candidate['score'] candidate['name'] = name candidate['score'] = score end end shell_name, surface_set = find_surface_set(prefix) next if check_shiori and candidate['score'].zero? shiori_name = candidate['name'] if desc.get('name') == 'default' pos = 0 else pos = ghosts.length end use_makoto = find_makoto_dll(ghost_dir) ## FIXME: check surface_set ghosts[subdir] = [desc, ghost_dir, use_makoto, surface_set, prefix, shiori_dll, shiori_name] end return ghosts end def self.search_balloons(target: nil) home_dir = get_ninix_home() balloons = {} balloon_dir = File.join(home_dir, 'balloon') unless target.nil? dirlist = [] dirlist += target else begin dirlist = [] Dir.foreach(balloon_dir, :encoding => 'UTF-8') do |file| next if file == '..' or file == '.' dirlist << file end rescue SystemCallError dirlist = [] end end for subdir in dirlist path = File.join(balloon_dir, subdir) next unless File.directory?(path) desc = read_descript_txt(path) # REQUIRED next if desc.nil? balloon_info = read_balloon_info(path) # REQUIRED next if balloon_info.empty? if balloon_info.include?('balloon_dir') # XXX Logging::Logging.warninig('Oops: balloon id confliction') next else balloon_info['balloon_dir'] = [subdir, NConfig.null_config()] end balloons[subdir] = [desc, balloon_info] end return balloons end def self.search_nekoninni() home_dir = get_ninix_home() buf = [] skin_dir = File.join(home_dir, 'nekodorif/skin') begin dirlist = [] Dir.foreach(skin_dir, :encoding => 'UTF-8') do |file| next if file == '..' or file == '.' dirlist << file end rescue SystemCallError dirlist = [] end for subdir in dirlist nekoninni = read_profile_txt(File.join(skin_dir, subdir)) next if nekoninni.nil? buf << nekoninni end return buf end def self.search_katochan() home_dir = get_ninix_home() buf = [] katochan_dir = File.join(home_dir, 'nekodorif/katochan') begin dirlist = [] Dir.foreach(katochan_dir, :encoding => 'UTF-8') do |file| next if file == '..' or file == '.' dirlist << file end rescue SystemCallError dirlist = [] end for subdir in dirlist katochan = read_katochan_txt(File.join(katochan_dir, subdir)) next if katochan.nil? buf << katochan end return buf end def self.search_kinoko() home_dir = get_ninix_home() buf = [] kinoko_dir = File.join(home_dir, 'kinoko') begin dirlist = [] Dir.foreach(kinoko_dir, :encoding => 'UTF-8') do |file| next if file == '..' or file == '.' dirlist << file end rescue SystemCallError dirlist = [] end for subdir in dirlist kinoko = read_kinoko_ini(File.join(kinoko_dir, subdir)) next if kinoko.nil? buf << kinoko end return buf end def self.read_kinoko_ini(top_dir) path = File.join(top_dir, 'kinoko.ini') kinoko = {} kinoko['base'] = 'surface0.png' kinoko['animation'] = nil kinoko['category'] = nil kinoko['title'] = nil kinoko['ghost'] = nil kinoko['dir'] = top_dir kinoko['offsetx'] = 0 kinoko['offsety'] = 0 kinoko['ontop'] = 0 kinoko['baseposition'] = 0 kinoko['baseadjust'] = 0 kinoko['extractpath'] = nil kinoko['nayuki'] = nil if File.readable_real?(path) f = open(path, 'rb:CP932') line = f.readline() return nil if line.strip.empty? or line.strip() != '[KINOKO]' lineno = 0 error = nil for line in f lineno += 1 if line.end_with?("\x00") # XXX line = line[0, line.length - 2] end next if line.strip.empty? line = line.encode('UTF-8', :invalid => :replace, :undef => :replace) unless line.include?('=') error = 'line ' + lineno.to_s + ': syntax error' break end x = line.split('=', 2) name = x[0].strip() value = x[1].strip() if ['title', 'ghost', 'category'].include?(name) kinoko[name] = value elsif ['offsetx', 'offsety'].include?(name) kinoko[name] = value.to_i elsif ['base', 'animation', 'extractpath'].include?(name) kinoko[name] = value elsif ['ontop', 'baseposition', 'baseadjust'].include?(name) kinoko[name] = value.to_i end unless error.nil? Logging::Logging.error('Error: ' + error + "\n" + path +' (skipped)') return nil end end end unless kinoko['title'].empty? return kinoko else return nil end end def self.read_profile_txt(top_dir) path = File.join(top_dir, 'profile.txt') name = nil if File.readable_real?(path) f = open(path, 'rb:CP932') line = f.readline() unless line.nil? name = line.strip.encode("UTF-8", :invalid => :replace, :undef => :replace) end end unless name.empty? return [name, top_dir] else return nil end end def self.read_katochan_txt(top_dir) path = File.join(top_dir, 'katochan.txt') katochan = {} katochan['dir'] = top_dir if File.readable_real?(path) f = open(path, 'rb:CP932') name = nil lineno = 0 error = nil for line in f lineno += 1 next if line.strip.empty? if line.start_with?('#') name = line[1, line.length - 1].strip() next elsif name.empty? error = 'line ' + lineno.to_s + ': syntax error' break else value = line.strip.encode("UTF-8", :invalid => :replace, :undef => :replace) if ['name', 'category'].include?(name) katochan[name] = value end if name.start_with?('before.script') or \ name.start_with?('hit.script') or \ name.start_with?('after.script') or \ name.start_with?('end.script') or \ name.start_with?('dodge.script') ## FIXME: should be array katochan[name] = value elsif ['before.fall.speed', 'before.slide.magnitude', 'before.slide.sinwave.degspeed', 'before.appear.ofset.x', 'before.appear.ofset.y', 'hit.waittime', 'hit.ofset.x', 'hit.ofset.y', 'after.fall.speed', 'after.slide.magnitude', 'after.slide.sinwave.degspeed'].include?(name) katochan[name] = value.to_i elsif ['target', 'before.fall.type', 'before.slide.type', 'before.wave', 'before.wave.loop', 'before.appear.direction', 'hit.wave', 'hit.wave.loop', 'after.fall.type', 'after.slide.type', 'after.wave', 'after.wave.loop', 'end.wave', 'end.wave.loop', 'end.leave.direction', 'dodge.wave', 'dodge.wave.loop'].include?(name) katochan[name] = value else name = nil end end end unless error.nil? Logging::Logging.error('Error: ' + error + "\n" + path + ' (skipped)') return nil end end unless katochan['name'].empty? return katochan else return nil end end def self.read_descript_txt(top_dir) path = File.join(top_dir, 'descript.txt') if File.readable_real?(path) return NConfig.create_from_file(path) end return nil end def self.read_install_txt(top_dir) path = File.join(top_dir, 'install.txt') if File.readable_real?(path) return NConfig.create_from_file(path) end return nil end def self.read_alias_txt(top_dir) path = File.join(top_dir, 'alias.txt') if File.readable_real?(path) return Alias.create_from_file(path) end return nil end def self.find_makoto_dll(top_dir) if File.readable_real?(File.join(top_dir, 'makoto.dll')) return true else return false end end def self.find_surface_set(top_dir) desc = read_descript_txt(File.join(top_dir, 'ghost', 'master')) default_sakura = desc.get('sakura.seriko.defaultsurface', :default => '0') default_kero = desc.get('kero.seriko.defaultsurface', :default => '10') unless desc.nil? shell_name = desc.get('name') else shell_name = nil end if shell_name.nil? or shell_name.empty? inst = read_install_txt(top_dir) unless inst.nil? shell_name = inst.get('name') end end surface_set = {} shell_dir = File.join(top_dir, 'shell') for name, desc, subdir in find_surface_dir(shell_dir) surface_dir = File.join(shell_dir, subdir) surface_info, alias_, tooltips, seriko_descript = read_surface_info(surface_dir) if not surface_info.nil? and \ surface_info.include?('surface' + default_sakura.to_s) and \ surface_info.include?('surface' + default_kero.to_s) if alias_.nil? alias_ = read_alias_txt(surface_dir) end surface_set[subdir] = [name, surface_dir, desc, alias_, surface_info, tooltips, seriko_descript] end end return shell_name, surface_set end def self.find_surface_dir(top_dir) buf = [] path = File.join(top_dir, 'surface.txt') if File.exists?(path) config = NConfig.create_from_file(path) for name, subdir in config.each_entry subdir = subdir.downcase desc = read_descript_txt(File.join(top_dir, subdir)) desc = NConfig.null_config() if desc.nil? buf << [name, desc, subdir] end else begin dirlist = [] Dir.foreach(top_dir, :encoding => 'UTF-8') do |file| next if file == '..' or file == '.' dirlist << file end rescue SystemCallError dirlist = [] end for subdir in dirlist desc = read_descript_txt(File.join(top_dir, subdir)) desc = NConfig.null_config() if desc.nil? name = desc.get('name', :default => subdir) buf << [name, desc, subdir] end end return buf end def self.read_surface_info(surface_dir) re_surface = Regexp.new('\Asurface([0-9]+)\.(png|dgp|ddp)') surface = {} begin filelist = [] Dir.foreach(surface_dir, :encoding => 'UTF-8') do |file| next if file == '..' or file == '.' filelist << file end rescue SystemCallError filelist = [] end filename_alias = {} path = File.join(surface_dir, 'alias.txt') if File.exists?(path) dic = Alias.create_from_file(path) for basename, alias_ in dic.each_entry if basename.start_with?('surface') filename_alias[alias_] = basename end end end # find png image and associated configuration file for filename in filelist basename = File.basename(filename, ".*") ext = File.extname(filename) if filename_alias.include?(basename) match = re_surface.match([filename_alias[basename], ext].join('')) else match = re_surface.match(filename) end next if match.nil? img = File.join(surface_dir, filename) next unless File.readable_real?(img) key = ['surface', match[1].to_i.to_s].join('') txt = File.join(surface_dir, [basename, 's.txt'].join('')) if File.readable_real?(txt) config = NConfig.create_from_file(txt) else config = NConfig.null_config() end txt = File.join(surface_dir, [basename, 'a.txt'].join('')) if File.readable_real?(txt) config.update(NConfig.create_from_file(txt)) end surface[key] = [img, config] end # find surfaces.txt alias_ = nil tooltips = {} seriko_descript = {} for key, config in read_surfaces_txt(surface_dir) if key == '__alias__' alias_ = config elsif key == '__tooltips__' tooltips = config elsif key.start_with?('surface') if surface.keys.include?(key) img, prev_config = surface[key] config = prev_config.merge(config) else img = nil end surface[key] = [img, config] elsif key == 'descript' seriko_descript = config end end # find surface elements for key in surface.keys value = surface[key] img, config = value for key, method, filename, x, y in list_surface_elements(config) filename = filename.downcase basename = File.basename(filename, ".*") ext = File.extname(filename) unless surface.include?(basename) surface[basename] = [File.join(surface_dir, filename), NConfig.null_config()] end end end return surface, alias_, tooltips, seriko_descript end def self.read_surfaces_txt(surface_dir) re_alias = Regexp.new('\A(sakura|kero|char[0-9]+)\.surface\.alias\z') config_list = [] return config_list unless File.directory?(surface_dir) Dir.foreach(surface_dir) do |file| next if /^\.+$/ =~ file if file.start_with?("surfaces") and file.end_with?(".txt") path = File.join(surface_dir, file) begin f = open(path, 'rb') alias_buffer = [] tooltips = {} charset = 'CP932' buf = [] key = nil opened = false if f.read(3).bytes == [239, 187, 191] # "\xEF\xBB\xBF" f.close f = File.open(path, 'rb:BOM|UTF-8') charset = 'UTF-8' else f.seek(0) # rewind end for line in f next if line.start_with?('#') or line.start_with?('//') if charset == 'CP932' # "\x81\x40": full-width space in CP932(Shift_JIS) temp = line.gsub(0x81.chr + 0x40.chr, "").strip() else temp = line.strip() end next if temp.empty? if temp.start_with?('charset') begin charset = temp.split(',', 2)[1].strip().force_encoding('ascii') rescue #pass end next end if key.nil? if temp.end_with?('{') key = temp[0, temp.length - 1].force_encoding(charset).encode("UTF-8", :invalid => :replace, :undef => :replace) opened = true else key = temp.force_encoding(charset).encode("UTF-8", :invalid => :replace, :undef => :replace) end elsif temp == '{' opened = true elsif temp.end_with?('}') if temp[0, temp.length - 2] buf << temp[0, temp.length - 2] end Logging::Logging.error('syntax error: unbalnced "}" in surfaces.txt.') unless opened match = re_alias.match(key) if not match.nil? alias_buffer << key alias_buffer << '{' for line in buf alias_buffer << line.force_encoding(charset).encode("UTF-8", :invalid => :replace, :undef => :replace) end alias_buffer << '}' elsif key.end_with?('.tooltips') begin key = key[0, -10] rescue #pass end value = {} for line in buf s = line.split(',', 2) region = s[0].strip().force_encoding(charset).encode("UTF-8", :invalid => :replace, :undef => :replace) text = s[1].strip().force_encoding(charset).encode("UTF-8", :invalid => :replace, :undef => :replace) value[region] = text tooltips[key] = value end elsif key.start_with?('surface') keys = key.split(',', 0) include_list = [] exclude_list = [] flg_append = false for key in keys flg_delete = false next if key.empty? if key.start_with?('surface') unless include_list.empty? for num in (include_list - exclude_list) key = ['surface', num].join('') if flg_append config_list.reverse_each {|x| break x[1].update(NConfig.create_from_buffer(buf, :charset => charset)) if x[0] == key } else config_list << [key, NConfig.create_from_buffer(buf, :charset => charset)] end end end include_list = [] exclude_list = [] flg_append = false end if key.start_with?('surface.append') flg_append = true key_range = key[14, key.length - 1] elsif key.start_with?('surface') key_range = key[7, key.length - 1] elsif key.start_with?("!") flg_delete = true key_range = key[1, key.length - 1] else key_range = key end s, e = key_range.split("-", 2) e = s if e.nil? begin s = Integer(s) e = Integer(e) rescue next end if flg_delete exclude_list.concat(Range.new(s, e).to_a) else include_list.concat(Range.new(s, e).to_a) end end unless include_list.empty? for num in (include_list - exclude_list) key = ['surface', num].join('') if flg_append config_list.reverse_each {|x| break x[1].update(NConfig.create_from_buffer(buf, :charset => charset)) if x[0] == key } else config_list << [key, NConfig.create_from_buffer(buf, :charset => charset)] end end end elsif key == 'descript' config_list << [key, NConfig.create_from_buffer(buf, :charset => charset)] end buf = [] key = nil opened = false else buf << temp end end rescue SystemCallError return config_list end unless alias_buffer.empty? config_list << ['__alias__', Alias.create_from_buffer(alias_buffer)] end config_list << ['__tooltips__', tooltips] end end return config_list end def self.list_surface_elements(config) buf = [] for n in 0..255 key = ['element', n.to_s].join('') break unless config.include?(key) spec = [] for value in config[key].split(',', 0) spec << value.strip() end begin method, filename, x, y = spec x = Integer(x) y = Integer(y) rescue Loggin::Logging.error( 'invalid element spec for ' + key + ': ' + config[key]) next end buf << [key, method, filename, x, y] end return buf end def self.read_balloon_info(balloon_dir) re_balloon = Regexp.new('\Aballoon([skc][0-9]+)\.(png)') re_annex = Regexp.new('\A(arrow[01]|sstp)\.(png)') balloon = {} begin filelist = [] Dir.foreach(balloon_dir, :encoding => 'UTF-8') do |file| next if file == '..' or file == '.' filelist << file end rescue SystemCallError filelist = [] end for filename in filelist match = re_balloon.match(filename) next if match.nil? img = File.join(balloon_dir, filename) if match[2] != 'png' and \ File.readable_real?([img[img.length - 4, img.length - 1], 'png'].join('')) next end next unless File.readable_real?(img) key = match[1] txt = File.join(balloon_dir, 'balloon' + key.to_s + 's.txt') if File.readable_real?(txt) config = NConfig.create_from_file(txt) else config = NConfig.null_config() end balloon[key] = [img, config] end for filename in filelist match = re_annex.match(filename) next if match.nil? img = File.join(balloon_dir, filename) next unless File.readable_real?(img) key = match[1] config = NConfig.null_config() balloon[key] = [img, config] end return balloon end end ninix-aya-5.0.9/lib/ninix/nekodorif.rb0000644000175000017500000005770313416507430016031 0ustar shyshy# -*- coding: utf-8 -*- # # Copyright (C) 2004-2019 by Shyouzou Sugitani # # This program is free software; you can redistribute it and/or modify it # under the terms of the GNU General Public License (version 2) as # published by the Free Software Foundation. It is distributed in the # hope that it will be useful, but WITHOUT ANY WARRANTY; without even the # implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR # PURPOSE. See the GNU General Public License for more details. # # TODO: # - 「きのこ」へのステータス送信. # - 「きのこ」の情報の参照. # - SERIKO/1.2ベースのアニメーション # - (スキン側の)katochan.txt # - balloon.txt # - surface[0/1/2]a.txt(@ゴースト) # - 自爆イベント # - headrect.txt : 頭の当たり判定領域データ # 当たり領域のleft/top/right/bottomを半角カンマでセパレートして記述. # 1行目がsurface0、2行目がsurface1の領域データ. # このファイルがない場合、領域は自動計算される. # - speak.txt # - katochan が無い場合の処理.(本体の方のpopup menuなども含めて) # - 設定ダイアログ : [会話/反応]タブ -> [SEND SSTP/1.1] or [SHIORI] # - 見切れ連続20[s]、もしくは画面内で静止20[s]でアニメーション記述ミスと見なし自動的に落ちる # - 発言中にバルーンをダブルクリックで即閉じ # - @ゴースト名は#nameと#forには使えない. もし書いても無視されすべて有効になる # - 連続落し不可指定 # チェックしておくと落下物を2個以上同時に落とせなくなる # - スキンチェンジ時も起動時のトークを行う # - ファイルセット設定機能 # インストールされたスキン/落下物のうち使用するものだけを選択できる # - ターゲットのアイコン化への対応 # - アイコン化されているときは自動落下しない # - アイコン化されているときのDirectSSTP SEND/DROPリクエストはエラー(Invisible) # - 落下物の透明化ON/OFF # - 落下物が猫どりふ自身にも落ちてくる # 不在時に1/2、ランダム/全員落し時に1/10の確率で自爆 # - 一定時間間隔で勝手に物を落とす # - ターゲット指定落し、ランダム落し、全員落し # - 出現即ヒットの場合への対応 # - 複数ゴーストでの当たり判定. # - 透明ウィンドウ require "gettext" require "gtk3" require_relative "pix" require_relative "home" require_relative "logging" module Nekodorif class Menu include GetText bindtextdomain("ninix-aya") def initialize(accelgroup) @parent = nil @__katochan_list = nil @__menu_list = {} @__popup_menu = Gtk::Menu.new item = Gtk::MenuItem.new(:label => _('Settings...(_O)'), :use_underline => true) item.signal_connect('activate') do |a, b| @parent.handle_request('NOTIFY', 'edit_preferences') end @__popup_menu.add(item) @__menu_list['settings'] = item item = Gtk::MenuItem.new(:label => _('Katochan(_K)'), :use_underline => true) @__popup_menu.add(item) @__menu_list['katochan'] = item item = Gtk::MenuItem.new(:label => _('Exit(_Q)'), :use_underline => true) item.signal_connect('activate') do |a, b| @parent.handle_request('NOTIFY', 'close') end @__popup_menu.add(item) @__menu_list['exit'] = item @__popup_menu.show_all end def set_responsible(parent) @parent = parent end def popup() katochan_list = @parent.handle_request('GET', 'get_katochan_list') __set_katochan_menu(katochan_list) @__popup_menu.popup_at_pointer(nil) end def __set_katochan_menu(list) key = 'katochan' unless list.empty? menu = Gtk::Menu.new() for katochan in list item = Gtk::MenuItem.new(:label => katochan['name']) item.signal_connect('activate', katochan) do |a, k| @parent.handle_request('NOTIFY', 'select_katochan', k) next true end menu.add(item) item.show() end @__menu_list[key].set_submenu(menu) menu.show() @__menu_list[key].show() else @__menu_list[key].hide() end end end class Nekoninni def initialize @mode = 1 # 0: SEND SSTP1.1, 1: SHIORI/2.2 @__running = false @skin = nil @katochan = nil end def observer_update(event, args) if ['set position', 'set surface'].include?(event) @skin.set_position() unless @skin.nil? if not @katochan.nil? and @katochan.loaded @katochan.set_position() end elsif event == 'set scale' scale = @target.get_surface_scale() @skin.set_scale(scale) unless @skin.nil? @katochan.set_scale(scale) unless @katochan.nil? elsif event == 'finalize' finalize() else Logging::Logging.debug("OBSERVER(nekodorif): ignore - #{event}") end end def load(dir, katochan, target) return 0 if katochan.empty? @dir = dir @target = target @target.attach_observer(self) @accelgroup = Gtk::AccelGroup.new() scale = @target.get_surface_scale() @skin = Skin.new(@dir, @accelgroup, scale) @skin.set_responsible(self) @skin.setup return 0 if @skin.nil? @katochan_list = katochan @katochan = nil launch_katochan(@katochan_list[0]) @__running = true GLib::Timeout.add(50) { do_idle_tasks } # 50[ms] return 1 end def handle_request(event_type, event, *arglist) fail "assert" unless ['GET', 'NOTIFY'].include?(event_type) handlers = { 'get_katochan_list' => lambda { return @katochan_list }, 'get_mode' => lambda { return @mode }, 'get_workarea' => lambda { return @target.get_workarea }, } if handlers.include?(event) result = handlers[event].call # no argument else if Nekoninni.method_defined?(event) result = method(event).call(*arglist) else result = nil # XXX end end return result if event_type == 'GET' end def do_idle_tasks return false unless @__running @skin.update() @katochan.update() unless @katochan.nil? #process_script() return true end def send_event(event) if not ['Emerge', # 可視領域内に出現 'Hit', # ヒット 'Drop', # 再落下開始 'Vanish', # ヒットした落下物が可視領域内から消滅 'Dodge' # よけられてヒットしなかった落下物が可視領域内から消滅 ].include?(event) return end args = [@katochan.get_name(), @katochan.get_ghost_name(), @katochan.get_category(), @katochan.get_kinoko_flag(), @katochan.get_target()] @target.notify_event('OnNekodorifObject' + event.to_s, *args) end def has_katochan unless @katochan.nil? return true else return false end end def select_katochan(args) launch_katochan(args) end def drop_katochan @katochan.drop() end def delete_katochan @katochan.destroy() @katochan = nil @skin.reset() end def launch_katochan(katochan) delete_katochan unless @katochan.nil? @katochan = Katochan.new(@target) @katochan.set_responsible(self) @katochan.load(katochan) end def edit_preferences end def finalize @__running = false @target.detach_observer(self) @katochan.destroy() unless @katochan.nil? @skin.destroy() unless @skin.nil? ##if self.balloon is not None: ## self.balloon.destroy() end def close finalize() end end class Skin def initialize(dir, accelgroup, scale) @dir = dir @accelgroup = accelgroup @parent = nil @dragged = false @x_root = nil @y_root = nil @__scale = scale @__menu = Menu.new(@accelgroup) @__menu.set_responsible(self) path = File.join(@dir, 'omni.txt') if File.file?(path) and File.size(path).zero? @omni = 1 else @omni = 0 end @window = Pix::TransparentWindow.new() name, top_dir = Home.read_profile_txt(dir) # XXX @window.set_title(name) @window.signal_connect('delete_event') do |w, e| delete(w, e) next true end @window.signal_connect('key_press_event') do |w, e| next key_press(w, e) end @window.add_accel_group(@accelgroup) @darea = @window.darea @darea.set_events(Gdk::EventMask::EXPOSURE_MASK| Gdk::EventMask::BUTTON_PRESS_MASK| Gdk::EventMask::BUTTON_RELEASE_MASK| Gdk::EventMask::POINTER_MOTION_MASK| Gdk::EventMask::POINTER_MOTION_HINT_MASK| Gdk::EventMask::LEAVE_NOTIFY_MASK) @darea.signal_connect('draw') do |w, cr| redraw(w, cr) next true end @darea.signal_connect('button_press_event') do |w, e| next button_press(w, e) end @darea.signal_connect('button_release_event') do |w, e| next button_release(w, e) end @darea.signal_connect('motion_notify_event') do |w, e| next motion_notify(w, e) end @darea.signal_connect('leave_notify_event') do |w, e| leave_notify(w, e) next true end @id = [0, nil] end def set_responsible(parent) @parent = parent end def handle_request(event_type, event, *arglist) fail "assert" unless ['GET', 'NOTIFY'].include?(event_type) handlers = { } unless handlers.include?(event) result = @parent.handle_request(event_type, event, *arglist) else if Skin.method_defined?(event) result = method(event).call(*arglist) else result = nil end end return result if event_type == 'GET' end def setup set_surface() set_position(:reset => 1) @window.show_all() end def set_scale(scale) @__scale = scale set_surface() set_position() end def redraw(widget, cr) @window.set_surface(cr, @image_surface, @__scale) @window.set_shape(cr, @reshape) @reshape = false end def delete(widget, event) @parent.handle_request('NOTIFY', 'finalize') end def key_press(window, event) if event.state & (Gdk::ModifierType::CONTROL_MASK | Gdk::ModifierType::SHIFT_MASK) if event.keyval == Gdk::Keyval::KEY_F12 Logging::Logging.info('reset skin position') set_position(:reset => 1) end end return true end def destroy @window.destroy() end def button_press(widget, event) if event.button == 1 if event.event_type == Gdk::EventType::BUTTON_PRESS @x_root = event.x_root @y_root = event.y_root elsif event.event_type == Gdk::EventType::DOUBLE_BUTTON_PRESS # double click if @parent.handle_request('GET', 'has_katochan') start() @parent.handle_request('NOTIFY', 'drop_katochan') end end elsif event.button == 3 if event.event_type == Gdk::EventType::BUTTON_PRESS @__menu.popup() end end return true end def set_surface unless @id[1].nil? path = File.join(@dir, 'surface' + @id[0].to_s + @id[1].to_s + '.png') unless File.exists?(path) @id[1] = nil set_surface() return end else path = File.join(@dir, 'surface' + @id[0].to_s + '.png') end begin new_surface = Pix.create_surface_from_file(path) w = [8, (new_surface.width * @__scale / 100).to_i].max h = [8, (new_surface.height * @__scale / 100).to_i].max rescue @parent.handle_request('NOTIFY', 'finalize') return end @w, @h = w, h @reshape = true @image_surface = new_surface @darea.queue_draw() end def set_position(reset: 0) left, top, scrn_w, scrn_h = @parent.handle_request('GET', 'get_workarea') unless reset.zero? @x = left @y = (top + scrn_h - @h) else @y = (top + scrn_h - @h) unless @omni.zero? end @window.move(@x, @y) end def move(x_delta, y_delta) @x = (@x + x_delta) @y = (@y + y_delta) unless @omni.zero? set_position() end def update unless @id[1].nil? @id[1] += 1 else return unless Random.rand(0..99).zero? ## XXX @id[1] = 0 end set_surface() end def start @id[0] = 1 set_surface() end def reset @id[0] = 0 set_surface() end def button_release(widget, event) if @dragged @dragged = false set_position() end @x_root = nil @y_root = nil return true end def motion_notify(widget, event) x, y, state = event.x, event.y, event.state if state & Gdk::ModifierType::BUTTON1_MASK unless @x_root.nil? or @y_root.nil? @dragged = true x_delta = (event.x_root - @x_root).to_i y_delta = (event.y_root - @y_root).to_i move(x_delta, y_delta) @x_root = event.x_root @y_root = event.y_root end end if event.is_hint == 1 Gdk::Event.request_motions(event) end return true end def leave_notify(widget, event) ## FIXME end end class Balloon def initialize end def destroy ## FIXME #pass end end class Katochan attr_reader :loaded CATEGORY_LIST = ['pain', # 痛い 'stab', # 刺さる 'surprise', # びっくり 'hate', # 嫌い、気持ち悪い 'huge', # 巨大 'love', # 好き、うれしい 'elegant', # 風流、優雅 'pretty', # かわいい 'food', # 食品 'reference', # 見る/読むもの 'other' # 上記カテゴリに当てはまらないもの ] def initialize(target) @side = 0 @target = target @parent = nil @settings = {} @settings['state'] = 'before' @settings['fall.type'] = 'gravity' @settings['fall.speed'] = 1 @settings['slide.type'] = 'none' @settings['slide.magnitude'] = 0 @settings['slide.sinwave.degspeed'] = 30 @settings['wave'] = nil @settings['wave.loop'] = 0 @__scale = 100 @loaded = false end def set_responsible(parent) @parent = parent end def get_name @data['name'] end def get_category @data['category'] end def get_kinoko_flag ## FIXME return 0 # 0/1 = きのこに当たっていない(ない場合を含む)/当たった end def get_target if @side.zero? return @target.get_selfname() else return @target.get_keroname() end end def get_ghost_name if @data.include?('for') # 落下物が主に対象としているゴーストの名前 return @data['for'] else return '' end end def destroy @window.destroy() end def delete(widget, event) destroy() end def redraw(widget, cr) @window.set_surface(cr, @image_surface, @__scale) @window.set_shape(cr, @reshape) @reshape = false end def set_movement(timing) key = (timing + 'fall.type') if @data.include?(key) and \ ['gravity', 'evenspeed', 'none'].include?(@data[key]) @settings['fall.type'] = @data[key] else @settings['fall.type'] = 'gravity' end if @data.include?(timing + 'fall.speed') @settings['fall.speed'] = @data[timing + 'fall.speed'] else @settings['fall.speed'] = 1 end if @settings['fall.speed'] < 1 @settings['fall.speed'] = 1 end if @settings['fall.speed'] > 100 @settings['fall.speed'] = 100 end key = (timing + 'slide.type') if @data.include?(key) and \ ['none', 'sinwave', 'leaf'].include?(@data[key]) @settings['slide.type'] = @data[key] else @settings['slide.type'] = 'none' end if @data.include?(timing + 'slide.magnitude') @settings['slide.magnitude'] = @data[timing + 'slide.magnitude'] else @settings['slide.magnitude'] = 0 end if @data.include?(timing + 'slide.sinwave.degspeed') @settings['slide.sinwave.degspeed'] = @data[timing + 'slide.sinwave.degspeed'] else @settings['slide.sinwave.degspeed'] = 30 end if @data.include?(timing + 'wave') @settings['wave'] = @data[timing + 'wave'] else @settings['wave'] = nil end if @data.include?(timing + 'wave.loop') if @data[timing + 'wave.loop'] == 'on' @settings['wave.loop'] = 1 else @settings['wave.loop'] = 0 end else @settings['wave.loop'] = 0 end end def set_scale(scale) @__scale = scale set_surface() set_position() end def set_position return if @settings['state'] != 'before' target_x, target_y = @target.get_surface_position(@side) target_w, target_h = @target.get_surface_size(@side) left, top, scrn_w, scrn_h = @parent.handle_request('GET', 'get_workarea') @x = (target_x + target_w / 2 - @w / 2 + (@offset_x * @__scale / 100).to_i) @y = (top + (@offset_y * @__scale / 100).to_i) @window.move(@x, @y) end def set_surface path = File.join(@data['dir'], 'surface' + @id.to_s + '.png') begin new_surface = Pix.create_surface_from_file(path) w = [8, (new_surface.width * @__scale / 100).to_i].max h = [8, (new_surface.height * @__scale / 100).to_i].max rescue @parent.handle_request('NOTIFY', 'finalize') return end @w, @h = w, h @reshape = true @image_surface = new_surface @darea.queue_draw() end def load(data) @data = data @__scale = @target.get_surface_scale() set_state('before') if @data.include?('category') category = @data['category'].split(',', 0) unless category.empty? unless CATEGORY_LIST.include?(category[0]) Logging::Logging.warning('WARNING: unknown major category - ' + category[0]) ##@data['category'] = CATEGORY_LIST[-1] end else @data['category'] = CATEGORY_LIST[-1] end else @data['category'] = CATEGORY_LIST[-1] end if @data.include?('target') if @data['target'] == 'sakura' @side = 0 elsif @data['target'] == 'kero' @side = 1 else @side = 0 # XXX end else @side = 0 # XXX end if @parent.handle_request('GET', 'get_mode') == 1 @parent.handle_request('NOTIFY', 'send_event', 'Emerge') else if @data.include?('before.script') #pass ## FIXME else #pass ## FIXME end end set_movement('before') if @data.include?('before.appear.direction') #pass ## FIXME else #pass ## FIXME end if @data.include?('before.appear.ofset.x') offset_x = @data['before.appear.ofset.x'] else offset_x = 0 end if offset_x < -32768 offset_x = -32768 end if offset_x > 32767 offset_x = 32767 end if @data.include?('before.appear.ofset.y') offset_y = @data['before.appear.ofset.y'] else offset_y = 0 end if offset_y < -32768 offset_y = -32768 end if offset_y > 32767 offset_y = 32767 end @offset_x = offset_x @offset_y = offset_y @window = Pix::TransparentWindow.new() @window.set_title(@data['name']) @window.set_skip_taskbar_hint(true) # XXX @window.signal_connect('delete_event') do |w, e| delete(w, e) next true end @darea = @window.darea @darea.set_events(Gdk::EventMask::EXPOSURE_MASK) @darea.signal_connect('draw') do |w, cr| redraw(w, cr) next true end @window.show() @id = 0 set_surface() set_position() @loaded = true end def drop set_state('fall') end def set_state(state) @settings['state'] = state @time = 0 @hit = 0 @hit_stop = 0 end def update_surface ## FIXME #pass end def update_position ## FIXME if @settings['slide.type'] == 'leaf' #pass else if @settings['fall.type'] == 'gravity' @y += (@settings['fall.speed'].to_i * \ (@time / 20.0)**2) elsif @settings['fall.type'] == 'evenspeed' @y += @settings['fall.speed'] else #pass end if @settings['slide.type'] == 'sinwave' #pass ## FIXME else #pass end end @window.move(@x, @y) end def check_collision ## FIXME: check self position for side in [0, 1] target_x, target_y = @target.get_surface_position(side) target_w, target_h = @target.get_surface_size(side) center_x = (@x + @w / 2) center_y = (@y + @h / 2) if target_x < center_x and center_x < (target_x + target_w) and \ target_y < center_y and center_y < (target_y + target_h) @side = side return 1 end end return 0 end def check_mikire left, top, scrn_w, scrn_h = @parent.handle_request('GET', 'get_workarea') if (@x + @w - @w / 3) > (left + scrn_w) or \ (@x + @w / 3) < left or \ (@y + @h - @h / 3) > (top + scrn_h) or \ (@y + @h / 3) < top return 1 else return 0 end end def update if @settings['state'] == 'fall' update_surface() update_position() unless check_collision().zero? set_state('hit') @hit = 1 if @parent.handle_request('GET', 'get_mode') == 1 @id = 1 set_surface() @parent.handle_request('NOTIFY', 'send_event', 'Hit') else #pass ## FIXME end end set_state('dodge') unless check_mikire().zero? elsif @settings['state'] == 'hit' if @data.include?('hit.waittime') wait_time = @data['hit.waittime'] else wait_time = 0 end if @hit_stop >= wait_time set_state('after') set_movement('after') if @parent.handle_request('GET', 'get_mode') == 1 @id = 2 set_surface() @parent.handle_request('NOTIFY', 'send_event', 'Drop') else #pass ## FIXME end else @hit_stop += 1 update_surface() end elsif @settings['state'] == 'after' update_surface() update_position() set_state('end') unless check_mikire().zero? elsif @settings['state'] == 'end' if @parent.handle_request('GET', 'get_mode') == 1 @parent.handle_request('NOTIFY', 'send_event', 'Vanish') else #pass ## FIXME end @parent.handle_request('NOTIFY', 'delete_katochan') return false elsif @settings['state'] == 'dodge' if @parent.handle_request('GET', 'get_mode') == 1 @parent.handle_request('NOTIFY', 'send_event', 'Dodge') else #pass ## FIXME end @parent.handle_request('NOTIFY', 'delete_katochan') return false else ## check collision and mikire end @time += 1 return true end end end ninix-aya-5.0.9/lib/ninix/prefs.rb0000644000175000017500000004125213416507430015160 0ustar shyshy# -*- coding: utf-8 -*- # # Copyright (C) 2001, 2002 by Tamito KAJIYAMA # Copyright (C) 2002, 2003 by MATSUMURA Namihiko # Copyright (C) 2004-2019 by Shyouzou Sugitani # Copyright (C) 2003-2005 by Shun-ichi TAHARA # # This program is free software; you can redistribute it and/or modify it # under the terms of the GNU General Public License (version 2) as # published by the Free Software Foundation. It is distributed in the # hope that it will be useful, but WITHOUT ANY WARRANTY; without even the # implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR # PURPOSE. See the GNU General Public License for more details. # require 'gettext' require "gtk3" require_relative "home" module Prefs RANGE_SCALE = [100, 90, 80, 70, 60, 50, 40, 30, 200, 300, 1000] RANGE_SCRIPT_SPEED = [-1, 0, 1, 2, 3, 4, 5, 6, 8] # -1: no wait # default settings DEFAULT_BALLOON_FONTS = 'Sans' def self.get_default_surface_scale() RANGE_SCALE[0] end def self.get_default_script_speed() RANGE_SCRIPT_SPEED[RANGE_SCRIPT_SPEED.length / 2] end class Preferences include GetText bindtextdomain("ninix-aya") def initialize(filename) @dic = {} @filename = filename @__stack = {} end def get(name, default) @dic.include?(name) ? @dic[name] : default end def delete(key) @dic.delete(key) if @dic.has_key?(key) end def include?(name) @dic.include?(name) or @__stack.include?(name) end def set(key, item) if @dic.include?(key) and not @__stack.include?(key) @__stack[key] = @dic[key] end @dic[key] = item end def commit @__stack = {} end def revert update(@__stack) @__stack = {} end def update(stack) for key in stack.keys @dic[key] = stack[key] end end def load @dic = {} begin open(@filename) {|f| while line = f.gets @dic.store(*line.chomp.split(': ', 2)) end } rescue # IOError return end end def save begin Dir.mkdir(File.dirname(@filename), 0755) rescue SystemCallError #pass end open(@filename, 'w') {|f| keys = @dic.keys.sort for key in keys if @__stack.include?(key) value = @__stack[key] else value = @dic[key] end f.write([key, ": ", value, "\n"].join('')) end } end end class PreferenceDialog include GetText bindtextdomain("ninix-aya") def initialize @parent = nil @dialog = Gtk::Dialog.new() @dialog.signal_connect('delete_event') do |i, *a| next true end @dialog.set_title('Preferences') @dialog.set_default_size(-1, 600) @notebook = Gtk::Notebook.new() @notebook.set_tab_pos(Gtk::PositionType::TOP) content_area = @dialog.content_area content_area.add(@notebook) @notebook.show() @notebook.append_page(make_page_surface_n_balloon(), Gtk::Label.new(_('Surface&Balloon'))) @notebook.append_page(make_page_misc(), Gtk::Label.new(_('Misc'))) @notebook.append_page(make_page_debug(), Gtk::Label.new(_('Debug'))) @dialog.add_button("_OK", Gtk::ResponseType::OK) @dialog.add_button("_Apply", Gtk::ResponseType::APPLY) @dialog.add_button("_Cancel", Gtk::ResponseType::CANCEL) @dialog.signal_connect('response') do |i, *a| next response(i, *a) end end def set_responsible(parent) @parent = parent end def load filename = Home.get_preferences() @__prefs = Preferences.new(filename) @__prefs.load() reset() update() # XXX @parent.handle_request('NOTIFY', 'notify_preference_changed') end def save @__prefs.save end def reset @fontchooser.set_font_name(get('balloon_fonts', :default => DEFAULT_BALLOON_FONTS)) set_default_balloon(get('default_balloon')) @ignore_button.set_active((not get('ignore_default', :default => 0).zero?)) scale = get('surface_scale', :default => Prefs.get_default_surface_scale()) unless RANGE_SCALE.include?(scale) @surface_scale_combo.set_active(RANGE_SCALE.index(Prefs.get_default_surface_scale())) else @surface_scale_combo.set_active(RANGE_SCALE.index(scale)) end script_speed = get('script_speed', :default => Prefs.get_default_script_speed()) unless RANGE_SCRIPT_SPEED.include?(script_speed) @script_speed_combo.set_active( RANGE_SCRIPT_SPEED.index(Prefs.get_default_script_speed())) else @script_speed_combo.set_active(RANGE_SCRIPT_SPEED.index(script_speed)) end @balloon_scaling_button.set_active((not get('balloon_scaling').zero?)) @allowembryo_button.set_active((not get('allowembryo').zero?)) @check_collision_button.set_active((not get('check_collision', :default => 0).zero?)) @check_collision_name_button.set_active((not get('check_collision_name', :default => 0).zero?)) @use_pna_button.set_active((not get('use_pna', :default => 1).zero?)) @sink_after_talk_button.set_active((not get('sink_after_talk').zero?)) @raise_before_talk_button.set_active((not get('raise_before_talk').zero?)) @animation_quality_adjustment.set_value(get('animation_quality', :default => 1.0)) end def get(name, default: nil) if ['sakura_name', # XXX: backward compat 'sakura_dir', 'default_balloon', 'balloon_fonts'].include?(name) value = @__prefs.get(name, default) elsif ['ignore_default', 'script_speed', 'surface_scale', 'balloon_scaling', 'allowembryo', 'check_collision', 'check_collision_name', 'use_pna', 'sink_after_talk', 'raise_before_talk'].include?(name) value = @__prefs.get(name, default).to_i elsif ['animation_quality'].include?(name) value = @__prefs.get(name, default).to_f else # should not reach here value = @__prefs.get(name, default) end return value end def set_current_sakura(directory) key = 'sakura_name' # obsolete if @__prefs.include?(key) @__prefs.delete(key) end key = 'sakura_dir' if @__prefs.include?(key) @__prefs.delete(key) end @__prefs.set(key, directory) end def edit_preferences show() end def update(commit: false) @__prefs.set('allowembryo', (@allowembryo_button.active? ? 1 : 0).to_s) @__prefs.set('balloon_fonts', @fontchooser.font_name) selected = @balloon_treeview.selection.selected unless selected.nil? model, listiter = selected unless listiter.nil? # XXX directory = model.get_value(listiter, 1) @__prefs.set('default_balloon', directory) end end @__prefs.set('ignore_default', (@ignore_button.active? ? 1 : 0).to_s) @__prefs.set('surface_scale', RANGE_SCALE[@surface_scale_combo.active].to_i.to_s) @__prefs.set('script_speed', RANGE_SCRIPT_SPEED[@script_speed_combo.active].to_i.to_s) @__prefs.set('balloon_scaling', (@balloon_scaling_button.active? ? 1 : 0).to_s) @__prefs.set('check_collision', (@check_collision_button.active? ? 1 : 0).to_s) @__prefs.set('check_collision_name', (@check_collision_name_button.active? ? 1 : 0).to_s) @__prefs.set('use_pna', (@use_pna_button.active? ? 1 : 0).to_s) @__prefs.set('sink_after_talk', (@sink_after_talk_button.active? ? 1: 0).to_s) @__prefs.set('raise_before_talk', (@raise_before_talk_button.active? ? 1 : 0).to_s) @__prefs.set('animation_quality', @animation_quality_adjustment.value.to_f.to_s) if commit @__prefs.commit() end end def ok hide() update(:commit => true) @parent.handle_request('NOTIFY', 'notify_preference_changed') end def apply update() @parent.handle_request('NOTIFY', 'notify_preference_changed') end def cancel hide() @__prefs.revert() reset() @parent.handle_request('NOTIFY', 'notify_preference_changed') end def show @dialog.show() end def response(widget, response) if response == Gtk::ResponseType::OK ok() elsif response == Gtk::ResponseType::CANCEL cancel() elsif response == Gtk::ResponseType::APPLY apply elsif response == Gtk::ResponseType::DELETE_EVENT cancel() else # should not reach here # pass end return true end def hide @dialog.hide() end def make_page_surface_n_balloon page = Gtk::Box.new(orientation=Gtk::Orientation::VERTICAL, spacing=5) page.set_border_width(5) page.show() frame = Gtk::Frame.new(label=_('Surface Scaling')) page.pack_start(frame, :expand => false, :fill => true, :padding => 0) frame.show() box = Gtk::Box.new(orientation=Gtk::Orientation::VERTICAL, spacing=5) box.set_border_width(5) frame.add(box) box.show() hbox = Gtk::Box.new(orientation=Gtk::Orientation::HORIZONTAL, spacing=5) box.pack_start(hbox, :expand => false, :fill => true, :padding => 0) hbox.show() label = Gtk::Label.new(label=_('Default Setting')) hbox.pack_start(label, :expand => false, :fill => true, :padding => 0) label.show() @surface_scale_combo = Gtk::ComboBoxText.new() for value in RANGE_SCALE @surface_scale_combo.append_text(sprintf("%4d", value)) end hbox.pack_start(@surface_scale_combo, :expand => false, :fill => true, :padding => 0) @surface_scale_combo.show() button = Gtk::CheckButton.new(_('Scale Balloon')) @balloon_scaling_button = button box.pack_start(button, :expand => false, :fill => true, :padding => 0) button.show() frame = Gtk::Frame.new(label=_('Default Balloon')) page.pack_start(frame, :expand => true, :fill => true, :padding => 0) frame.show() box = Gtk::Box.new(orientation=Gtk::Orientation::VERTICAL, spacing=5) box.set_border_width(5) frame.add(box) box.show() scrolled = Gtk::ScrolledWindow.new() scrolled.set_vexpand(true) scrolled.set_policy(Gtk::PolicyType::NEVER, Gtk::PolicyType::ALWAYS) scrolled.set_shadow_type(Gtk::ShadowType::ETCHED_IN) box.pack_start(scrolled, :expand => true, :fill => true, :padding => 0) scrolled.show() treeview = Gtk::TreeView.new() column = Gtk::TreeViewColumn.new(_('Balloon Name'), Gtk::CellRendererText.new(), :text => 0) treeview.append_column(column) treeview.selection.set_mode(Gtk::SelectionMode::SINGLE) @balloon_treeview = treeview scrolled.add(treeview) treeview.show() button = Gtk::CheckButton.new(_('Always Use This Balloon')) @ignore_button = button box.pack_start(button, :expand => false, :fill => true, :padding => 0) button.show() frame = Gtk::Frame.new(label=_('Font(s) for balloons')) page.pack_start(frame, :expand => false, :fill => true, :padding => 0) frame.show() box = Gtk::Box.new(orientation=Gtk::Orientation::VERTICAL, spacing=5) box.set_border_width(5) frame.add(box) box.show() @fontchooser = Gtk::FontButton.new() @fontchooser.set_show_size(false) box.add(@fontchooser) @fontchooser.show() frame = Gtk::Frame.new(label=_('Translucency')) page.pack_start(frame, :expand => false, :fill => true, :padding => 0) frame.show() box = Gtk::Box.new(orientation=Gtk::Orientation::VERTICAL, spacing=5) box.set_border_width(5) frame.add(box) box.show() button = Gtk::CheckButton.new(_('Use PNA file')) @use_pna_button = button box.pack_start(button, :expand => false, :fill => true, :padding => 0) button.show() frame = Gtk::Frame.new(label=_('Animation')) page.pack_start(frame, :expand => false, :fill => true, :padding => 0) frame.show() box = Gtk::Box.new(orientation=Gtk::Orientation::VERTICAL, spacing=5) box.set_border_width(5) frame.add(box) box.show() hbox = Gtk::Box.new(orientation=Gtk::Orientation::HORIZONTAL, spacing=5) box.add(hbox) hbox.show() label = Gtk::Label.new(label=_('Quality')) hbox.pack_start(label, :expand => false, :fill => true, :padding => 0) label.show() @animation_quality_adjustment = Gtk::Adjustment.new(1.0, 0.4, 1.0, 0.1, 0.1, 0) button = Gtk::SpinButton.new(adjustment=@animation_quality_adjustment, climb_rate=0.2, digits=1) hbox.pack_start(button, :expand => false, :fill => true, :padding => 0) button.show() hbox.show() return page end def make_page_misc page = Gtk::Box.new(orientation=Gtk::Orientation::VERTICAL, spacing=5) page.set_border_width(5) page.show() frame = Gtk::Frame.new(label=_('SSTP Setting')) page.pack_start(frame, :expand => false, :fill => true, :padding => 0) frame.show() button = Gtk::CheckButton.new(_('Allowembryo')) @allowembryo_button = button frame.add(button) button.show() frame = Gtk::Frame.new(label=_('Script Wait')) page.pack_start(frame, :expand => false, :fill => true, :padding => 0) frame.show() hbox = Gtk::Box.new(orientation=Gtk::Orientation::HORIZONTAL, spacing=5) frame.add(hbox) hbox.show() label = Gtk::Label.new(label=_('Default Setting')) hbox.pack_start(label, :expand => false, :fill => true, :padding => 0) label.show() @script_speed_combo = Gtk::ComboBoxText.new() for index in 0..(RANGE_SCRIPT_SPEED.length - 1) case index when 0 label = _('None') when 1 label = ['1 (', _('Fast'), ')'].join('') when RANGE_SCRIPT_SPEED.length - 1 label = [index.to_s, ' (', _('Slow'), ')'].join('') else label = index.to_s end @script_speed_combo.append_text(label) end hbox.pack_start(@script_speed_combo, :expand => false, :fill => true, :padding => 0) @script_speed_combo.show() frame = Gtk::Frame.new(label=_('Raise & Lower')) page.pack_start(frame, :expand => false, :fill => true, :padding => 0) frame.show() box = Gtk::Box.new(orientation=Gtk::Orientation::VERTICAL, spacing=5) box.set_border_width(5) frame.add(box) box.show() button = Gtk::CheckButton.new(_('Sink after Talk')) @sink_after_talk_button = button box.pack_start(button, :expand => false, :fill => true, :padding => 0) button.show() button = Gtk::CheckButton.new(_('Raise before Talk')) @raise_before_talk_button = button box.pack_start(button, :expand => false, :fill => true, :padding => 0) button.show() return page end def make_page_debug page = Gtk::Box.new(orientation=Gtk::Orientation::VERTICAL, spacing=5) page.set_border_width(5) page.show() frame = Gtk::Frame.new(label=_('Surface Debugging')) page.pack_start(frame, :expand => false, :fill => true, :padding => 0) frame.show() box = Gtk::Box.new(orientation=Gtk::Orientation::VERTICAL, spacing=5) box.set_border_width(5) frame.add(box) box.show() button = Gtk::CheckButton.new(_('Display Collision Area')) @check_collision_button = button box.pack_start(button, :expand => false, :fill => true, :padding => 0) button.show() button = Gtk::CheckButton.new(_('Display Collision Area Name')) @check_collision_name_button = button box.pack_start(button, :expand => false, :fill => true, :padding => 0) button.show() return page end def set_default_balloon(directory) model = Gtk::ListStore.new(String, String) for name, directory in @parent.handle_request('GET', 'get_balloon_list') listiter = model.append() model.set_value(listiter, 0, name) model.set_value(listiter, 1, directory) end @balloon_treeview.set_model(model) listiter = model.iter_first selected = false while not listiter.nil? value = model.get_value(listiter, 1) if value == directory or directory.nil? @balloon_treeview.selection.select_iter(listiter) selected = true break end listiter.next! end unless selected listiter = model.iter_first fail "assert" if listiter.nil? @balloon_treeview.selection.select_iter(listiter) end end end end ninix-aya-5.0.9/lib/ninix/dll.rb0000644000175000017500000001131213416507430014606 0ustar shyshy# -*- coding: utf-8 -*- # # dll.rb - a pseudo DLL (SHIORI/SAORI API support) module for ninix # Copyright (C) 2002-2019 by Shyouzou Sugitani # Copyright (C) 2002, 2003 by MATSUMURA Namihiko # # This program is free software; you can redistribute it and/or modify it # under the terms of the GNU General Public License (version 2) as # published by the Free Software Foundation. It is distributed in the # hope that it will be useful, but WITHOUT ANY WARRANTY; without even the # implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR # PURPOSE. See the GNU General Public License for more details. # require_relative "logging" module DLL def self.get_path() # XXX File.expand_path(File.join(File.dirname(__FILE__), 'dll')) end class SAORI RESPONSE = { 204 => "SAORI/1.0 204 No Content\r\n\r\n", 400 => "SAORI/1.0 400 Bad Request\r\n\r\n", 500 => "SAORI/1.0 500 Internal Server Error\r\n\r\n", } def initialize @loaded = 0 end def check_import 1 end def load(dir: nil) dir = File.expand_path(File.dirname(__FILE__)) if dir.nil? @dir = dir result = 0 if check_import.zero? #pass elsif not @loaded.zero? result = 2 else unless setup.zero? @loaded = 1 result = 1 end end return result end def setup 1 end def unload return 0 if @loaded.zero? @loaded = 0 return finalize() end def finalize 1 end def request(req) req_type, argument = evaluate_request(req) case req_type when nil return RESPONSE[400] when 'GET Version' return RESPONSE[204] when 'EXECUTE' result = execute(argument) return RESPONSE[204] if result.nil? return result else return RESPONSE[400] end end def execute(args) nil end def evaluate_request(req) req_type = nil argument = [] @charset = 'CP932' # default for line in req.split("\n", 0) line = line.force_encoding(@charset).strip.encode("UTF-8", :invalid => :replace, :undef => :replace) next if line.empty? if req_type.nil? for request in ['EXECUTE', 'GET Version'] if line.start_with?(request) req_type = request end end next end next if line.index(':').nil? key, value = line.split(':', 2) key = key.strip() if key == 'Charset' charset = value.strip() if not Encoding.name_list.include?(charset) Logging::Logging.warning('DLL: Unsupported charset ' + charset) end @charset = charset end if key.start_with?('Argument') argument << value.strip else next end end return req_type, argument end end class Library def initialize(dll_type, sakura: nil, saori_lib: nil) @type = dll_type @sakura = sakura @saori_lib = saori_lib end def request(name) if @type == 'shiori' dll_name, name = name if name.empty? and not dll_name.empty? name = dll_name end end name = name.gsub('\\', '/') head, tail = File.split(name) name = tail return nil if name.nil? or name.empty? if name.downcase.end_with?('.dll') # XXX name = name[0, name.length - 4] end path = File.join(DLL::get_path, name.downcase).concat('.rb') # XXX if File.exist?(path) require(path) begin module_ = Module.module_eval(name[0].upcase + name.downcase[1..name.length-1]) rescue #if not module_ return nil end else return nil end instance = nil case @type when 'saori' begin saori_ = module_.class_eval('Saori') saori = saori_.new() if saori_.method_defined?('need_ghost_backdoor') saori.need_ghost_backdoor(@sakura) end rescue saori = nil end instance = saori when 'shiori' begin shiori_ = module_.class_eval('Shiori') shiori = shiori_.new(dll_name) if shiori_.method_defined?('use_saori') shiori.use_saori(@saori_lib) end rescue shiori = nil end instance = shiori end return instance end def __import_module(name) path = get_path() loader = importlib.find_loader(name, [path]) begin return loader.load_module(name) rescue return nil end end end end ninix-aya-5.0.9/lib/ninix/entry_db.rb0000644000175000017500000000175013416507430015646 0ustar shyshy# -*- coding: utf-8 -*- # # Copyright (C) 2001, 2002 by Tamito KAJIYAMA # Copyright (C) 2004-2019 by Shyouzou Sugitani # # This program is free software; you can redistribute it and/or modify it # under the terms of the GNU General Public License (version 2) as # published by the Free Software Foundation. It is distributed in the # hope that it will be useful, but WITHOUT ANY WARRANTY; without even the # implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR # PURPOSE. See the GNU General Public License for more details. # module EntryDB class EntryDatabase def initialize(db: nil) fail "assert" unless (db.nil? or db.is_a?(Hash)) @__db = (db.nil? ? Hash.new : db) end def add(key, script) @__db[key] = [] if not @__db.has_key?(key) @__db[key] << script end def get(key, default: nil) @__db.has_key?(key) ? @__db[key].sample : default end def is_empty @__db.empty? end end end ninix-aya-5.0.9/lib/ninix/menu.rb0000644000175000017500000010337313416507430015010 0ustar shyshy# -*- coding: utf-8 -*- # # Copyright (C) 2003-2019 by Shyouzou Sugitani # Copyright (C) 2003 by Shun-ichi TAHARA # # This program is free software; you can redistribute it and/or modify it # under the terms of the GNU General Public License (version 2) as # published by the Free Software Foundation. It is distributed in the # hope that it will be useful, but WITHOUT ANY WARRANTY; without even the # implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR # PURPOSE. See the GNU General Public License for more details. # require 'gettext' require "gtk3" require_relative "pix" module Menu class Menu include GetText bindtextdomain("ninix-aya") def initialize @parent = nil @__fontcolor = { 'normal' => [0, 0, 0], 'hover' => [255, 255, 255] } @__imagepath = { 'background' => nil, 'foreground' => nil, 'background_with_sidebar' => nil, 'foreground_with_sidebar' => nil } @__align = { 'background' => nil, 'foreground' => nil, 'sidebar' => nil } @__menu_list = {} @__popup_menu = Gtk::Menu.new item = Gtk::MenuItem.new(:label => _('Recommend sites(_R)'), :use_underline => true) @__popup_menu.add(item) @__menu_list['Recommend'] = {:entry => item, :visible => true} item = Gtk::MenuItem.new(:label => _('Portal sites(_P)'), :use_underline => true) @__popup_menu.add(item) @__menu_list['Portal'] = {:entry => item, :visible => true} item = Gtk::SeparatorMenuItem.new() @__popup_menu.add(item) item = Gtk::CheckMenuItem.new(:label => _('Stick(_Y)'), :use_underline => true) item.set_active(false) item.signal_connect('activate') do |a, b| @parent.handle_request('NOTIFY', 'stick_window') end @__popup_menu.add(item) @__menu_list['Stick'] = {:entry => item, :visible => true} item = Gtk::SeparatorMenuItem.new() @__popup_menu.add(item) item = Gtk::MenuItem.new(:label => _('Options(_F)'), :use_underline => true) @__popup_menu.add(item) @__menu_list['Options'] = {:entry => item, :visible => true} menu = Gtk::Menu.new() item.set_submenu(menu) item = Gtk::MenuItem.new(:label => _('Network Update(_U)'), :use_underline => true) item.signal_connect('activate') do |a, b| @parent.handle_request('NOTIFY', 'network_update') end menu.add(item) @__menu_list['Options/Update'] = {:entry => item, :visible => true} item = Gtk::MenuItem.new(:label => _('Vanish(_F)'), :use_underline => true) item.signal_connect('activate') do |a, b| @parent.handle_request('NOTIFY', 'vanish') end menu.add(item) @__menu_list['Options/Vanish'] = {:entry => item, :visible => true} item = Gtk::MenuItem.new(:label => _('Preferences...(_O)'), :use_underline => true) item.signal_connect('activate') do |a, b| @parent.handle_request('NOTIFY', 'edit_preferences') end menu.add(item) @__menu_list['Options/Preferences'] = {:entry => item, :visible => true} item = Gtk::MenuItem.new(:label => _('Console(_C)'), :use_underline => true) item.signal_connect('activate') do |a, b| @parent.handle_request('NOTIFY', 'open_console') end menu.add(item) @__menu_list['Options/Console'] = {:entry => item, :visible => true} item = Gtk::MenuItem.new(:label => _('Ghost Manager(_M)'), :use_underline => true) item.signal_connect('activate') do |a, b| @parent.handle_request('NOTIFY', 'open_ghost_manager') end menu.add(item) @__menu_list['Options/Manager'] = {:entry => item, :visible => true} item = Gtk::SeparatorMenuItem.new() @__popup_menu.add(item) item = Gtk::MenuItem.new(:label => _('Change(_G)'), :use_underline => true) @__popup_menu.add(item) @__menu_list['Change'] = {:entry => item, :visible => true} item = Gtk::MenuItem.new(:label => _('Summon(_X)'), :use_underline => true) @__popup_menu.add(item) @__menu_list['Summon'] = {:entry => item, :visible => true} item = Gtk::MenuItem.new(:label => _('Shell(_S)'), :use_underline => true) @__popup_menu.add(item) @__menu_list['Shell'] = {:entry => item, :visible => true} item = Gtk::MenuItem.new(:label => _('Costume(_C)'), :use_underline => true) @__popup_menu.add(item) @__menu_list['Costume'] = {:entry => item, :visible => true} item = Gtk::MenuItem.new(:label => _('Balloon(_B)'), :use_underline => true) @__popup_menu.add(item) @__menu_list['Balloon'] = {:entry => item, :visible => true} item = Gtk::SeparatorMenuItem.new() @__popup_menu.add(item) item = Gtk::MenuItem.new(:label => _('Information(_I)'), :use_underline => true) @__popup_menu.add(item) @__menu_list['Information'] = {:entry => item, :visible => true} menu = Gtk::Menu.new() item.set_submenu(menu) item = Gtk::MenuItem.new(:label => _('Usage graph(_A)'), :use_underline => true) item.signal_connect('activate') do |a, b| @parent.handle_request('NOTIFY', 'show_usage') end menu.add(item) @__menu_list['Information/Usage'] = {:entry => item, :visible => true} item = Gtk::MenuItem.new(:label => _('Version(_V)'), :use_underline => true) item.signal_connect('activate') do |a, b| @parent.handle_request('NOTIFY', 'about') end menu.add(item) @__menu_list['Information/Version'] = {:entry => item, :visible => true} item = Gtk::SeparatorMenuItem.new() @__popup_menu.add(item) item = Gtk::MenuItem.new(:label => _('Nekodorif(_N)'), :use_underline => true) @__popup_menu.add(item) @__menu_list['Nekodorif'] = {:entry => item, :visible => true} item = Gtk::MenuItem.new(:label => _('Kinoko(_K)'), :use_underline => true) @__popup_menu.add(item) @__menu_list['Kinoko'] = {:entry => item, :visible => true} item = Gtk::SeparatorMenuItem.new() @__popup_menu.add(item) item = Gtk::MenuItem.new(:label => _('Close(_W)'), :use_underline => true) item.signal_connect('activate') do |a, b| @parent.handle_request('NOTIFY', 'close_sakura') end @__popup_menu.add(item) @__menu_list['Close'] = {:entry => item, :visible => true} item = Gtk::MenuItem.new(:label => _('Quit(_Q)'), :use_underline => true) item.signal_connect('activate') do |a, b| @parent.handle_request('NOTIFY', 'close_all') end @__popup_menu.add(item) @__menu_list['Quit'] = {:entry => item, :visible => true} @__popup_menu.show_all provider = create_css_provider_for(@__popup_menu) @__popup_menu.signal_connect('realize', provider) do |i, *a, provider| next set_stylecontext_with_sidebar(i, *a, :provider => provider) end for key in @__menu_list.keys item = @__menu_list[key][:entry] provider = create_css_provider_for(item) item.signal_connect('draw', provider) do |i, *a, provider| next set_stylecontext(i, *a, :provider => provider) end submenu = item.submenu unless submenu.nil? provider = create_css_provider_for(submenu) submenu.signal_connect('realize', provider) do |i, *a, provider| next set_stylecontext(i, *a, :provider => provider) end end end end def create_css_provider_for(item) provider = Gtk::CssProvider.new() style_context = item.style_context style_context.add_provider(provider, Gtk::StyleProvider::PRIORITY_USER) return provider end def set_responsible(parent) @parent = parent end def set_fontcolor(background, foreground) @__fontcolor['normal'] = background @__fontcolor['hover'] = foreground end def set_pixmap(path_background, path_sidebar, path_foreground, align_background, align_sidebar, align_foreground) @__imagepath['background'] = nil @__imagepath['foreground'] = nil @__imagepath['background_with_sidebar'] = nil @__imagepath['foreground_with_sidebar'] = nil @__align['background'] = align_background @__align['foreground'] = align_foreground @__align['sidebar'] = align_sidebar if not path_background.nil? and File.exists?(path_background) begin color = Pix.get_png_lastpix(path_background) @__imagepath['background'] = ["background-image: url('", path_background, "');\n", "background-color: ", color, ";\n"].join('') if not path_sidebar.nil? and File.exists?(path_sidebar) sidebar_width, sidebar_height = Pix.get_png_size(path_sidebar) @__imagepath['background_with_sidebar'] = ["background-image: url('", path_sidebar, "'),url('", path_background, "');\n", "background-repeat: no-repeat, repeat-x;\n", "background-color: ", color, ";\n"].join('') @sidebar_width = sidebar_width else @sidebar_width = 0 end rescue # pass end end if @__imagepath['background'].nil? @__imagepath['background'] = ["background-image: none;\n", "background-color: transparent;\n"].join('') end if not path_foreground.nil? and File.exists?(path_foreground) begin color = Pix.get_png_lastpix(path_foreground) @__imagepath['foreground'] = ["background-image: url('", path_foreground, "');\n", "background-color: ", color, ";\n"].join('') if not path_sidebar.nil? and File.exists?(path_sidebar) sidebar_width, sidebar_height = Pix.get_png_size(path_sidebar) @__imagepath['foreground_with_sidebar'] = ["background-image: url('", path_sidebar, "'),url('", path_foreground, "');\n", "background-repeat: no-repeat, repeat-x;\n", "background-color: ", color, ";\n"].join('') @sidebar_width = sidebar_width else @sidebar_width = 0 end rescue #pass end end if @__imagepath['foreground'].nil? @__imagepath['foreground'] = ["background-image: none;\n", "background-color: transparent;\n"].join('') end end def __set_mayuna_menu(side) if @__mayuna_menu.length > side and not @__mayuna_menu[side].nil? menuitem = @__menu_list['Costume'][:entry] menuitem.set_submenu(@__mayuna_menu[side]) __set_visible('Costume', true) else __set_visible('Costume', false) end end def create_mayuna_menu(mayuna_menu) @__mayuna_menu = [] for side in mayuna_menu.keys if side == 'sakura' index = 0 elsif side == 'kero' index = 1 elsif side.start_with?('char') begin index = Integer(side[4, side.length]) rescue next end else next end for _ in @__mayuna_menu.length..index @__mayuna_menu << nil end unless mayuna_menu[side].nil? @__mayuna_menu[index] = Gtk::Menu.new() for j in 0..mayuna_menu[side].length-1 key, name, state = mayuna_menu[side][j] if key != '-' item = Gtk::CheckMenuItem.new(:label => name) item.set_name('popup menu item') item.set_active(state) item.signal_connect('activate', [index, key]) do |a, ik| @parent.handle_request('NOTIFY', 'toggle_bind', ik) next true end provider = create_css_provider_for(item) item.signal_connect('draw', provider) do |i, *a, provider| next set_stylecontext(i, *a, :provider => provider) end else item = Gtk::SeparatorMenuItem.new() end item.show() @__mayuna_menu[index] << item end provider = create_css_provider_for(@__mayuna_menu[index]) @__mayuna_menu[index].signal_connect('realize', provider) do |i, *a, provider| next set_stylecontext(i, *a, :provider => provider) end end end end __re_shortcut = Regexp.new('&(?=[\x21-\x7e])') def __modify_shortcut(caption) self.__re_shortcut.sub('_', caption) end __re_mnemonic = Regexp.new('\(_.\)|_') def __cut_mnemonic(caption) self.__re_mnemonic.sub('', caption) end def __update_ui(side) @__ui = { 'Options/Update' => [[['updatebuttoncaption', 'updatebutton.caption'], ['updatebuttoncaption', 'updatebutton.caption']], '(_U)', [[],[]]], 'Options/Vanish' => [[['vanishbuttoncaption', 'vanishbutton.caption'], ['vanishbuttoncaption', 'vanishbutton.caption']], '(_F)', [['vanishbuttonvisible', 'vanishbutton.visible'], ['vanishbuttonvisible', 'vanishbutton.visible']]], 'Portal' => [[['sakura.portalbuttoncaption', 'portalrootbutton.caption'], []], '(_P)', [[], nil]], 'Recommend' => [[['sakura.recommendbuttoncaption', 'recommendrootbutton.caption'], ['kero.recommendbuttoncaption']], '(_R)', [[], []]], } for key in @__ui.keys rasie "assert" unless @__menu_list.include?(key) if side > 1 if ['Options/Update', 'Options/Vanish'].include?(key) name_list = @__ui[key][0][1] # same as 'kero' elsif key == 'Portal' name_list = [] # same as 'kero' elsif key == 'Recommend' name_list = ['char' + side.to_s + '.recommendbuttoncaption'] else name_list = @__ui[key][0][side] end unless name_list.empty? # caption for name in name_list caption = @parent.handle_request('GET', 'getstring', name) break unless caption.nil? or caption.empty? end unless caption.nil? or caption.empty? caption = __modify_shortcut(caption) if caption == __cut_mnemonic(caption) caption = [caption, @__ui[key][1]].join('') end __set_caption(key, caption) end end end if side > 1 name_list = @__ui[key][2][1] # same as 'kero' else name_list = @__ui[key][2][side] end if not name_list.nil? and not name_list.empty? # visible for name in name_list visible = @parent.handle_request('GET', 'getstring', name) break unless visible.nil? or visible.empty? end if visible == '0' __set_visible(key, false) else __set_visible(key, true) end elsif name_list.nil? __set_visible(key, false) end end end def popup(side) @__popup_menu.unrealize() for key in @__menu_list.keys item = @__menu_list[key][:entry] submenu = item.submenu submenu.unrealize() unless submenu.nil? end if side > 1 string = 'char' + side.to_s else fail "assert" unless [0, 1].include?(side) string = ['sakura', 'kero'][side] end string = [string, '.popupmenu.visible'].join('') return if @parent.handle_request('GET', 'getstring', string) == '0' __update_ui(side) if side.zero? portal = @parent.handle_request( 'GET', 'getstring', 'sakura.portalsites') else portal = nil end __set_portal_menu(side, portal) if side > 1 string = 'char' + side.to_s else fail "assert" unless [0, 1].include?(side) string = ['sakura', 'kero'][side] end string = [string, '.recommendsites'].join('') recommend = @parent.handle_request('GET', 'getstring', string) __set_recommend_menu(recommend) __set_ghost_menu() __set_shell_menu() __set_balloon_menu() __set_mayuna_menu(side) __set_nekodorif_menu() __set_kinoko_menu() for key in @__menu_list.keys item = @__menu_list[key][:entry] visible = @__menu_list[key][:visible] unless item.nil? if visible item.show() else item.hide() end end end @__popup_menu.popup_at_pointer(nil) end def __set_caption(name, caption) fail "assert" unless @__menu_list.include?(name) fail "assert" unless caption.is_a?(String) item = @__menu_list[name][:entry] unless item.nil? label = item.get_children()[0] label.set_text_with_mnemonic(caption) end end def __set_visible(name, visible) fail "assert" unless @__menu_list.include?(name) fail "assert" unless [false, true].include?(visible) @__menu_list[name][:visible] = visible end def __set_portal_menu(side, portal) if side >= 1 __set_visible('Portal', false) else unless portal.nil? or portal.empty? menu = Gtk::Menu.new() portal_list = portal.split(2.chr, 0) for site in portal_list entry = site.split(1.chr, 0) next if entry.empty? title = entry[0] if title == '-' item = Gtk::SeparatorMenuItem.new() else item = Gtk::MenuItem.new(:label => title) if entry.length < 2 item.set_sensitive(false) end if entry.length > 1 url = entry[1] end if entry.length > 2 base_path = @parent.handle_request( 'GET', 'get_prefix') filename = entry[2].downcase tail = File.extname(filename) if tail.empty? for ext in ['.png', '.jpg', '.gif'] filename = [filename, ext].join('') banner = File.join( base_path, 'ghost/master/banner', filename) unless File.exists?(banner) banner = nil else break end end else banner = File.join( base_path, 'ghost/master/banner', filename) unless File.exists?(banner) banner = nil end end else banner = nil end if entry.length > 1 item.signal_connect('activate', title, url) do |a, title, url| @parent.handle_request( 'NOTIFY', 'notify_site_selection', title, url) next true end unless banner.nil? item.set_has_tooltip(true) pixbuf = Pix.create_pixbuf_from_file(banner) item.signal_connect('query-tooltip') do |widget, x, y, keyboard_mode, tooltip| next on_tooltip(widget, x, y, keyboard_mode, tooltip, pixbuf) end else item.set_has_tooltip(false) end end end provider = create_css_provider_for(item) item.signal_connect('draw', provider) do |i, *a, provider| next set_stylecontext(i, *a, :provider => provider) end menu.add(item) item.show() end menuitem = @__menu_list['Portal'][:entry] menuitem.set_submenu(menu) provider = create_css_provider_for(menu) menu.signal_connect('realize', provider) do |i, *a, provider| next set_stylecontext(i, *a, :provider => provider) end menu.show() __set_visible('Portal', true) else __set_visible('Portal', false) end end end def __set_recommend_menu(recommend) unless recommend.nil? or recommend.empty? menu = Gtk::Menu.new() recommend_list = recommend.split(2.chr, 0) for site in recommend_list entry = site.split(1.chr, 0) next if entry.empty? title = entry[0] if title == '-' item = Gtk::SeparatorMenuItem.new() else item = Gtk::MenuItem.new(:label => title) if entry.length < 2 item.set_sensitive(false) end if entry.length > 1 url = entry[1] end if entry.length > 2 base_path = @parent.handle_request('GET', 'get_prefix') filename = entry[2].downcase tail = File.extname(filename) if tail.empty? for ext in ['.png', '.jpg', '.gif'] filename = [filename, ext].join('') banner = File.join( base_path, 'ghost/master/banner', filename) unless File.exists?(banner) banner = nil else break end end else banner = File.join( base_path, 'ghost/master/banner', filename) unless File.exists?(banner) banner = nil end end else banner = nil end if entry.length > 1 item.signal_connect('activate', title, url) do |a, title, url| @parent.handle_request('NOTIFY', 'notify_site_selection', title, url) next true end unless banner.nil? item.set_has_tooltip(true) pixbuf = Pix.create_pixbuf_from_file(banner) item.signal_connect('query-tooltip') do |widget, x, y, keyboardmode, tooltip| next on_tooltip(widget, x, y, keyboard_mode, tooltip, pixbuf) end else item.set_has_tooltip(false) end end end provider = create_css_provider_for(item) item.signal_connect('draw', provider) do |i, *a, provider| next set_stylecontext(i, *a, :provider => provider) end menu.add(item) item.show() end menuitem = @__menu_list['Recommend'][:entry] menuitem.set_submenu(menu) provider = create_css_provider_for(menu) menu.signal_connect('realize', provider) do |i, *a, provider| next set_stylecontext(i, *a, :provider => provider) end menu.show() __set_visible('Recommend', true) else __set_visible('Recommend', false) end end def create_ghost_menuitem(name, icon, key, handler, thumbnail) item = Gtk::MenuItem.new() box = Gtk::Box.new(Gtk::Orientation::HORIZONTAL, 6) unless icon.nil? pixbuf = Pix.create_icon_pixbuf(icon) unless pixbuf.nil? image = Gtk::Image.new image.pixbuf = pixbuf image.show box.pack_start(image, :expand => false, :fill => false, :padding => 0) end end label = Gtk::Label.new(name) label.xalign = 0.0 label.show box.pack_end(label, :expand => true, :fill => true, :padding => 0) box.show item.add(box) item.set_name('popup menu item') item.show() item.signal_connect('activate') do |a, v| handler.call(key) next true end unless thumbnail.nil? item.set_has_tooltip(true) pixbuf = Pix.create_pixbuf_from_file(thumbnail) item.signal_connect('query-tooltip') do |widget, x, y, keyboard_mode, tooltip| next on_tooltip(widget, x, y, keyboard_mode, tooltip, pixbuf) end else item.set_has_tooltip(false) end provider = create_css_provider_for(item) item.signal_connect('draw', provider) do |i, *a, provider| next set_stylecontext(i, *a, :provider => provider) end return item end def set_stylecontext(item, *args, provider: nil) _, offset_y = item.translate_coordinates(item.parent, 0, 0) provider.load(data: ["menu {\n", @__imagepath['background'], "background-repeat: repeat-y;\n", "color: ", "\#", sprintf("%02x", @__fontcolor['normal'][0]), sprintf("%02x", @__fontcolor['normal'][1]), sprintf("%02x", @__fontcolor['normal'][2]), ";\n", ["background-position: ", @__align['background'], " ", (-offset_y).to_s, "px;\n"].join(''), "}\n", "\n", "menu :disabled {\n", @__imagepath['background'], "background-repeat: repeat-y;\n", ["background-position: ", @__align['background'], " ", (-offset_y).to_s, "px;\n"].join(''), "}\n", "\n", "menu :hover {\n", @__imagepath['foreground'], "background-repeat: repeat-y;\n", "color: ", "\#", sprintf("%02x", @__fontcolor['hover'][0]), sprintf("%02x", @__fontcolor['hover'][1]), sprintf("%02x", @__fontcolor['hover'][2]), ";\n", ["background-position: ", @__align['foreground'], " ", (-offset_y).to_s, "px;\n"].join(''), "}" ].join("")) return false end def set_stylecontext_with_sidebar(item, *args, provider: nil) if @__imagepath['background_with_sidebar'].nil? or \ @__imagepath['foreground_with_sidebar'].nil? or \ @sidebar_width <= 0 set_stylecontext(item, *args, :provider => provider) return false end _, offset_y = item.translate_coordinates(item.parent, 0, 0) provider.load(data: ["menu {\n", @__imagepath['background_with_sidebar'], "background-repeat: repeat-y;\n", "color: ", "\#", sprintf("%02x", @__fontcolor['normal'][0]), sprintf("%02x", @__fontcolor['normal'][1]), sprintf("%02x", @__fontcolor['normal'][2]), ";\n", ["background-position: ", "0px ", (-offset_y).to_s, "px", ", ", @sidebar_width.to_s, "px", " ", (-offset_y).to_s, "px;\n"].join(''), ["padding-left: ", @sidebar_width.to_s, "px;\n"].join(''), "}\n", "\n", "menu :disabled {\n", @__imagepath['background_with_sidebar'], "background-repeat: repeat-y;\n", ["background-position: ", "0px ", (-offset_y).to_s, "px", ", ", @sidebar_width.to_s, "px", " ", (-offset_y).to_s, "px;\n"].join(''), ["padding-left: ", @sidebar_width.to_s, "px;\n"].join(''), "}\n", "\n", "menu :hover {\n", @__imagepath['foreground_with_sidebar'], "background-repeat: repeat-y;\n", "color: ", "\#", sprintf("%02x", @__fontcolor['hover'][0]), sprintf("%02x", @__fontcolor['hover'][1]), sprintf("%02x", @__fontcolor['hover'][2]), ";\n", ["background-position: ", "0px ", (-offset_y).to_s, "px", ", ", @sidebar_width.to_s, "px", " ", (-offset_y).to_s, "px;\n"].join(''), ["padding-left: ", @sidebar_width.to_s, "px;\n"].join(''), "}" ].join('')) return false end def on_tooltip(widget, x, y, keyboard_mode, tooltip, pixbuf) return false if pixbuf.nil? tooltip.set_icon(pixbuf) return true end def __set_ghost_menu for path in ['Summon', 'Change'] ghost_menu = Gtk::Menu.new() for items in @parent.handle_request('GET', 'get_ghost_menus') item = items[path] unless item.parent.nil? item.reparent(ghost_menu) else ghost_menu << item end end menuitem = @__menu_list[path][:entry] menuitem.set_submenu(ghost_menu) provider = create_css_provider_for(ghost_menu) ghost_menu.signal_connect('realize', provider) do |i, *a, provider| next set_stylecontext(i, *a, :provider => provider) end end end def __set_shell_menu shell_menu = @parent.handle_request('GET', 'get_shell_menu') menuitem = @__menu_list['Shell'][:entry] menuitem.set_submenu(shell_menu) end def __set_balloon_menu balloon_menu = @parent.handle_request('GET', 'get_balloon_menu') menuitem = @__menu_list['Balloon'][:entry] menuitem.set_submenu(balloon_menu) end def create_meme_menu(menuitem) menu = Gtk::Menu.new() for item in menuitem.values() unless item.parent.nil? item.reparent(menu) else menu << item end end provider = create_css_provider_for(menu) menu.signal_connect('realize', provider) do |i, *a, provider| next set_stylecontext(i, *a, :provider => provider) end return menu end def create_meme_menuitem(name, value, handler, thumbnail) item = Gtk::MenuItem.new(:label => name) item.set_name('popup menu item') item.show() item.signal_connect('activate') do |a, v| handler.call(value) next true end unless thumbnail.nil? item.set_has_tooltip(true) pixbuf = Pix.create_pixbuf_from_file(thumbnail) item.signal_connect('query-tooltip') do |widget, x, y, keyboard_mode, tooltip| next on_tooltip(widget, x, y, keyboard_mode, tooltip, pixbuf) end else item.set_has_tooltip(false) end provider = create_css_provider_for(item) item.signal_connect('draw', provider) do |i, *a, provider| next set_stylecontext(i, *a, :provider => provider) end return item end def __set_nekodorif_menu nekodorif_list = @parent.handle_request('GET', 'get_nekodorif_list') nekodorif_menu = Gtk::Menu.new() for i in 0..(nekodorif_list.length - 1) name = nekodorif_list[i]['name'] item = Gtk::MenuItem.new(:label => name) item.set_name('popup menu item') item.show() nekodorif_menu << item item.signal_connect('activate', nekodorif_list[i]['dir']) do |a, dir| @parent.handle_request('NOTIFY', 'select_nekodorif', dir) next true end provider = create_css_provider_for(item) item.signal_connect('draw', provider) do |i, *a, provider| next set_stylecontext(i, *a, :provider => provider) end ##if working ## item.set_sensitive(false) end menuitem = @__menu_list['Nekodorif'][:entry] menuitem.set_submenu(nekodorif_menu) provider = create_css_provider_for(nekodorif_menu) nekodorif_menu.signal_connect('realize', provider) do |i, *a, provider| next set_stylecontext(i, *a, :provider => provider) end end def __set_kinoko_menu kinoko_list = @parent.handle_request('GET', 'get_kinoko_list') kinoko_menu = Gtk::Menu.new() for i in 0..(kinoko_list.length - 1) name = kinoko_list[i]['title'] item = Gtk::MenuItem.new(:label => name) item.set_name('popup menu item') item.show() kinoko_menu << item item.signal_connect('activate', kinoko_list[i]) do |a, k| @parent.handle_request('NOTIFY', 'select_kinoko', k) next true end provider = create_css_provider_for(item) item.signal_connect('draw', provider) do |i, *a, provider| next set_stylecontext(i, *a, :provider => provider) end ##if working ## item.set_sensitive(false) end menuitem = @__menu_list['Kinoko'][:entry] menuitem.set_submenu(kinoko_menu) provider = create_css_provider_for(kinoko_menu) kinoko_menu.signal_connect('realize', provider) do |i, *a, provider| next set_stylecontext(i, *a, :provider => provider) end end def get_stick item = @__menu_list['Stick'][:entry] if not item.nil? and item.active? return true else return false end end end end ninix-aya-5.0.9/lib/ninix/balloon.rb0000644000175000017500000014453513416507430015477 0ustar shyshy# -*- coding: utf-8 -*- # # Copyright (C) 2001, 2002 by Tamito KAJIYAMA # Copyright (C) 2002, 2003 by MATSUMURA Namihiko # Copyright (C) 2002-2019 by Shyouzou Sugitani # Copyright (C) 2003 by Shun-ichi TAHARA # # This program is free software; you can redistribute it and/or modify it # under the terms of the GNU General Public License (version 2) as # published by the Free Software Foundation. It is distributed in the # hope that it will be useful, but WITHOUT ANY WARRANTY; without even the # implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR # PURPOSE. See the GNU General Public License for more details. # require "gtk3" require_relative "pix" require_relative "metamagic" module Balloon class Balloon < MetaMagic::Holon attr_accessor :window, :user_interaction def initialize super("") # FIXME @handlers = { 'reset_user_interaction' => 'reset_user_interaction', } @synchronized = [] @user_interaction = false @window = [] # create communicatebox @communicatebox = CommunicateBox.new() @communicatebox.set_responsible(self) # create teachbox @teachbox = TeachBox.new() @teachbox.set_responsible(self) # create inputbox @inputbox = InputBox.new() @inputbox.set_responsible(self) # create passwordinputbox @passwordinputbox = PasswordInputBox.new() @passwordinputbox.set_responsible(self) end def reset_user_interaction @user_interaction = false end def get_text_count(side) return 0 unless @window.length > side return @window[side].get_text_count() end def get_window(side) return nil unless @window.length > side return @window[side].get_window end def reset_text_count(side) return unless @window.length > side @window[side].reset_text_count() end def reset_balloon for balloon_window in @window balloon_window.reset_balloon() end end def create_gtk_window(title) window = Pix::TransparentWindow.new() window.set_title(title) window.set_skip_pager_hint(false) window.set_skip_taskbar_hint(true) window.signal_connect('delete_event') do |w, e| next delete(w, e) end window.realize() return window end def identify_window(win) for balloon_window in @window return true if win == balloon_window.get_window.window end return false end def delete(window, event) return true end def finalize for balloon_window in @window balloon_window.destroy() end @window = [] @communicatebox.destroy() @teachbox.destroy() @inputbox.destroy() @passwordinputbox.destroy() end def new_(desc, balloon) @desc = desc @directory = balloon['balloon_dir'][0] balloon0 = {} balloon1 = {} communicate0 = nil communicate1 = nil communicate2 = nil communicate3 = nil for key in balloon.keys value = balloon[key] if ['arrow0', 'arrow1'].include?(key) balloon0[key] = value balloon1[key] = value elsif key == 'sstp' balloon0[key] = value # sstp marker elsif key.start_with?('s') balloon0[key] = value # Sakura elsif key.start_with?('k') balloon1[key] = value # Unyuu elsif key == 'c0' communicate0 = value # send box elsif key == 'c1' communicate1 = value # communicate box elsif key == 'c2' communicate2 = value # teach box elsif key == 'c3' communicate3 = value # input box end end @balloon0 = balloon0 @balloon1 = balloon1 # create balloon windows for balloon_window in @window balloon_window.destroy() end @window = [] add_window(0) add_window(1) # configure communicatebox @communicatebox.new_(desc, communicate1) # configure teachbox @teachbox.new_(desc, communicate2) # configure inputbox @inputbox.new_(desc, communicate3) # configure passwordinputbox @passwordinputbox.new_(desc, communicate3) end def add_window(side) fail "assert" unless @window.length == side case side when 0 name = 'balloon.sakura' id_format = 's' balloon = @balloon0 when 1 name = 'balloon.kero' id_format = 'k' balloon = @balloon1 else name = ('balloon.char' + side.to_s) id_format = 'k' balloon = @balloon1 end gtk_window = create_gtk_window(name) balloon_window = BalloonWindow.new( gtk_window, side, @desc, balloon, id_format) balloon_window.set_responsible(self) @window << balloon_window end def reset_fonts for window in @window window.reset_fonts() end end def get_balloon_directory @directory end def get_balloon_size(side) return [0, 0] unless @window.length > side return @window[side].get_balloon_size() end def get_balloon_windowposition(side) return [0, 0] unless @window.length > side return @window[side].get_balloon_windowposition() end def set_balloon_default default_id = @parent.handle_request('GET', 'get_balloon_default_id') begin default_id = Integer(default_id) rescue default_id = 0 end for side in 0..@window.length-1 @window[side].set_balloon(default_id) end end def set_balloon(side, num) return unless @window.length > side @window[side].set_balloon(num) end def set_position(side, base_x, base_y) return unless @window.length > side @window[side].set_position(base_x, base_y) end def get_position(side) return [0, 0,] unless @window.length > side return @window[side].get_position() end def set_autoscroll(flag) for side in 0..@window.length-1 @window[side].set_autoscroll(flag) end end def is_shown(side) return false if @window.length <= side return @window[side].is_shown() end def show(side) return unless @window.length > side @window[side].show() end def hide_all for side in 0..@window.length-1 @window[side].hide() end end def hide(side) return unless @window.length > side @window[side].hide() end def raise_all for side in 0..@window.length-1 @window[side].raise_() end end def raise_(side) return unless @window.length > side @window[side].raise_() end def lower_all for side in 0..@window.length-1 @window[side].lower() end end def lower(side) return unless @window.length > side @window[side].lower() end def synchronize(list) @synchronized = list end def clear_text_all for side in 0..@window.length-1 clear_text(side) end end def clear_text(side) unless @synchronized.empty? for side in @synchronized next unless @window.length > side @window[side].clear_text() end else if @window.length > side @window[side].clear_text() end end end def append_text(side, text) unless @synchronized.empty? for side in @synchronized next unless @window.length > side @window[side].append_text(text) end else if @window.length > side @window[side].append_text(text) end end end def append_sstp_marker(side) return unless @window.length > side @window[side].append_sstp_marker() end def append_link_in(side, label) unless @synchronized.empty? for side in @synchronized next unless @window.length > side @window[side].append_link_in(label) end else if @window.length > side @window[side].append_link_in(label) end end end def append_link_out(side, label, value) unless @synchronized.empty? for side in @synchronized next unless @window.length > side @window[side].append_link_out(label, value) end else if @window.length > side @window[side].append_link_out(label, value) end end end def append_link(side, label, value, newline_required: false) unless @synchronized.empty? for side in @synchronized if @window.length > side @window[side].append_link_in(label) @window[side].append_text(value) @window[side].append_link_out(label, value) if newline_required @window[side].set_newline() end end end else if @window.length > side @window[side].append_link_in(label) @window[side].append_text(value) @window[side].append_link_out(label, value) if newline_required @window[side].set_newline() end end end end def append_meta(side, tag) unless @synchronized.empty? for side in @synchronized next unless @window.length > side @window[side].append_meta(tag) end else if @window.length > side @window[side].append_meta(tag) end end end def append_image(side, path, x, y) return if side >= @window.length @window[side].append_image(path, x, y) end def show_sstp_message(message, sender) @window[0].show_sstp_message(message, sender) end def hide_sstp_message @window[0].hide_sstp_message() end def open_communicatebox return if @user_interaction @user_interaction = true @communicatebox.show() end def open_teachbox return if @user_interaction @user_interaction = true @parent.handle_request('NOTIFY', 'notify_event', 'OnTeachStart') @teachbox.show() end def open_inputbox(symbol, limittime: -1, default: nil) return if @user_interaction @user_interaction = true @inputbox.set_symbol(symbol) @inputbox.set_limittime(limittime) @inputbox.show(default) end def open_passwordinputbox(symbol, limittime: -1, default: nil) return if @user_interaction @user_interaction = true @passwordinputbox.set_symbol(symbol) @passwordinputbox.set_limittime(limittime) @passwordinputbox.show(default) end def close_inputbox(symbol) return unless @user_interaction @inputbox.close(symbol) @passwordinputbox.close(symbol) end end class BalloonWindow attr_accessor :direction def initialize(window, side, desc, balloon, id_format) @window = window @side = side @parent = nil @desc = desc @balloon = balloon @balloon_id = nil @id_format = id_format @num = 0 @__shown = false @sstp_marker = [] @sstp_region = nil @sstp_message = nil @images = [] @width = 0 @height = 0 @__font_name = '' @text_count = 0 @balloon_surface = nil @autoscroll = true @dragged = false @x_root = nil @y_root = nil @x_fractions = 0 @y_fractions = 0 @reshape = true @darea = @window.darea @darea.set_events(Gdk::EventMask::EXPOSURE_MASK| Gdk::EventMask::BUTTON_PRESS_MASK| Gdk::EventMask::BUTTON_RELEASE_MASK| Gdk::EventMask::POINTER_MOTION_MASK| Gdk::EventMask::POINTER_MOTION_HINT_MASK| Gdk::EventMask::SCROLL_MASK) @darea.signal_connect('draw') do |w, e| redraw(w, e) next true end @darea.signal_connect('button_press_event') do |w, e| next button_press(w, e) end @darea.signal_connect('button_release_event') do |w, e| #button_release(w, e) next true end @darea.signal_connect('motion_notify_event') do |w, e| next motion_notify(w, e) end @darea.signal_connect('scroll_event') do |w, e| next scroll(w, e) end @layout = Pango::Layout.new(@darea.pango_context) @sstp_layout = Pango::Layout.new(@darea.pango_context()) mask_r = desc.get('maskcolor.r', :default => 128).to_i mask_g = desc.get('maskcolor.g', :default => 128).to_i mask_b = desc.get('maskcolor.b', :default => 128).to_i @cursor_color = [mask_r / 255.0, mask_g / 255.0, mask_b / 255.0] text_r = desc.get(['font.color.r', 'fontcolor.r'], :default => 0).to_i text_g = desc.get(['font.color.g', 'fontcolor.g'], :default => 0).to_i text_b = desc.get(['font.color.b', 'fontcolor.b'], :default => 0).to_i @text_normal_color = [text_r / 255.0, text_g / 255.0, text_b / 255.0] if desc.get('maskmethod').to_i == 1 text_r = (255 - text_r) text_g = (255 - text_g) text_b = (255 - text_b) end @text_active_color = [text_r / 255.0, text_g / 255.0, text_b / 255.0] sstp_r = desc.get('sstpmessage.font.color.r', :default => text_r).to_i sstp_g = desc.get('sstpmessage.font.color.g', :default => text_g).to_i sstp_b = desc.get('sstpmessage.font.color.b', :default => text_b).to_i @sstp_message_color = [sstp_r / 255.0, sstp_g / 255.0, sstp_b / 255.0] # initialize @__direction = [side, 1].min ## kluge: multi character @position = [0, 0] reset_fonts() clear_text() end def get_window @window end def set_responsible(parent) @parent = parent end #@property def scale scaling = (not @parent.handle_request('GET', 'get_preference', 'balloon_scaling').zero?) scale = @parent.handle_request('GET', 'get_preference', 'surface_scale') if scaling return scale else return 100 # [%] end end def direction @__direction end def direction=(value) if @__direction != value @__direction = value # 0: left, 1: right reset_balloon() end end def get_balloon_windowposition x = __get_with_scaling('windowposition.x', 0).to_i y = __get_with_scaling('windowposition.y', 0).to_i return x, y end def get_image_surface(balloon_id) return nil unless @balloon.include?(balloon_id) begin path, config = @balloon[balloon_id] use_pna = (not @parent.handle_request('GET', 'get_preference', 'use_pna').zero?) surface = Pix.create_surface_from_file(path, :use_pna => use_pna) rescue return nil end return surface end def reset_fonts unless @parent.nil? font_name = @parent.handle_request('GET', 'get_preference', 'balloon_fonts') else font_name = nil end return if @__font_name == font_name @font_desc = Pango::FontDescription.new(font_name) pango_size = @font_desc.size if pango_size.zero? default_size = 12 # for Windows environment size = @desc.get(['font.height', 'font.size'], :default => default_size).to_i pango_size = (size * 3 / 4) # convert from Windows to GTK+ pango_size *= Pango::SCALE end @font_desc.set_size(pango_size) @__font_name = font_name @layout.set_font_description(@font_desc) @layout.set_wrap(:char) # XXX # font for sstp message if @side.zero? @sstp_font_desc = Pango::FontDescription.new(font_name) pango_size = @sstp_font_desc.size if pango_size.zero? default_size = 10 # for Windows environment size = @desc.get('sstpmessage.font.height', :default => default_size).to_i pango_size = (size * 3 / 4) # convert from Windows to GTK+ pango_size *= Pango::SCALE end @sstp_font_desc.set_size(pango_size) @sstp_layout.set_font_description(@sstp_font_desc) @sstp_layout.set_wrap(:char) end unless @balloon_id.nil? reset_message_regions() if @__shown @darea.queue_draw() end end end def reset_sstp_marker if @side.zero? fail "assert" if @balloon_surface.nil? w = @balloon_surface.width h = @balloon_surface.height # sstp marker position @sstp = [] x = config_adjust('sstpmarker.x', w, 30) y = config_adjust('sstpmarker.y', h, -20) @sstp << [x, y] # sstp marker x = config_adjust('sstpmessage.x', w, 50) y = config_adjust('sstpmessage.y', h, -20) @sstp << [x, y] # sstp message end # sstp marker surface (not only for @side.zero?) @sstp_surface = get_image_surface('sstp') end def reset_arrow # arrow positions @arrow = [] fail "assert" if @balloon_surface.nil? w = @balloon_surface.width h = @balloon_surface.height x = config_adjust('arrow0.x', w, -10) y = config_adjust('arrow0.y', h, 10) @arrow << [x, y] x = config_adjust('arrow1.x', w, -10) y = config_adjust('arrow1.y', h, -20) @arrow << [x, y] # arrow surfaces and sizes @arrow0_surface = get_image_surface('arrow0') @arrow1_surface = get_image_surface('arrow1') end def reset_message_regions w, h = @layout.pixel_size @font_height = h @line_space = 1 @layout.set_spacing(@line_space) # font metrics origin_x = __get('origin.x', __get('zeropoint.x', __get('validrect.left', 14).to_i).to_i).to_i origin_y = __get('origin.y', __get('zeropoint.y', __get('validrect.top', 14).to_i).to_i).to_i wpx = __get('wordwrappoint.x', __get('validrect.right', -14).to_i).to_i if wpx > 0 line_width = (wpx - origin_x) elsif wpx < 0 line_width = (@width - origin_x + wpx) else line_width = (@width - origin_x * 2) end wpy = __get('validrect.bottom', -14).to_i if wpy > 0 text_height = ([wpy, @height].min - origin_y) elsif wpy < 0 text_height = (@height - origin_y + wpy) else text_height = (@height - origin_y * 2) end line_height = (@font_height + @line_space) @lines = (text_height / line_height).to_i @line_regions = [] y = origin_y for _ in 0..@lines @line_regions << [origin_x, y, line_width, line_height] y = (y + line_height) end @line_width = line_width # sstp message region if @side.zero? w, h = @sstp_layout.pixel_size x, y = @sstp[1] w = (line_width + origin_x - x) @sstp_region = [x, y, w, h] end end def update_line_regions(offset, new_y) origin_y = __get('origin.y', __get('zeropoint.y', __get('validrect.top', 14).to_i).to_i).to_i wpy = __get('validrect.bottom', -14).to_i if wpy > 0 text_height = ([wpy, @height].min - origin_y) elsif wpy < 0 text_height = (@height - origin_y + wpy) else text_height = (@height - origin_y * 2) end line_height = (@font_height + @line_space) origin_x, y, line_width, line_height = @line_regions[offset] @lines = (offset + ((text_height - new_y) / line_height).to_i) y = new_y for i in offset..(@line_regions.length - 1) @line_regions[i] = [origin_x, y, line_width, line_height] y += line_height end for i in @line_regions.length..@lines @line_regions << [origin_x, y, line_width, line_height] y += line_height end end def get_balloon_size(scaling: true) w = @width h = @height if scaling w = (w * scale / 100.0).to_i h = (h * scale / 100.0).to_i end return w, h end def reset_balloon set_balloon(@num) end def set_balloon(num) @num = num balloon_id = (@id_format + (num * 2 + @__direction).to_i.to_s) @balloon_surface = get_image_surface(balloon_id) if @balloon_surface.nil? balloon_id = (@id_format + (0 + @__direction).to_i.to_s) @balloon_surface = get_image_surface(balloon_id) end fail "assert" if @balloon_surface.nil? @balloon_id = balloon_id # change surface and window position x, y = @position @width = @balloon_surface.width @height = @balloon_surface.height reset_arrow() reset_sstp_marker() reset_message_regions() @parent.handle_request('NOTIFY', 'position_balloons') @darea.queue_draw() if @__shown @reshape = true end def set_autoscroll(flag) @autoscroll = flag end def config_adjust(name, base, default_value) path, config = @balloon[@balloon_id] value = config.get(name) if value.nil? value = @desc.get(name) end if value.nil? value = default_value end value = value.to_i if value < 0 value = (base + value) end return value.to_i end def __get(name, default_value) path, config = @balloon[@balloon_id] value = config.get(name) if value.nil? value = @desc.get(name) if value.nil? value = default_value end end return value end def __get_with_scaling(name, default_value) path, config = @balloon[@balloon_id] value = config.get(name) if value.nil? value = @desc.get(name) if value.nil? value = default_value end end return (value.to_f * scale / 100) end def __move x, y = get_position() @window.move(x, y) end def set_position(base_x, base_y) return if @balloon_id.nil? px, py = get_balloon_windowposition() w, h = get_balloon_size() if @__direction.zero? x = (base_x + px - w) else x = (base_x + px) end y = (base_y + py) left, top, scrn_w, scrn_h = @parent.handle_request('GET', 'get_workarea') if (y + h) > scrn_h # XXX y = (scrn_h - h) end if y < top # XXX y = top end @position = [x, y] __move() end def get_position @position end def destroy(finalize: 0) @window.destroy() end def is_shown @__shown end def show return if @parent.handle_request('GET', 'lock_repaint') return if @__shown @__shown = true # make sure window is in its position (call before showing the window) __move() @window.show() # make sure window is in its position (call after showing the window) __move() raise_() @reshape = true end def hide return unless @__shown @window.hide() @__shown = false @images = [] end def raise_ return unless @__shown @window.window.raise end def lower return unless @__shown @window.get_window().lower() end def show_sstp_message(message, sender) show() if @sstp_region.nil? @sstp_message = (message.to_s + " (" + sender.to_s + ")") x, y, w, h = @sstp_region @sstp_layout.set_text(@sstp_message) message_width, message_height = @sstp_layout.pixel_size if message_width > w @sstp_message = ('... (' + sender + ')') i = 0 while true i += 1 s = (message[0, i] + '... (' + sender + ')') @sstp_layout.set_text(s) message_width, message_height = \ @sstp_layout.pixel_size break if message_width > w @sstp_message = s end end @darea.queue_draw() end def hide_sstp_message @sstp_message = nil @darea.queue_draw() end def redraw_sstp_message(widget, cr) return if @sstp_message.nil? cr.save() # draw sstp marker unless @sstp_surface.nil? x, y = @sstp[0] cr.set_source(@sstp_surface, x, y) cr.paint() end # draw sstp message x, y, w, h = @sstp_region @sstp_layout.set_text(@sstp_message) cr.set_source_rgb(@sstp_message_color) cr.move_to(x, y) cr.show_pango_layout(@sstp_layout) cr.restore() end def redraw_arrow0(widget, cr) return if @lineno <= 0 cr.save() x, y = @arrow[0] cr.set_source(@arrow0_surface, x, y) cr.paint() cr.restore() end def redraw_arrow1(widget, cr) return if (@lineno + @lines) >= @text_buffer.length cr.save() x, y = @arrow[1] cr.set_source(@arrow1_surface, x, y) cr.paint() cr.restore() end def markup_escape_text(markup_string) ## FIXME: GLib::Markup.escape_text escaped_string = markup_string.dup { '<' => '<', '>' => '>', '&' => '&' }.each do |pattern, replace| escaped_string.gsub!(pattern, replace) end return escaped_string end def set_markup(index, text) tags_ = ['sup', 'sub', 's', 'u'] count_ = {} for tag_ in tags_ count_[tag_] = 0 end markup_list = [] for sl, sn, tag in @meta_buffer if sl == index markup_list << [sn, tag] end end if markup_list.empty? return markup_escape_text(text) end markup_list.sort! markup_list.reverse! pn = text.length for sn, tag in markup_list text = [text[0, sn], tag, markup_escape_text(text[sn, pn]), text[pn, text.length]].join('') pn = sn if tag[1] == '/' tag_ = tag[2, tag.length - 1] fail "assert" unless tags_.include?(tag_) count_[tag_] -= 1 if count_[tag_] < 0 text = ['<', tag_, '>', text].join('') count_[tag_] += 1 end else tag_ = tag[1, tag.length - 1] fail "assert" unless tags_.include?(tag_) count_[tag_] += 1 if count_[tag_] > 0 text = [text, ''].join('') count_[tag_] -= 1 end end end return text end def redraw(widget, cr) return if @parent.handle_request('GET', 'lock_repaint') return true unless @__shown fail "assert" if @balloon_surface.nil? @window.set_surface(cr, @balloon_surface, scale) cr.set_operator(Cairo::OPERATOR_OVER) # restore default cr.translate(*@window.get_draw_offset) # XXX # draw images for i in 0..(@images.length - 1) image_surface, (x, y) = @images[i] w = image_surface.width h = image_surface.height if x == 'centerx' bw, bh = get_balloon_size(:scaling => false) x = ((bw - w) / 2) else begin x = Integer(x) rescue next end end if y == 'centery' bw, bh = get_balloon_size(:scaling => false) y = ((bh - h) / 2) else begin y = Integer(y) rescue next end end cr.set_source(image_surface, x, y) cr.paint() end # draw text i = @lineno j = @text_buffer.length line = 0 while line < @lines if i >= j break end x, y, w, h = @line_regions[line] if @text_buffer[i].end_with?('\n[half]') temp = @text_buffer[i] new_y = y while temp.end_with?('\n[half]') new_y += ((@font_height + @line_space) / 2).to_i temp = temp[0..-9] end markup = set_markup(i, temp) else new_y = (y + @font_height + @line_space).to_i markup = set_markup(i, @text_buffer[i]) end update_line_regions(line + 1, new_y) @layout.set_markup(markup) cr.set_source_rgb(@text_normal_color) cr.move_to(x, y) cr.show_pango_layout(@layout) unless @sstp_surface.nil? for l, c in @sstp_marker if l == i mw = @sstp_surface.width mh = @sstp_surface.height @layout.set_text(@text_buffer[i][0, c]) text_w, text_h = @layout.pixel_size mx = (x + text_w) my = (y + (@font_height + @line_space) / 2) my = (my - mh / 2) cr.set_source(@sstp_surface, mx, my) cr.paint() end end end i += 1 line += 1 end redraw_sstp_message(widget, cr) if @side.zero? and not @sstp_message.nil? update_link_region(widget, cr, @selection) unless @selection.nil? redraw_arrow0(widget, cr) redraw_arrow1(widget, cr) @window.set_shape(cr, @reshape) @reshape = false return false end def update_link_region(widget, cr, index) cr.save() sl = @link_buffer[index][0] el = @link_buffer[index][2] if @lineno <= sl and sl <= (@lineno + @lines) sn = @link_buffer[index][1] en = @link_buffer[index][3] for n in sl..el if (n - @lineno) >= @line_regions.length break end x, y, w, h = @line_regions[n - @lineno] if sl == el markup = set_markup(n, @text_buffer[n][0, sn]) @layout.set_markup(markup) text_w, text_h = @layout.pixel_size x += text_w markup = set_markup(n, @text_buffer[n][sn, en]) @layout.set_markup(markup) text_w, text_h = @layout.pixel_size w = text_w start = sn end_ = en elsif n == sl markup = set_markup(n, @text_buffer[n][0, sn]) @layout.set_markup(markup) text_w, text_h = @layout.pixel_size x += text_w markup = set_markup(n, @text_buffer[n][sn, @text_buffer.length]) @layout.set_markup(markup) text_w, text_h = @layout.pixel_size w = text_w start = sn end_ = @text_buffer[n].length elsif n == el markup = set_markup(n, @text_buffer[n][0, en]) @layout.set_markup(markup) text_w, text_h = @layout.pixel_size w = text_w start = 0 end_ = en else markup = set_markup(n, @text_buffer[n]) @layout.set_markup(markup) text_w, text_h = @layout.pixel_size w = text_w start = 0 end_ = @text_buffer[n].length end markup = set_markup(n, @text_buffer[n][start, end_]) @layout.set_markup(markup) cr.set_source_rgb(@cursor_color) cr.rectangle(x, y, w, h) cr.fill() cr.move_to(x, y) cr.set_source_rgb(@text_active_color) cr.show_pango_layout(@layout) end end cr.restore() end def check_link_region(px, py) new_selection = nil for i in 0..(@link_buffer.length - 1) sl = @link_buffer[i][0] el = @link_buffer[i][2] if @lineno <= sl and sl <= (@lineno + @lines) sn = @link_buffer[i][1] en = @link_buffer[i][3] for n in sl..el if (n - @lineno) >= @line_regions.length break end x, y, w, h = @line_regions[n - @lineno] if n == sl markup = set_markup(n, @text_buffer[n][0, sn]) @layout.set_markup(markup) text_w, text_h = @layout.pixel_size x += text_w end if n == sl and n == el markup = set_markup(n, @text_buffer[n][sn, en]) elsif n == el markup = set_markup(n, @text_buffer[n][0, en]) else markup = set_markup(n, @text_buffer[n]) end @layout.set_markup(markup) text_w, text_h = @layout.pixel_size w = text_w if x <= px and px < (x + w) and y <= py and py < (y + h) new_selection = i break end end end end unless new_selection.nil? if @selection != new_selection sl, sn, el, en, link_id, raw_text, text = \ @link_buffer[new_selection] @parent.handle_request( 'NOTIFY', 'notify_event', 'OnChoiceEnter', raw_text, link_id, @selection) end else unless @selection.nil? @parent.handle_request('NOTIFY', 'notify_event', 'OnChoiceEnter') end end if new_selection == @selection return false else @selection = new_selection return true # dirty flag end end def motion_notify(widget, event) x, y, state = event.x, event.y, event.state px, py = @window.winpos_to_surfacepos(x, y, scale) unless @link_buffer.empty? if check_link_region(px, py) widget.queue_draw() end end unless @parent.handle_request('GET', 'busy') if (state & Gdk::ModifierType::BUTTON1_MASK).nonzero? unless @x_root.nil? or @y_root.nil? @dragged = true x_delta = ((event.x_root - @x_root) * 100 / scale + @x_fractions) y_delta = ((event.y_root - @y_root) * 100 / scale + @y_fractions) @x_fractions = (x_delta - x_delta.to_i) @y_fractions = (y_delta - y_delta.to_i) @parent.handle_request( 'NOTIFY', 'update_balloon_offset', @side, x_delta.to_i, y_delta.to_i) @x_root = event.x_root @y_root = event.y_root end end end Gdk::Event.request_motions(event) if event.is_hint == 1 return true end def scroll(darea, event) px, py = @window.winpos_to_surfacepos( event.x.to_i, event.y.to_i, scale) case event.direction when Gdk::ScrollDirection::UP if @lineno > 0 @lineno = [@lineno - 2, 0].max check_link_region(px, py) @darea.queue_draw() end when Gdk::ScrollDirection::DOWN if (@lineno + @lines) < @text_buffer.length @lineno = [@lineno + 2, @text_buffer.length - @lines].min check_link_region(px, py) @darea.queue_draw() end end return true end def button_press(darea, event) @parent.handle_request('NOTIFY', 'reset_idle_time') click = event.event_type == Gdk::EventType::BUTTON_PRESS ? 1 : 2 if @parent.handle_request('GET', 'is_paused') @parent.handle_request('NOTIFY', 'notify_balloon_click', event.button, click, @side) return true end # arrows px, py = @window.winpos_to_surfacepos( event.x.to_i, event.y.to_i, scale) # up arrow w = @arrow0_surface.width h = @arrow0_surface.height x, y = @arrow[0] if x <= px and px <= (x + w) and y <= py and py <= (y + h) if @lineno > 0 @lineno = [@lineno - 2, 0].max @darea.queue_draw() end return true end # down arrow w = @arrow1_surface.width h = @arrow1_surface.height x, y = @arrow[1] if x <= px and px <= (x + w) and y <= py and py <= (y + h) if @lineno + @lines < @text_buffer.length @lineno = [@lineno + 2, @text_buffer.length - @lines].min @darea.queue_draw() end return true end # links unless @selection.nil? sl, sn, el, en, link_id, raw_text, text = \ @link_buffer[@selection] @parent.handle_request('NOTIFY', 'notify_link_selection', link_id, raw_text, @selection) return true end # balloon's background @parent.handle_request('NOTIFY', 'notify_balloon_click', event.button, click, @side) @x_root = event.x_root @y_root = event.y_root return true end def button_release(window, event) x, y = @window.winpos_to_surfacepos( event.x.to_i, event.y.to_i, scale) @dragged = false if @dragged @x_root = nil @y_root = nil @y_fractions = 0 @y_fractions = 0 return true end def clear_text @selection = nil @lineno = 0 @text_buffer = [] @meta_buffer = [] @link_buffer = [] @newline_required = false @images = [] @sstp_marker = [] @darea.queue_draw() end def get_text_count @text_count end def reset_text_count @text_count = 0 end def set_newline @newline_required = true end def append_text(text) if @text_buffer.empty? s = '' column = 0 index = 0 elsif @newline_required s = '' column = 0 @newline_required = false index = @text_buffer.length else index = (@text_buffer.length - 1) s = @text_buffer.pop column = s.length end i = s.length text = [s, text].join('') j = text.length @text_count += j p = 0 while true if i >= j @text_buffer << text[p..i-1] draw_last_line(:column => column) break end if text[i, 2] == '\n' if j >= (i + 8) and text[i, 8] == '\n[half]' @text_buffer << [text[p..i-1], '\n[half]'].join('') p = i = (i + 8) else if i.zero? @text_buffer << "" else @text_buffer << text[p..i-1] end p = i = (i + 2) end draw_last_line(:column => column) column = 0 next end n = (i + 1) show unless @__shown markup = set_markup(index, text[p..n-1]) @layout.set_markup(markup) text_width, text_height = @layout.pixel_size if text_width > @line_width @text_buffer << text[p..i-1] draw_last_line(:column => column) column = 0 p = i end i = n end end def append_sstp_marker return if @sstp_surface.nil? if @text_buffer.empty? line = 0 offset = 0 else line = (@text_buffer.length - 1) offset = @text_buffer[-1].length end if @newline_required line = (line + 1) offset = 0 end @sstp_marker << [line, offset] w = @sstp_surface.width h = @sstp_surface.height i = 1 while true space = ("\u3000" * i) # XXX @layout.set_text(space) text_w, text_h = @layout.pixel_size break if text_w > w i += 1 end append_text(space) draw_last_line(:column => offset) end def append_link_in(link_id) if @text_buffer.empty? sl = 0 sn = 0 else sl = (@text_buffer.length - 1) sn = @text_buffer[-1].length end @link_buffer << [sl, sn, sl, sn, link_id, '', ''] end def append_link_out(link_id, text) return unless text raw_text = text if @text_buffer.empty? el = 0 en = 0 else el = (@text_buffer.length - 1) en = @text_buffer[-1].length end for i in 0..@link_buffer.length-1 if @link_buffer[i][4] == link_id sl = @link_buffer[i][0] sn = @link_buffer[i][1] @link_buffer.slice!(i) @link_buffer.insert(i, [sl, sn, el, en, link_id, raw_text, text]) break end end end def append_meta(tag) return if tag.nil? if @text_buffer.empty? sl = 0 sn = 0 else sl = (@text_buffer.length - 1) sn = @text_buffer[-1].length end @meta_buffer << [sl, sn, tag] end def append_image(path, x, y) begin image_surface = Pix.create_surface_from_file(path) rescue return end show() @images << [image_surface, [x, y]] @darea.queue_draw() end def draw_last_line(column: 0) return unless @__shown line = (@text_buffer.length - 1) if @lineno <= line and line < (@lineno + @lines) x, y, w, h = @line_regions[line - @lineno] if @text_buffer[line].end_with?('\n[half]') offset = (line - @lineno + 1) new_y = (y + (@font_height + @line_space) / 2).to_i update_line_regions(offset, new_y) else @darea.queue_draw() end unless @sstp_surface.nil? for l, c in @sstp_marker if l == line mw = @sstp_surface.width mh = @sstp_surface.height @layout.set_text(@text_buffer[l][0, c]) text_w, text_h = @layout.pixel_size mx = (x + text_w) my = (y + (@font_height + @line_space) / 2) my = (my - mh / 2) cr = @darea.window.create_cairo_context cr.set_source(@sstp_surface, mx, my) cr.paint() end end end else @darea.queue_draw() if @autoscroll while line >= (@lineno + @lines) @lineno += 1 @darea.queue_draw() end end end end end class CommunicateWindow NAME = '' ENTRY = '' def initialize @parent = nil @window = nil end def set_responsible(parent) @parent = parent end def new_(desc, balloon) @window.destroy unless @window.nil? @window = Pix::BaseTransparentWindow.new() @__surface_position = [0, 0] @window.set_title('communicate') @window.signal_connect('delete_event') do |w ,e| next delete(w, e) end @window.signal_connect('key_press_event') do |w, e| next key_press(w, e) end @window.signal_connect('button_press_event') do |w, e| next button_press(w, e) end @window.signal_connect('drag_data_received') do |widget, context, x, y, data, info, time| drag_data_received(widget, context, x, y, data, info, time) next true end # DnD data types dnd_targets = [['text/plain', 0, 0]] @window.drag_dest_set(Gtk::DestDefaults::ALL, dnd_targets, Gdk::DragAction::COPY) @window.drag_dest_add_text_targets() @window.set_events(Gdk::EventMask::BUTTON_PRESS_MASK) @window.set_modal(true) #@window.set_window_position(Gtk::WindowPosition::CENTER) @window.realize() @window.override_background_color( Gtk::StateFlags::NORMAL, Gdk::RGBA.new(0, 0, 0, 0)) w = desc.get('communicatebox.width', :default => 250).to_i h = desc.get('communicatebox.height', :default => -1).to_i left, top, scrn_w, scrn_h = @parent.handle_request('GET', 'get_workarea') @__surface_position = [(scrn_w - w) / 2, (scrn_h - h) / 2] # XXX @entry = Gtk::Entry.new @entry.signal_connect('activate') do |w| next activate(w) end @entry.set_inner_border(nil) @entry.set_has_frame(false) font_desc = Pango::FontDescription.new() font_desc.set_size(9 * 3 / 4 * Pango::SCALE) # XXX @entry.override_font(font_desc) @entry.set_size_request(w, h) @entry.show() surface = nil unless balloon.nil? path, config = balloon # load pixbuf begin surface = Pix.create_surface_from_file(path) rescue surface = nil end end unless surface.nil? darea = Gtk::DrawingArea.new() darea.set_events(Gdk::EventMask::EXPOSURE_MASK) darea.signal_connect('draw') do |w, e| redraw(w, e, surface) next true end darea.show() x = desc.get('communicatebox.x', :default => 10).to_i y = desc.get('communicatebox.y', :default => 20).to_i overlay = Gtk::Overlay.new() @entry.set_margin_left(x + get_draw_offset()[0]) @entry.set_margin_top(y + get_draw_offset()[1]) @entry.set_halign(Gtk::Align::START) @entry.set_valign(Gtk::Align::START) overlay.add_overlay(@entry) overlay.add(darea) overlay.show() @window.add(overlay) darea.set_size_request(*@window.size) # XXX else box = Gtk::Box.new(orientation=Gtk::Orientation::HORIZONTAL, spacing=10) box.set_border_width(10) unless ENTRY.empty? label = Gtk::Label.new(label=ENTRY) box.pack_start(label, :expand => false, :fill => true, :padding => 0) label.show() end box.pack_start(@entry, :expand => true, :fill => true, :padding => 0) @window.add(box) box.show() end end def drag_data_received(widget, context, x, y, data, info, time) @entry.set_text(data.text) end def get_draw_offset return @__surface_position end def redraw(widget, cr, surface) cr.save() # clear cr.set_operator(Cairo::OPERATOR_SOURCE) cr.set_source_rgba(0, 0, 0, 0) cr.paint # translate the user-space origin cr.translate(*get_draw_offset) # XXX cr.set_source(surface, 0, 0) cr.set_operator(Cairo::OPERATOR_SOURCE) # copy rectangle on the destination cr.rectangle(0, 0, surface.width, surface.height) cr.fill() cr.restore() return if RbConfig::CONFIG['host_os'] =~ /mswin|mingw|cygwin/ region = Pix.surface_to_region(cr.target.map_to_image) # XXX: to avoid losing focus in the text input region x = @entry.margin_left y = @entry.margin_top w = @entry.allocated_width h = @entry.allocated_height region.union!(x, y, w, h) if @window.supports_alpha @window.input_shape_combine_region(nil) @window.input_shape_combine_region(region) else @window.shape_combine_region(nil) @window.shape_combine_region(region) end end def destroy @window.destroy unless @window.nil? @window = nil end def delete(widget, event) @window.hide() cancel() return true end def key_press(widget, event) if event.keyval == Gdk::Keyval::KEY_Escape @window.hide() cancel() return true end return false end def button_press(widget, event) if [1, 2].include?(event.button) @window.begin_move_drag( event.button, event.x_root.to_i, event.y_root.to_i, Gtk.current_event_time()) end return true end def activate(widget) @window.hide() enter() return true end def show(default: '') @entry.set_text(default) @window.show() end def enter #pass end def cancel #pass end end class CommunicateBox < CommunicateWindow NAME = 'communicatebox' ENTRY = 'Communicate' def new_(desc, balloon) super @window.set_modal(false) end def delete(widget, event) @window.hide() cancel() @parent.handle_request('NOTIFY', 'reset_user_interaction') return true end def key_press(widget, event) if event.keyval == Gdk::Keyval::KEY_Escape @window.hide() cancel() @parent.handle_request('NOTIFY', 'reset_user_interaction') return true end return false end def activate(widget) enter() @entry.set_text('') return true end def enter send(@entry.text) end def cancel @parent.handle_request('NOTIFY', 'notify_event', 'OnCommunicateInputCancel', '', 'cancel') end def send(data) unless data.nil? @parent.handle_request('NOTIFY', 'notify_event', 'OnCommunicate', 'user', data) end end end class TeachBox < CommunicateWindow NAME = 'teachbox' ENTRY = 'Teach' def enter send(@entry.text) end def cancel @parent.handle_request('NOTIFY', 'notify_event', 'OnTeachInputCancel', '', 'cancel') @parent.handle_request('NOTIFY', 'reset_user_interaction') end def send(data) @parent.handle_request('NOTIFY', 'notify_user_teach', data) @parent.handle_request('NOTIFY', 'reset_user_interaction') end end class InputBox < CommunicateWindow NAME = 'inputbox' ENTRY = 'Input' def new_(desc, balloon) super @symbol = nil @limittime = -1 end def set_symbol(symbol) @symbol = symbol end def set_limittime(limittime) begin limittime = Integer(limittime) rescue limittime = -1 end @limittime = limittime end def show(default) unless default.nil? begin text = default.to_s rescue text = '' end else text = '' end if @limittime.to_i < 0 @timeout_id = nil else @timeout_id = GLib.timeout_add(@limittime, @timeout) end super(:default => text) end def timeout @window.hide() send('timeout', :timeout => true) end def enter send(@entry.text) end def cancel send(nil, :cancel => true) end def close(symbol) return if @symbol.nil? return if symbol != '__SYSTEM_ALL_INPUT__' and @symbol != symbol @window.hide() cancel() end def send(data, cancel: false, timeout: false) GLib.source_remove(@timeout_id) unless @timeout_id.nil? data = '' if data.nil? ## CHECK: symbol if cancel @parent.handle_request('NOTIFY', 'notify_event', 'OnUserInputCancel', '', 'cancel') elsif timeout and \ not @parent.handle_request('GET', 'notify_event', 'OnUserInputCancel', '', 'timeout').nil? # pass elsif @symbol == 'OnUserInput' and \ not @parent.handle_request('GET', 'notify_event', 'OnUserInput', data).nil? # pass elsif not @parent.handle_request('GET', 'notify_event', 'OnUserInput', @symbol, data).nil? # pass elsif not @parent.handle_request('GET', 'notify_event', @symbol, data).nil? # pass end @symbol = nil @parent.handle_request('NOTIFY', 'reset_user_interaction') end end class PasswordInputBox < InputBox NAME = 'passwordinputbox' ENTRY = 'PasswordInput' def new_(desc, balloon) super @entry.set_visibility(false) end end end ninix-aya-5.0.9/lib/ninix/sakura.rb0000644000175000017500000026165413416507430015341 0ustar shyshy# -*- coding: utf-8 -*- # # Copyright (C) 2001, 2002 by Tamito KAJIYAMA # Copyright (C) 2002, 2003 by MATSUMURA Namihiko # Copyright (C) 2002-2019 by Shyouzou Sugitani # Copyright (C) 2003 by Shun-ichi TAHARA # # This program is free software; you can redistribute it and/or modify it # under the terms of the GNU General Public License (version 2) as # published by the Free Software Foundation. It is distributed in the # hope that it will be useful, but WITHOUT ANY WARRANTY; without even the # implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR # PURPOSE. See the GNU General Public License for more details. # require "gtk3" begin require "gst" rescue LoadError Gst = nil end require "cgi" require "uri" require_relative "surface" require_relative "balloon" require_relative "dll" require_relative "makoto" require_relative "pix" require_relative "script" require_relative "version" require_relative "update" require_relative "home" require_relative "metamagic" require_relative "logging" module Sakura class ShellMeme < MetaMagic::Meme def initialize(key) super(key) end def create_menuitem(data) shell_name = data[0] subdir = data[1] base_path = @parent.handle_request('GET', 'get_prefix') thumbnail_path = File.join(base_path, 'shell', subdir, 'thumbnail.png') unless File.exist?(thumbnail_path) thumbnail_path = nil end return @parent.handle_request( 'GET', 'create_shell_menuitem', shell_name, @key, thumbnail_path) end def delete_by_myself() @parent.handle_request('NOTIFY', 'delete_shell', @key) end end class Sakura < MetaMagic::Holon attr_reader :key, :cantalk, :last_script include GetText bindtextdomain("ninix-aya") BALLOON_LIFE = 10 # [sec] (0: never closed automatically) SELECT_TIMEOUT = 15 # [sec] PAUSE_TIMEOUT = 30 # [sec] SILENT_TIME = 15 # [sec] # script modes BROWSE_MODE = 1 SELECT_MODE = 2 PAUSE_MODE = 3 WAIT_MODE = 4 PAUSE_NOCLEAR_MODE = 5 # script origins FROM_SSTP_CLIENT = 1 FROM_GHOST = 2 def initialize super("") # FIXME @handlers = { 'lock_repaint' => "get_lock_repaint" } @sstp_handle = nil @sstp_entry_db = nil @sstp_request_handler = nil # error = 'loose'(default) or 'strict' @script_parser = Script::Parser.new(:error => 'loose') @char = 2 # 'sakura' and 'kero' @script_queue = [] @script_mode = BROWSE_MODE @script_post_proc = [] @script_finally = [] @script_position = 0 @event_queue = [] @__current_script = '' @__balloon_life = 0 @__surface_life = 0 @__boot = [false, false] @surface_mouse_motion = nil @time_critical_session = false @lock_repaint = false @passivemode = false @__running = false @anchor = nil @clock = [0, 0] @synchronized_session = [] @force_quit = false ## @old_otherghostname = nil # create vanish dialog @__vanish_dialog = VanishDialog.new @__vanish_dialog.set_responsible(self) @cantalk = true @__sender = 'ninix-aya' @__charset = 'Shift_JIS' saori_lib = DLL::Library.new('saori', :sakura => self) @__dll = DLL::Library.new('shiori', :saori_lib => saori_lib) @__temp_mode = 0 @__observers = {} @__listening = { 'OnOtherGhostBooted' => true, 'OnOtherGhostClosed' => true, 'OnOtherGhostChanged' => true, 'OnOtherSurfaceChange' => false, 'OnOtherGhostVanished' => true, 'OnOtherGhostTalk' => false, 'OnOtherOverlap' => true, 'OnOtherOffscreen' => true } @balloon = Balloon::Balloon.new @balloon.set_responsible(self) @surface = Surface::Surface.new @surface.set_responsible(self) keep_silence(false) @updateman = Update::NetworkUpdate.new() @updateman.set_responsible(self) unless Gst.nil? @audio_player = Gst::ElementFactory.make('playbin', 'player') fakesink = Gst::ElementFactory.make('fakesink', 'fakesink') @audio_player.set_property('video-sink', fakesink) bus = @audio_player.bus bus.add_signal_watch() bus.signal_connect('message') do |bus, message| on_audio_message(bus, message) next true end else @audio_player = nil end @audio_loop = false @reload_event = nil end def get_lock_repaint(*args) @lock_repaint end def attach_observer(observer) unless @__observers.include?(observer) @__observers[observer] = 1 end end def notify_observer(event, args: nil) if args.nil? args = [] end for observer in @__observers.keys observer.observer_update(event, args) end end def detach_observer(observer) if @__observers.include?(observer) @__observers.delete(observer) end end def delete_shell(key) fail "assert" unless @shells.include?(key) @shells.delete(key) end def notify_installedshellname() installed = [] for key in @shells.keys installed << @shells[key].baseinfo[0] end notify_event('installedshellname', *installed) end def get_shell_menu() current_key = get_current_shell() for key in @shells.keys menuitem = @shells[key].menuitem menuitem.set_sensitive(key != current_key) # not working end return @shell_menu end def new_(desc, shiori_dir, use_makoto, surface_set, prefix, shiori_dll, shiori_name) @shiori = nil @desc = desc @shiori_dir = shiori_dir @use_makoto = use_makoto @prefix = prefix @shells = {} # Ordered Hash shell_menuitems = {} # Ordered Hash for key, value in surface_set meme = ShellMeme.new(key) meme.set_responsible(self) @shells[key] = meme meme.baseinfo = value shell_menuitems[key] = meme.menuitem end @shell_menu = @parent.handle_request( 'GET', 'create_shell_menu', shell_menuitems) @shiori_dll = shiori_dll @shiori_name = shiori_name name = [shiori_dll, shiori_name] @shiori = @__dll.request(name) char = 2 while not @desc.get(sprintf('char%d.seriko.defaultsurface', char)).nil? char += 1 end if char > 2 @char = char end # XXX if @desc.get('name') == 'BTH小っちゃいってことは便利だねっ' set_SSP_mode(true) else set_SSP_mode(false) end @last_script = nil @status_icon = Gtk::StatusIcon.new @status_icon.set_title(get_name(:default => '')) @status_icon.set_visible(false) end def set_SSP_mode(flag) # XXX if flag @__sender = 'SSP' else @__sender = 'ninix-aya' end end def save_history() path = File.join(get_prefix(), 'HISTORY') begin open(path, 'w') do |file| file.write("time, " + @ghost_time.to_s + "\n") file.write("vanished_count, " + @vanished_count.to_s + "\n") end rescue # IOError, SystemCallError => e Logging::Logging.error('cannot write ' + path) end end def save_settings() path = File.join(get_prefix(), 'SETTINGS') begin open(path, 'w') do |file| unless @balloon_directory.nil? file.write("balloon_directory, " + @balloon_directory + "\n") end unless @shell_directory.nil? file.write("shell_directory, " + @shell_directory + "\n") end end rescue # IOError, SystemCallError => e Logging::Logging.error('cannot write ' + path) end end def load_history() path = File.join(get_prefix(), 'HISTORY') if File.exist?(path) ghost_time = 0 ghost_vanished_count = 0 begin f = open(path, 'r') for line in f next unless line.include?(',') key, value = line.split(',', 2) key = key.strip() if key == 'time' begin ghost_time = Integer(value.strip()) rescue #pass end elsif key == 'vanished_count' begin ghost_vanished_count = Integer(value.strip()) rescue #pass end end end rescue # IOError => e Logging::Logging.error('cannot read ' + path) ghost_time = 0 vanished_count = 0 end @ghost_time = ghost_time @vanished_count = ghost_vanished_count end end def load_settings() path = File.join(get_prefix(), 'SETTINGS') if File.exist?(path) balloon_directory = nil shell_directory = nil begin f = open(path, 'r') for line in f next unless line.include?(',') key, value = line.split(',', 2) if key.strip() == 'balloon_directory' balloon_directory = value.strip() end if key.strip() == 'shell_directory' shell_directory = value.strip() end end rescue # IOError => e Logging::Logging.error('cannot read ' + path) end @balloon_directory = balloon_directory @shell_directory = shell_directory else @balloon_directory = nil @shell_directory = nil end end def load_shiori() unless @shiori.nil? or @shiori.load(:dir => @shiori_dir).zero? if @shiori.respond_to?("show_description") @shiori.show_description() end else Logging::Logging.error(get_selfname + ' cannot load SHIORI(' + @shiori_name + ')') end @__charset = 'Shift_JIS' # default get_event_response('OnInitialize', :event_type => 'NOTIFY') get_event_response('basewareversion', Version.VERSION, 'ninix-aya', Version.NUMBER, :event_type => 'NOTIFY') end def finalize() unless @script_finally.empty? # XXX for proc_obj in @script_finally proc_obj.call(:flag_break => false) end @script_finally = [] end if @__temp_mode.zero? get_event_response('OnDestroy', :event_type => 'NOTIFY') @shiori.unload() end stop() end def enter_temp_mode() @__temp_mode = 2 if @__temp_mode.zero? end def leave_temp_mode() @__temp_mode = 0 end def is_listening(key) return false unless @__listening.include?(key) return @__listening[key] end def on_audio_message(bus, message) if message.nil? # XXX: workaround for Gst Version < 0.11 if @script_mode == WAIT_MODE @script_mode = BROWSE_MODE end return end t = message.type if t == Gst::MessageType::EOS @audio_player.set_state(Gst::State::NULL) if @script_mode == WAIT_MODE fail "assert" if @audio_loop @script_mode = BROWSE_MODE end if @audio_loop @audio_player.set_state(Gst::State::PLAYING) end elsif t == Gst::MessageType::ERROR @audio_player.set_state(Gst::State::NULL) err, debug = message.parse_error() Logging::Logging.error('Error: ' + err + ', ' + debug) @audio_loop = false end end def set_surface(desc, surface_alias, surface, name, surface_dir, tooltips, seriko_descript) default_sakura = @desc.get('sakura.seriko.defaultsurface', :default => '0') default_kero = @desc.get('kero.seriko.defaultsurface', :default => '10') @surface.new_(desc, surface_alias, surface, name, surface_dir, tooltips, seriko_descript, default_sakura, default_kero) for side in 2..@char-1 default = @desc.get('char' + side.to_s + '.seriko.defaultsurface') @surface.add_window(side, default) end icon = @desc.get('icon', :default => nil) unless icon.nil? icon_path = File.join(@shiori_dir, icon) unless File.exist?(icon_path) icon_path = nil end else icon_path = nil end @surface.set_icon(icon_path) end def set_balloon(desc, balloon) @balloon.new_(desc, balloon) for side in 2..@char-1 @balloon.add_window(side) end end def update_balloon_offset(side, x_delta, y_delta) return if side >= @char ox, oy = @surface.window[side].get_balloon_offset # without scaling direction = @balloon.window[side].direction sx, sy = get_surface_position(side) if direction.zero? # left nx = (ox + x_delta) else w, h = @surface.get_surface_size(side) nx = (ox - x_delta) end ny = (oy + y_delta) @surface.set_balloon_offset(side, [nx, ny]) end def enqueue_script(event, script, sender, handle, host, show_sstp_marker, use_translator, db: nil, request_handler: nil) if @script_queue.empty? and \ not @time_critical_session and not @passivemode unless @sstp_request_handler.nil? @sstp_request_handler.send_sstp_break() @sstp_request_handler = nil end reset_script(:reset_all => true) end @script_queue << [event, script, sender, handle, host, show_sstp_marker, use_translator, db, request_handler] end RESET_ENQUEUE_EVENT = ['OnGhostChanging', 'OnShellChanging', 'OnVanishSelected'] def check_event_queue() return (not @event_queue.empty?) end def enqueue_event(event, *arglist, proc_obj: nil) if RESET_ENQUEUE_EVENT.include?(event) reset_script(:reset_all => true) end @event_queue << [event, arglist, proc_obj] end EVENT_SCRIPTS = { 'OnUpdateBegin' => \ ['\t\h\s[0]', _('Network Update has begun.'), '\e'].join(''), 'OnUpdateComplete' => \ ['\t\h\s[5]', _('Network Update completed successfully.'), '\e'].join(''), 'OnUpdateFailure' => \ ['\t\h\s[4]', _('Network Update failed.'), '\e'].join(''), } def handle_event() while not @event_queue.empty? event, arglist, proc_obj = @event_queue.shift if EVENT_SCRIPTS.include?(event) default = EVENT_SCRIPTS[event] else default = nil end if notify_event(event, *arglist, :default => default) unless proc_obj.nil? @script_post_proc << proc_obj end return true elsif not proc_obj.nil? proc_obj.call() return true end end return false end def is_running() @__running end def is_paused() return [PAUSE_MODE, PAUSE_NOCLEAR_MODE].include?(@script_mode) end def is_talking() unless @processed_script.empty? and @processed_text.empty? return true else return false end end def busy(check_updateman: true) return (@time_critical_session or \ @balloon.user_interaction or \ not @event_queue.empty? or \ @passivemode or \ not @sstp_request_handler.nil? or \ (check_updateman and @updateman.is_active())) end def get_silent_time() @silent_time end def keep_silence(quiet) if quiet @silent_time = Time.new.to_f else @silent_time = 0 reset_idle_time() end end def get_idle_time() now = Time.new.to_f idle = (now - @idle_start) return idle end def reset_idle_time() @idle_start = Time.new.to_f end def notify_preference_changed() @balloon.reset_fonts() @surface.reset_surface() notify_observer('set scale') @balloon.reset_balloon() end def get_workarea @parent.handle_request('GET', 'get_workarea') end def get_surface_position(side) result = @surface.get_position(side) unless result.nil? return result else return [0, 0] end end def set_balloon_position(side, base_x, base_y) @balloon.set_position(side, base_x, base_y) end def set_balloon_direction(side, direction) return if side >= @char @balloon.window[side].direction = direction end def get_balloon_size(side) result = @balloon.get_balloon_size(side) unless result.nil? return result else return [0, 0] end end def get_balloon_windowposition(side) @balloon.get_balloon_windowposition(side) end def get_balloon_position(side) result = @balloon.get_position(side) unless result.nil? return result else return [0, 0] end end def balloon_is_shown(side) if @balloon and @balloon.is_shown(side) return true else return false end end def surface_is_shown(side) if @surface and @surface.is_shown(side) return true else return false end end def is_URL(s) return (s.start_with?('http://') or \ s.start_with?('ftp://') or \ s.start_with?('file:/')) end def is_anchor(link_id) if link_id.length == 2 and link_id[0] == 'anchor' return true else return false end end def vanish() if busy() Gdk.beep() ## FIXME return end notify_event('OnVanishSelecting') @__vanish_dialog.show() end def vanish_by_myself(next_ghost) @vanished_count += 1 @ghost_time = 0 @parent.handle_request('NOTIFY', 'vanish_sakura', self, next_ghost) end def get_ifghost() return [get_selfname(), ',', get_keroname()].join('') end def ifghost(ifghost) names = get_ifghost() name = get_selfname() return [name, names].include?(ifghost) end def get_name(default: _('Sakura&Unyuu')) @desc.get('name', :default => default) end def get_username() username = getstring('username') if username.nil? username = @surface.get_username() end if username.nil? username = @desc.get('user.defaultname', :default => _('User')) end return username end def get_selfname(default: _('Sakura')) selfname = @surface.get_selfname() if selfname.nil? selfname = @desc.get('sakura.name', :default => default) end return selfname end def get_selfname2() selfname2 = @surface.get_selfname2() if selfname2.nil? selfname2 = @desc.get('sakura.name2', :default => _('Sakura')) end return selfname2 end def get_keroname() keroname = @surface.get_keroname() if keroname.nil? keroname = @desc.get('kero.name', :default => _('Unyuu')) end return keroname end def get_friendname() friendname = @surface.get_friendname() if friendname.nil? friendname = @desc.get('sakura.friend.name', :default => _('Tomoyo')) end return friendname end def getaistringrandom() # obsolete result = get_event_response('OnAITalk') return translate(result) end def getdms() result = get_event_response('dms') return translate(result) end def getword(word_type) result = get_event_response(word_type) return translate(result) end def getstring(name) get_event_response(name) end def translate(s) unless s.nil? if @use_makoto s = Makoto.execute(s) else r = get_event_response('OnTranslate', s, :translate => 0) unless r.empty? s = r end end end return s end def get_value(response) # FIXME: check return code result = {} to = nil for line in response.force_encoding(@__charset).split(/\r?\n/, 0) line = line.encode("UTF-8", :invalid => :replace, :undef => :replace).strip() next if line.empty? next unless line.include?(':') key, value = line.split(':', 2) key = key.strip() if key == 'Charset' charset = value.strip() if charset != @__charset unless Encoding.name_list.include?(charset) Logging::Logging.warning( 'Unsupported charset ' + charset) else @__charset = charset end end end result[key] = value end for key in result.keys result[key].strip! end if result.include?('Reference0') to = result['Reference0'] end if result.include?('Value') return result['Value'], to else return nil, to end end def get_event_response_with_communication(event, *arglist, event_type: 'GET', translate: 1) return '' if @__temp_mode == 1 ref = arglist header = [event_type.to_s, " SHIORI/3.0\r\n", "Sender: ", @__sender.to_s, "\r\n", "ID: ", event.to_s, "\r\n", "SecurityLevel: local\r\n", "Charset: ", @__charset.to_s, "\r\n"].join("") for i in 0..ref.length-1 value = ref[i] unless value.nil? value = value.to_s header = [header, "Reference", i.to_s, ": ", value, "\r\n"].join("") end end header = [header, "\r\n"].join("") header = header.encode(@__charset, :invalid => :replace, :undef => :replace) response = @shiori.request(header) if event_type != 'NOTIFY' and @cantalk result, to = get_value(response) result = translate(result) unless translate.zero? else result, to = '', nil end result = '' if result.nil? unless to.nil? or result.empty? communication = to else communication = nil end return result, communication end def get_event_response(event, *arglist, event_type: 'GET', translate: 1) result, communication = get_event_response_with_communication( event, *arglist, :event_type => event_type, :translate => translate) return result end ### CALLBACK ### def notify_start(init, vanished, ghost_changed, name, prev_name, prev_shell, path, last_script, abend: nil) unless @__temp_mode.zero? default = nil else default = Version.VERSION_INFO end if init if @ghost_time.zero? unless notify_event('OnFirstBoot', @vanished_count, nil, nil, nil, nil, nil, nil, @surface.name) unless abend.nil? notify_event('OnBoot', @surface.name, nil, nil, nil, nil, nil, 'halt', abend, :default => default) else notify_event('OnBoot', @surface.name, :default => default) end end else unless abend.nil? notify_event('OnBoot', @surface.name, nil, nil, nil, nil, nil, 'halt', abend, :default => default) else notify_event('OnBoot', @surface.name, :default => default) end end left, top, scrn_w, scrn_h = get_workarea notify_event('OnDisplayChange', Gdk::Visual.best_depth, scrn_w, scrn_h, :event_type => 'NOTIFY') elsif vanished if @ghost_time.zero? if notify_event('OnFirstBoot', @vanished_count, nil, nil, nil, nil, nil, nil, @surface.name) return end elsif notify_event('OnVanished', name) return elsif notify_event('OnGhostChanged', name, last_script, prev_name, nil, nil, nil, nil, pref_shell) return end unless abend.nil? notify_event('OnBoot', @surface.name, nil, nil, nil, nil, nil, nil, 'halt', abend, :default => default) else notify_event('OnBoot', @surface.name, :default => default) end elsif ghost_changed if @ghost_time.zero? if notify_event('OnFirstBoot', @vanished_count, nil, nil, nil, nil, nil, nil, @surface.name) return end elsif notify_event('OnGhostChanged', name, last_script, prev_name, nil, nil, nil, nil, prev_shell) return end unless abend.nil? notify_event('OnBoot', @surface.name, nil, nil, nil, nil, nil, 'halt', abend, :default => default) else notify_event('OnBoot', @surface.name, :default => default) end else #pass ## FIXME end end def notify_vanish_selected() proc_obj = lambda { @vanished_count += 1 @ghost_time = 0 GLib::Idle.add{ @parent.handle_request('NOTIFY', 'vanish_sakura', self, nil) } } enqueue_event('OnVanishSelected', :proc_obj => proc_obj) @vanished = true end def notify_vanish_canceled() notify_event('OnVanishCancel') end def notify_iconified() @cantalk = false @parent.handle_request('NOTIFY', 'select_current_sakura') unless @passivemode reset_script(:reset_all => true) stand_by(true) notify_event('OnWindowStateMinimize') end notify_observer('iconified') end def notify_deiconified() unless @cantalk @cantalk = true @parent.handle_request('NOTIFY', 'select_current_sakura') unless @passivemode notify_event('OnWindowStateRestore') end end notify_observer('deiconified') end def notify_link_selection(link_id, text, number) if @script_origin == FROM_SSTP_CLIENT and \ not @sstp_request_handler.nil? @sstp_request_handler.send_answer(text) @sstp_request_handler = nil end if is_anchor(link_id) notify_event('OnAnchorSelect', link_id[1]) elsif is_URL(link_id) browser_open(link_id) reset_script(:reset_all => true) stand_by(false) elsif not @sstp_entry_db.nil? # leave the previous sstp message as it is start_script(@sstp_entry_db.get(link_id, :default => '\e')) @sstp_entry_db = nil elsif not notify_event('OnChoiceSelect', link_id, text, number) reset_script(:reset_all => true) stand_by(false) end end def browser_open(url) if RbConfig::CONFIG['host_os'] =~ /mswin|mingw|cygwin/ system "start #{url}" elsif RbConfig::CONFIG['host_os'] =~ /darwin/ system "open #{url}" elsif RbConfig::CONFIG['host_os'] =~ /linux|bsd/ system "xdg-open #{url}" end end def notify_site_selection(args) title, url = args if is_URL(url) browser_open(url) end enqueue_event('OnRecommandedSiteChoice', title, url) end def notify_surface_click(button, click, side, x, y) if button == 1 and click == 1 raise_all() end if @vanished if side == 0 and button == 1 unless @sstp_request_handler.nil? @sstp_request_handler.send_sstp_break() @sstp_request_handler = nil end reset_script(:reset_all => true) notify_event('OnVanishButtonHold', :default => '\e') @vanished = false end return end if @updateman.is_active() if button == 1 and click == 2 @updateman.interrupt() end return end return if @time_critical_session if click == 1 return if @passivemode and not @processed_script.empty? part = @surface.get_touched_region(side, x, y) if [1, 2, 3].include?(button) num_button = [0, 2, 1][button - 1] unless notify_event('OnMouseUp', x, y, 0, side, part, num_button, 'mouse') # FIXME if button == 2 if notify_event( 'OnMouseUpEx', x, y, 0, side, part, 'middle', 'mouse') # FIXME return end if notify_event('OnMouseClickEx', x, y, 0, side, part, 'middle', 'mouse') # FIXME return end end notify_event('OnMouseClick', x, y, 0, side, part, num_button, 'mouse') # FIXME end elsif [8, 9].include?(button) ex_button = { 2 => 'middle', 8 => 'xbutton1', 9 => 'xbutton2' }[button] unless notify_event('OnMouseUpEx', x, y, 0, side, part, ex_button, 'mouse') # FIXME notify_event('OnMouseClickEx', x, y, 0, side, part, ex_button, 'mouse') # FIXME end end elsif @passivemode return elsif [1, 3].include?(button) and click == 2 if @sstp_request_handler @sstp_request_handler.send_sstp_break() @sstp_request_handler = nil end part = @surface.get_touched_region(side, x, y) num_button = [0, 2, 1][button - 1] notify_event('OnMouseDoubleClick', x, y, 0, side, part, num_button, 'mouse') # FIXME elsif [2, 8, 9].include?(button) and click == 2 part = @surface.get_touched_region(side, x, y) ex_button = { 2 => 'middle', 8 => 'xbutton1', 9 => 'xbutton2' }[button] notify_event('OnMouseDoubleClickEx', x, y, 0, side, part, ex_button, 'mouse') # FIXME end end def notify_balloon_click(button, click, side) if @script_mode == PAUSE_MODE @script_mode = BROWSE_MODE @balloon.clear_text_all() @balloon.hide_all() @script_side = 0 elsif @script_mode == PAUSE_NOCLEAR_MODE @script_mode = BROWSE_MODE elsif button == 1 and click == 1 raise_all() end if @vanished return end if @updateman.is_active() if button == 1 and click == 2 @updateman.interrupt() end return end if @time_critical_session @time_critical_session = false return elsif @passivemode return elsif button == 1 and click == 2 unless @sstp_request_handler.nil? @sstp_request_handler.send_sstp_break() @sstp_request_handler = nil reset_script(:reset_all => true) stand_by(false) end elsif button == 3 and click == 1 if @sstp_request_handler @sstp_request_handler.send_sstp_break() @sstp_request_handler = nil end if is_talking() notify_event('OnBalloonBreak', @__current_script, side, @script_position) else notify_event('OnBalloonClose', @__current_script) reset_script(:reset_all => true) stand_by(false) end end end def notify_surface_mouse_motion(side, x, y, part) return unless @surface_mouse_motion.nil? unless part.empty? @surface_mouse_motion = [side, x, y, part] else @surface_mouse_motion = nil end end def notify_user_teach(word) unless word.nil? script = translate(get_event_response('OnTeach', word)) unless script.empty? start_script(script) @balloon.hide_sstp_message() end end end MONTH_NAMES = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'] BOOT_EVENT = ['OnBoot', 'OnFirstBoot', 'OnGhostChanged', 'OnShellChanged', 'OnUpdateComplete'] RESET_NOTIFY_EVENT = ['OnVanishSelecting', 'OnVanishCancel'] def notify_event(event, *arglist, event_type: 'GET', default: nil) return false if @time_critical_session and event.start_with?('OnMouse') if RESET_NOTIFY_EVENT.include?(event) reset_script(:reset_all => true) end result = get_event_response_with_communication(event, *arglist, :event_type => event_type) unless result.nil? script, communication = result else script, communication = [default, nil] end if not script.empty? or (script.empty? and event != 'OnSecondChange') t = Time.new.localtime m = MONTH_NAMES[t.month - 1] Logging::Logging.debug( sprintf("\n[%02d/%s/%d:%02d:%02d:%02d %+05d]", t.day, m, t.year, t.hour, t.min, t.sec, t.utc_offset / 36)) Logging::Logging.debug('Event: ' + event) for n in 0..arglist.length-1 value = arglist[n] unless value.nil? value = value.to_s Logging::Logging.debug( 'Reference' + n.to_s + ': ' + value) end end end if event == 'OnCloseAll' @force_quit = true if script.empty? # fallback result = get_event_response_with_communication( 'OnClose', *arglist, :event_type => event_type) unless result.nil? script, communication = result else script, communication = [default, nil] end end unless script.empty? start_script(script) @balloon.hide_sstp_message() end return true end if event == 'OnClose' and arglist[0] == 'shutdown' # XXX @force_quit = true end if script.empty? # an empty script is ignored if BOOT_EVENT.include?(event) surface_bootup() end if event == 'OnMouseClick' and arglist[5] == 1 @parent.handle_request( 'NOTIFY', 'open_popup_menu', self, arglist[3]) end @parent.handle_request( 'NOTIFY', 'notify_other', @key, event, get_name(:default => ''), get_selfname(:default => ''), get_current_shell_name(), false, communication, nil, false, script, arglist) return false end Logging::Logging.debug('=> "' + script + '"') if @__temp_mode == 2 @parent.handle_request('NOTIFY', 'reset_sstp_flag') leave_temp_mode() end if @passivemode and \ (event == 'OnSecondChange' or event == 'OnMinuteChange') return false end start_script(script) @balloon.hide_sstp_message() if BOOT_EVENT.include?(event) @script_finally << lambda {|flag_break: false| @surface_bootup } end proc_obj = lambda {|flag_break: false| @parent.handle_request( 'NOTIFY', 'notify_other', @key, event, get_name(:default => ''), get_selfname(:default => ''), get_current_shell_name(), flag_break, communication, nil, false, script, arglist) } @script_finally << proc_obj return true end def get_prefix() @prefix end def stick_window(flag) @surface.window_stick(flag) end def toggle_bind(args) @surface.toggle_bind(args) end def get_menu_pixmap() path_background, path_sidebar, path_foreground, \ align_background, align_sidebar, align_foreground = \ @surface.get_menu_pixmap() top_dir = @surface.prefix ghost_dir = File.join(get_prefix(), 'ghost', 'master') name = getstring('menu.background.bitmap.filename') unless name.empty? name = name.gsub("\\", '/') path_background = File.join(top_dir, name) end if path_background.nil? path_background = File.join(ghost_dir, 'menu_background.png') end unless File.exist?(path_background) path_background = nil end name = getstring('menu.sidebar.bitmap.filename') unless name.empty? name = name.gsub("\\", '/') path_sidebar = File.join(top_dir, name) end if path_sidebar.nil? path_sidebar = File.join(ghost_dir, 'menu_sidebar.png') end unless File.exist?(path_sidebar) path_sidebar = nil end name = getstring('menu.foreground.bitmap.filename') unless name.empty? name = name.gsub("\\", '/') path_foreground = File.join(top_dir, name) end if path_foreground.nil? path_foreground = File.join(ghost_dir, 'menu_foreground.png') end unless File.exist?(path_foreground) path_foreground = nil end align = getstring('menu.background.alignment') unless align.empty? align_background = align end unless ['lefttop', 'righttop', 'centertop'].include?(align_background) align_background = 'lefttop' end align_background = align_background[0..-4].encode('ascii', :invalid => :replace, :undef => :replace) # XXX align = getstring('menu.sidebar.alignment') unless align.empty? align_sidebar = align end unless ['top', 'bottom'].include?(align_sidebar) align_sidebar = 'bottom' end align_sidebar = align_sidebar.encode('ascii', :invalid => :replace, :undef => :replace) # XXX align = getstring('menu.foreground.alignment') unless align.empty? align_foreground = align end unless ['lefttop', 'righttop', 'centertop'].include?(align_foreground) align_foreground = 'lefttop' end align_foreground = align_foreground[0..-4].encode('ascii', :invalid => :replace, :undef => :replace) # XXX return path_background, path_sidebar, path_foreground, \ align_background, align_sidebar, align_foreground end def get_menu_fontcolor() background, foreground = @surface.get_menu_fontcolor() color_r = getstring('menu.background.font.color.r') color_g = getstring('menu.background.font.color.g') color_b = getstring('menu.background.font.color.b') begin color_r = [0, [255, Integer(color_r)].min].max color_g = [0, [255, Integer(color_g)].min].max color_b = [0, [255, Integer(color_b)].min].max rescue #pass else background = [color_r, color_g, color_b] end color_r = getstring('menu.foreground.font.color.r') color_g = getstring('menu.foreground.font.color.g') color_b = getstring('menu.foreground.font.color.b') begin color_r = [0, [255, Integer(color_r)].min].max color_g = [0, [255, Integer(color_g)].min].max color_b = [0, [255, Integer(color_b)].min].max rescue #pass else foreground = [color_r, color_g, color_b] end return background, foreground end def get_mayuna_menu() @surface.get_mayuna_menu() end def get_current_balloon_directory() @balloon.get_balloon_directory() end def get_current_shell() @shell_directory end def get_current_shell_name() @shells[get_current_shell()].baseinfo[0] end def get_default_shell() default = @shell_directory or 'master' unless @shells.include?(default) default = @shells.keys()[0] # XXX end return default end def get_balloon_default_id() @desc.get('balloon.defaultsurface', :default => '0') end def select_shell(shell_key) fail "assert" unless not @shells.nil? and @shells.include?(shell_key) @shell_directory = shell_key # save user's choice surface_name, surface_dir, surface_desc, surface_alias, surface, surface_tooltips, seriko_descript = \ @shells[shell_key].baseinfo proc_obj = lambda { Logging::Logging.info('ghost ' + @key + ' ' + shell_key) set_surface(surface_desc, surface_alias, surface, surface_name, surface_dir, surface_tooltips, seriko_descript) @surface.reset_alignment() @surface.reset_position() notify_event('OnShellChanged', surface_name, surface_name, surface_dir) } enqueue_event('OnShellChanging', surface_name, surface_dir, :proc_obj => proc_obj) end def select_balloon(item, desc, balloon) @balloon_directory = item # save user's choice if item == get_current_balloon_directory() # no change return # need reloadning? end fail "assert" unless item == balloon['balloon_dir'][0] path = File.join(Home.get_ninix_home(), 'balloon', item) @balloon.hide_all() set_balloon(desc, balloon) @balloon.set_balloon_default() position_balloons() name = desc.get('name', :default => '') Logging::Logging.info('balloon ' + name + ' ' + path) notify_event('OnBalloonChange', name, path) end def surface_bootup(flag_break: false) for side in [0, 1] unless @__boot[side] set_surface_default(:side => side) @surface.show(side) end end end def get_uptime() uptime = ((Time.new.to_f - @start_time).to_i / 3600).to_i if uptime < 0 @start_time = Time.new.to_f return 0 end return uptime end def hide_all() @surface.hide_all() @balloon.hide_all() end def position_balloons() @surface.reset_balloon_position() end def align_top(side) @surface.set_alignment(side, 1) end def align_bottom(side) @surface.set_alignment(side, 0) end def align_current() @surface.set_alignment_current() end def identify_window(win) return (@surface.identify_window(win) or @balloon.identify_window(win)) end def set_surface_default(side: nil) @surface.set_surface_default(side) end def get_surface_scale() @parent.handle_request('GET', 'get_preference', 'surface_scale') end def get_surface_size(side) result = @surface.get_surface_size(side) unless result.nil? return result else return [0, 0] end end def set_surface_position(side, x, y) @surface.set_position(side, x, y) end def set_surface_id(side, id) @surface.set_surface(side, id) end def get_surface_id(side) @surface.get_surface(side) end def surface_is_shown(side) return (@surface and @surface.is_shown(side)) end def get_target_window @surface.get_window(0) end def get_kinoko_position(baseposition) side = 0 x, y = get_surface_position(side) w, h = get_surface_size(side) if baseposition == 1 rect = @surface.get_collision_area(side, 'face') unless rect.nil? x1, y1, x2, y2 = rect return (x + ((x2 - x1) / 2).to_i), (y + ((y2 - y1) / 2).to_i) else return (x + (w / 2).to_i), (y + (h / 4).to_i) end elsif baseposition == 2 rect = @surface.get_collision_area(side, 'bust') unless rect.nil? x1, y1, x2, y2 = rect return (x + ((x2 - x1) / 2).to_i), (y + ((y2 - y1) / 2).to_i) else return (x + (w / 2).to_i), (y + (h / 2).to_i) end elsif baseposition == 3 centerx, centery = @surface.get_center(side) if centerx.nil? centerx = (w / 2).to_i end if centery.nil? centery = (h / 2).to_i end return x + centerx, y + centery else # baseposition == 0 or baseposition not in [1, 2, 3]: # AKF centerx, centery = @surface.get_kinoko_center(side) if centerx.nil? or centery.nil? rect = @surface.get_collision_area(side, 'head') unless rect.nil? x1, y1, x2, y2 = rect return (x + ((x2 - x1) / 2).to_i), (y + ((y2 - y1) / 2).to_i) else return (x + (w / 2).to_i), (y + (h / 8).to_i) end end return (x + centerx), (y + centery) end end def raise_surface(side) @surface.raise_(side) end def lower_surface(side) @surface.lower(side) end def raise_all() @surface.raise_all() @balloon.raise_all() end def lower_all() @surface.lower_all() @balloon.lower_all() end ### STARTER ### def stand_by(reset_surface) @balloon.hide_all() @balloon.hide_sstp_message() default_sakura = @desc.get('sakura.seriko.defaultsurface', :default => '0') default_kero = @desc.get('kero.seriko.defaultsurface', :default => '10') if reset_surface set_surface_default() @balloon.set_balloon_default() elsif get_surface_id(0) != default_sakura or \ get_surface_id(1) != default_kero @__surface_life = Array(20..30).sample ##Logging::Logging.debug('surface_life = ' + @__surface_life) end end def start(key, init, temp, vanished, ghost_changed, prev_self_name, prev_name, prev_shell, last_script, abend) if is_running() unless temp.zero? enter_temp_mode() else if @__temp_mode == 1 @__temp_mode = 2 load_shiori() notify_start( init, vanished, ghost_changed, prev_self_name, prev_name, prev_shell, '', last_script, :abend => abend) end end return end @ghost_time = 0 @vanished_count = 0 @__running = true @__temp_mode = temp @key = key @force_quit = false Logging::Logging.info('ghost ' + key) load_settings() shell_key = get_default_shell() @shell_directory = shell_key # XXX fail "assert" unless not @shells.nil? and @shells.include?(shell_key) surface_name, surface_dir, surface_desc, surface_alias, surface, surface_tooltips, seriko_descript = \ @shells[shell_key].baseinfo if ghost_changed name = prev_self_name else name = surface_name end set_surface(surface_desc, surface_alias, surface, surface_name, surface_dir, surface_tooltips, seriko_descript) balloon = nil if @parent.handle_request('GET', 'get_preference', 'ignore_default').zero? ## FIXME: change prefs key balloon_path = @desc.get('deault.balloon.path', :default => '') balloon_name = @desc.get('balloon', :default => '') unless balloon_path.empty? balloon = @parent.handle_request( 'GET', 'find_balloon_by_subdir', balloon_path) end if balloon.nil? and not balloon_name.empty? balloon = @parent.handle_request( 'GET', 'find_balloon_by_name', balloon_name) end end if balloon.nil? unless @balloon_directory.nil? balloon = @balloon_directory else balloon = @parent.handle_request( 'GET', 'get_preference', 'default_balloon') end end desc, balloon = @parent.handle_request( 'GET', 'get_balloon_description', balloon) set_balloon(desc, balloon) if temp.zero? load_shiori() end restart() @start_time = Time.new.to_f notify_start( init, vanished, ghost_changed, name, prev_name, prev_shell, surface_dir, last_script, :abend => abend) GLib::Timeout.add(10) { do_idle_tasks } # 10[ms] end def restart() load_history() @vanished = false @__boot = [false, false] @old_otherghostname = nil reset_script(:reset_all => true) @surface.reset_alignment() stand_by(true) @surface.reset_position() reset_idle_time() @__running = true @force_quit = false end def stop() return unless @__running notify_observer('finalize') @__running = false save_settings() save_history() @parent.handle_request('NOTIFY', 'rebuild_ghostdb', self, :name => nil) hide_all() @surface.finalize() @balloon.finalize() unless @audio_player.nil? @audio_player.set_state(Gst::State::NULL) end @audio_loop = false end def process_script() now = Time.new idle = get_idle_time() second, minute = now.localtime.to_a[0, 2] if @clock[0] != second @ghost_time += 1 if @__temp_mode.zero? @parent.handle_request( 'NOTIFY', 'rebuild_ghostdb', self, :name => get_selfname(), :s0 => get_surface_id(0), :s1 => get_surface_id(1)) otherghostname = @parent.handle_request( 'GET', 'get_otherghostname', get_selfname()) if otherghostname != @old_otherghostname notify_event('otherghostname', otherghostname, :event_type => 'NOTIFY') end @old_otherghostname = otherghostname end if not @__running #pass elsif [PAUSE_MODE, PAUSE_NOCLEAR_MODE].include?(@script_mode) ##if idle > PAUSE_TIMEOUT: ## @script_mode = BROWSE_MODE #pass elsif @script_mode == WAIT_MODE #pass elsif not @processed_script.empty? or not @processed_text.empty? interpret_script() elsif not @script_post_proc.empty? for proc_obj in @script_post_proc proc_obj.call() end @script_post_proc = [] elsif not @script_finally.empty? for proc_obj in @script_finally proc_obj.call() end @script_finally = [] elsif @script_mode == SELECT_MODE if @passivemode #pass elsif idle > SELECT_TIMEOUT @script_mode = BROWSE_MODE unless @sstp_request_handler.nil? @sstp_request_handler.send_timeout() @sstp_request_handler = nil end unless notify_event('OnChoiceTimeout') stand_by(false) end end elsif not @sstp_handle.nil? close_sstp_handle() elsif @balloon.user_interaction #pass elsif idle > @__balloon_life and @__balloon_life > 0 and not @passivemode @__balloon_life = 0 for side in 0..@char-1 if balloon_is_shown(side) notify_event('OnBalloonTimeout', @__current_script) break end end stand_by(false) unless @parent.handle_request('GET', 'get_preference', 'sink_after_talk').zero? @surface.lower_all() end elsif not @event_queue.empty? and handle_event() #pass elsif not @script_queue.empty? and not @passivemode if get_silent_time() > 0 keep_silence(true) # extend silent time end event, script, sender, @sstp_handle, \ host, show_sstp_marker, use_translator, \ @sstp_entry_db, @sstp_request_handler = \ @script_queue.shift if @cantalk if show_sstp_marker @balloon.show_sstp_message(sender, host) else @balloon.hide_sstp_message() end # XXX: how about the use_translator flag? start_script(script, :origin => FROM_SSTP_CLIENT) proc_obj = lambda {|flag_break: false| @parent.handle_request( 'NOTIFY', 'notify_other', @key, event, get_name(:default => ''), get_selfname(:default => ''), get_current_shell_name(), flag_break, nil, [sender, host], (not use_translator), script, []) } @script_finally << proc_obj end elsif get_silent_time() > 0 if now.to_f - get_silent_time() > SILENT_TIME keep_silence(false) end elsif @clock[0] != second and \ notify_event('OnSecondChange', get_uptime(), @surface.get_mikire(), @surface.get_kasanari(), (not @passivemode and @cantalk)) #pass elsif @clock[1] != minute and \ notify_event('OnMinuteChange', get_uptime(), @surface.get_mikire(), @surface.get_kasanari(), (not @passivemode and @cantalk)) #pass elsif not @surface_mouse_motion.nil? side, x, y, part = @surface_mouse_motion notify_event('OnMouseMove', x, y, '', side, part) @surface_mouse_motion = nil elsif idle > @__surface_life and @__surface_life > 0 and not @passivemode @__surface_life = 0 notify_event('OnSurfaceRestore', get_surface_id(0), get_surface_id(1)) end @clock = [second, minute] end def do_idle_tasks() return false unless @__running if @force_quit and not busy() and \ not (not @processed_script.empty? or not @processed_text.empty?) quit() end @parent.handle_request('NOTIFY', 'update_working', get_name()) unless @__temp_mode.zero? process_script() if not busy() and \ @script_queue.empty? and \ not (not @processed_script.empty? or \ not @processed_text.empty?) if @__temp_mode == 1 sleep(1.4) finalize() @parent.handle_request('NOTIFY', 'close_ghost', self) @parent.handle_request('NOTIFY', 'reset_sstp_flag') return false else @parent.handle_request('NOTIFY', 'reset_sstp_flag') leave_temp_mode() return true end else return true end end if not @reload_event.nil? and not busy() and \ not (not @processed_script.empty? or not @processed_text.empty?) hide_all() Logging::Logging.info('reloading....') @shiori.unload() @updateman.clean_up() # Don't call before unloading SHIORI @parent.handle_request( 'NOTIFY', 'stop_sakura', self, lambda {|a| @parent.handle_request('NOTIFY', 'reload_current_sakura', a) }, [self]) load_settings() restart() Logging::Logging.info('done.') enqueue_event(*@reload_event) @reload_event = nil end # continue network update (enqueue events) if @updateman.is_active() @updateman.run() while true event = @updateman.get_event() break if event.nil? if event[0] == 'OnUpdateComplete' and event[1] == 'changed' @reload_event = event else enqueue_event(*event) end end end process_script() return true end def quit() @parent.handle_request('NOTIFY', 'stop_sakura', self) end ### SCRIPT PLAYER ### def start_script(script, origin: nil) return if script.empty? @last_script = script if origin.nil? @script_origin = FROM_GHOST else @script_origin = origin end reset_script(:reset_all => true) @__current_script = script unless script.rstrip().end_with?('\e') script = [script, '\e'].join('') end @processed_script = [] @script_position = 0 while true begin @processed_script.concat(@script_parser.parse(script)) break rescue Script::ParserError => e Logging::Logging.error('-' * 50) Logging::Logging.error(e.format) # 'UTF-8' done, script = e.get_item @processed_script.concat(done) end end @script_mode = BROWSE_MODE @script_wait = nil @script_side = 0 @time_critical_session = false @quick_session = false set_synchronized_session(:list => [], :reset => true) @balloon.hide_all() return if @processed_script.empty? node = @processed_script[0] if node[0] == Script::SCRIPT_TAG and node[1] == '\C' @processed_script.shift @script_position = node[-1] else @balloon.clear_text_all() end @balloon.set_balloon_default() @current_time = Time.new.to_a reset_idle_time() unless @parent.handle_request('GET', 'get_preference', 'raise_before_talk').zero? raise_all() end end def __yen_e(args) surface_id = get_surface_id(@script_side) @surface.invoke_yen_e(@script_side, surface_id) reset_script() @__balloon_life = BALLOON_LIFE end def __yen_0(args) ##@balloon.show(0) @script_side = 0 end def __yen_1(args) ##@balloon.show(1) @script_side = 1 end def __yen_p(args) begin chr_id = Integer(args[0]) rescue ArgumentError return end if chr_id >= 0 @script_side = chr_id end end def __yen_4(args) case @script_side when 0 sw, sh = get_surface_size(1) sx, sy = get_surface_position(1) when 1 sw, sh = get_surface_size(0) sx, sy = get_surface_position(0) else return end w, h = get_surface_size(@script_side) x, y = get_surface_position(@script_side) left, top, scrn_w, scrn_h = get_workarea if (sx + (sw / 2).to_i) > (left + (scrn_w / 2).to_i) new_x = [x - (scrn_w / 20).to_i, sx - (scrn_w / 20).to_i].min else new_x = [x + (scrn_w / 20).to_i, sx + (scrn_w / 20).to_i].max end if x > new_x step = -10 else step = 10 end for current_x in x.step(new_x-1, step) set_surface_position(@script_side, current_x, y) end set_surface_position(@script_side, new_x, y) end def __yen_5(args) case @script_side when 0 sw, sh = get_surface_size(1) sx, sy = get_surface_position(1) when 1 sw, sh = get_surface_size(0) sx, sy = get_surface_position(0) else return end w, h = get_surface_size(@script_side) x, y = get_surface_position(@script_side) left, top, scrn_w, scrn_h = get_workarea if (x < (sx + (sw / 2).to_i) and (sx + (sw / 2).to_i) < (x + w)) or (sx < (x + (w / 2).to_i) and (x + (w / 2).to_i) < (sx + sw)) return end if (sx + (sw / 2).to_i) > (x + (w / 2).to_i) new_x = (sx - (w / 2).to_i + 1) else new_x = (sx + sw - (w / 2).to_i - 1) end new_x = [new_x, left].max new_x = [new_x, left + scrn_w - w].min if x > new_x step = -10 else step = 10 end for current_x in x.step(new_x-1, step) set_surface_position(@script_side, current_x, y) end set_surface_position(@script_side, new_x, y) end def __yen_s(args) surface_id = args[0] if surface_id == '-1' @surface.hide(@script_side) else set_surface_id(@script_side, surface_id) @surface.show(@script_side) end if [0, 1].include?(@script_side) and not @__boot[@script_side] @__boot[@script_side] = true end end def __yen_b(args) if args[0] == '-1' @balloon.hide(@script_side) else begin balloon_id = (Integer(args[0]) / 2).to_i rescue ArgumentError balloon_id = 0 else @balloon.set_balloon(@script_side, balloon_id) end end end def __yen__b(args) begin filename, x, y = expand_meta(args[0]).split(',', 3) rescue ArgumentError ## FIXME: no exception in Ruby filename, param = expand_meta(args[0]).split(',', 2) raise "assert" unless param == 'inline' x, y = 0, 0 ## FIXME end filename = Home.get_normalized_path(filename) path = File.join(get_prefix(), 'ghost/master', filename) if File.file?(path) @balloon.append_image(@script_side, path, x, y) else path = [path, '.png'].join('') if File.file?(path) @balloon.append_image(@script_side, path, x, y) end end end def __yen_n(args) if not args.empty? and expand_meta(args[0]) == 'half' @balloon.append_text(@script_side, '\n[half]') else @balloon.append_text(@script_side, '\n') end end def __yen_c(args) @balloon.clear_text(@script_side) end def __set_weight(value, unit) begin amount = (Integer(value) * unit - 0.01) rescue ArgumentError amount = 0 end if amount > 0 @script_wait = (Time.new.to_f + amount) end end def __yen_w(args) script_speed = @parent.handle_request( 'GET', 'get_preference', 'script_speed') if not @quick_session and script_speed >= 0 __set_weight(args[0], 0.05) # 50[ms] end end def __yen__w(args) script_speed = @parent.handle_request( 'GET', 'get_preference', 'script_speed') if not @quick_session and script_speed >= 0 __set_weight(args[0], 0.001) # 1[ms] end end def __yen_t(args) @time_critical_session = (not @time_critical_session) end def __yen__q(args) @quick_session = (not @quick_session) end def __yen__s(args) list = [] for arg in args list << arg.to_i end set_synchronized_session(:list => list) end def __yen__e(args) @balloon.hide(@script_side) @balloon.clear_text(@script_side) end def __yen_q(args) newline_required = false if args.length == 3 # traditional syntax num, link_id, text = args newline_required = true else # new syntax text, link_id = args end text = expand_meta(text) @balloon.append_link(@script_side, link_id, text, :newline_required => newline_required) @script_mode = SELECT_MODE end def __yen_URL(args) text = expand_meta(args[0]) if args.length == 1 link = text else link = '#cancel' end @balloon.append_link(@script_side, link, text) for i in 1.step(args.length-1, 2) link = expand_meta(args[i]) text = expand_meta(args[i + 1]) @balloon.append_link(@script_side, link, text) end @script_mode = SELECT_MODE end def __yen__a(args) unless @anchor.nil? anchor_id = @anchor[0] text = @anchor[1] @balloon.append_link_out(@script_side, anchor_id, text) @anchor = nil else anchor_id = args[0] @anchor = [['anchor', anchor_id], ''] @balloon.append_link_in(@script_side, @anchor[0]) end end def __yen_x(args) if @script_mode == BROWSE_MODE if args.length > 0 and expand_meta(args[0]) == 'noclear' @script_mode = PAUSE_NOCLEAR_MODE else @script_mode = PAUSE_MODE end end end def __yen_a(args) start_script(getaistringrandom()) end def __yen_i(args) begin actor_id = Integer(args[0]) rescue ArgumentError #pass else @surface.invoke(@script_side, actor_id) end end def __yen_j(args) jump_id = args[0] if is_URL(jump_id) browser_open(jump_id) elsif not @sstp_entry_db.nil? start_script(@sstp_entry_db.get(jump_id, :default => '\e')) end end def __yen_minus(args) quit() end def __yen_plus(args) @parent.handle_request('NOTIFY', 'select_ghost', self, true) end def __yen__plus(args) @parent.handle_request('NOTIFY', 'select_ghost', self, false) end def __yen_m(args) write_sstp_handle(expand_meta(args[0])) end def __yen_and(args) begin text = CGI.unescape_html("&" + args[0].to_s + ";") rescue ArgumentError text = nil end if text.nil? text = '?' end @balloon.append_text(@script_side, text) end def __yen__m(args) begin num = Integer(args[0], 16) rescue ArgumentError num = 0 end if 0x20 <= num and num <= 0x7e text = num.chr else text = '?' end @balloon.append_text(@script_side, text) end def __yen__u(args) re__u = Regexp.new('\A(0x[a-fA-F0-9]{4}|[0-9]{4})\z') unless re__u.match(args[0]).nil? temp = Integer(re__u.match(args[0])[0]) temp1 = ((temp & 0xFF00) >> 8) temp2 = (temp & 0x00FF) text = [temp2, temp1].pack("C*").force_encoding("UTF-16LE").encode("UTF-8", :invalid => :replace, :undef => :replace) @balloon.append_text(@script_side, text) else @balloon.append_text(@script_side, '?') end end def __yen__v(args) return if @audio_player.nil? filename = expand_meta(args[0]) filename = Home.get_normalized_path(filename) path = File.join(get_prefix(), 'ghost/master', filename) if File.file?(path) @audio_player.set_state(Gst::State::NULL) @audio_player.set_property( 'uri', 'file://' + URI.escape(path)) @audio_loop = false @audio_player.set_state(Gst::State::PLAYING) end end def __yen_8(args) return if @audio_player.nil? filename = expand_meta(args[0]) filename = Home.get_normalized_path(filename) basename = File.basename(filename) ext = File.extname(filename) ext = ext.lower() return if ext != '.wav' path = File.join(get_prefix(), 'ghost/master', filename) if File.file?(path) @audio_player.set_state(Gst::State::NULL) @audio_player.set_property( 'uri', 'file://' + URI.escape(path)) @audio_loop = false @audio_player.set_state(Gst::State::PLAYING) end end def __yen__V(args) return if @audio_loop # nothing to do if @audio_player.get_state(timeout=Gst::SECOND)[1] == Gst::State::PLAYING @script_mode = WAIT_MODE end end def __yen_exclamation(args) return if args.empty? argc = args.length args = args.map {|s| expand_meta(s)} if args[0] == 'raise' and argc >= 2 notify_event(*args[1..9]) elsif args[0, 2] == ['open', 'readme'] ReadmeDialog.new.show(get_name(), get_prefix()) elsif args[0, 2] == ['open', 'browser'] and argc > 2 browser_open(args[2]) elsif args[0, 2] == ['open', 'communicatebox'] @balloon.open_communicatebox() elsif args[0, 2] == ['open', 'teachbox'] @balloon.open_teachbox() elsif args[0, 2] == ['open', 'inputbox'] and argc > 2 if argc > 4 @balloon.open_inputbox(args[2], :limittime => args[3], :default => args[4]) elsif argc == 4 @balloon.open_inputbox(args[2], :limittime => args[3]) else @balloon.open_inputbox(args[2]) end elsif args[0, 2] == ['open', 'passwordinputbox'] and argc > 2 if argc > 4 @balloon.open_passwordinputbox(args[2], :limittime => args[3], :default => args[4]) elsif argc == 4 @balloon.open_passwordinputbox(args[2], :limittime => args[3]) else @balloon.open_passwordinputbox(args[2]) end elsif args[0, 2] == ['open', 'configurationdialog'] @parent.handle_request('NOTIFY', 'edit_preferences') elsif args[0, 2] == ['close', 'inputbox'] and argc > 2 @balloon.close_inputbox(args[2]) elsif args[0, 2] == ['change', 'balloon'] and argc > 2 key = @parent.handle_request('GET', 'find_balloon_by_name', args[2]) unless key.nil? desc, balloon = @parent.handle_request( 'GET', 'get_balloon_description', key) select_balloon(key, desc, balloon) end elsif args[0, 2] == ['change', 'shell'] and argc > 2 for key in @shells.keys shell_name = @shells[key].baseinfo[0] if shell_name == args[2] select_shell(key) break end end elsif args[0, 2] == ['change', 'ghost'] and argc > 2 if args[2] == 'random' @parent.handle_request('NOTIFY', 'select_ghost', self, false, :event => 0) else @parent.handle_request( 'NOTIFY', 'select_ghost_by_name', self, args[2], :event => 0) end elsif args[0, 2] == ['call', 'ghost'] and argc > 2 ## FIXME: 'random', 'lastinstalled'対応 key = @parent.handle_request('GET', 'find_ghost_by_name', args[2]) unless key.nil? @parent.handle_request('NOTIFY', 'start_sakura_cb', key, :caller => self) end elsif args[0, 1] == ['updatebymyself'] unless busy(:check_updateman => false) __update() end elsif args[0, 1] == ['vanishbymyself'] @vanished = true if argc > 1 next_ghost = args[1] else next_ghost = nil end vanish_by_myself(next_ghost) elsif args[1, 1] == ['repaint'] if args[0, 1] == ['lock'] @lock_repaint = true elsif args[0, 1] == ['unlock'] @lock_repaint = false end elsif args[1, 1] == ['passivemode'] if args[0, 1] == ['enter'] @passivemode = true elsif args[0, 1] == ['leave'] @passivemode = false end elsif args[1, 1] == ['collisionmode'] if args[0, 1] == ['enter'] if args[2, 1] == ['rect'] @parent.handle_request( 'NOTIFY', 'set_collisionmode', true, :rect => true) else @parent.handle_request( 'NOTIFY', 'set_collisionmode', true) end elsif args[0, 1] == ['leave'] @parent.handle_request( 'NOTIFY', 'set_collisionmode', false) end elsif args[0, 2] == ['set', 'alignmentondesktop'] and argc > 2 case args[2] when 'bottom' unless @synchronized_session.empty? for chr_id in @synchronized_session align_bottom(chr_id) end else align_bottom(@script_side) end when 'top' unless @synchronized_session.empty? for chr_id in @synchronized_session align_top(chr_id) end else align_top(@script_side) end when 'free' unless @synchronized_session.empty? for chr_id in @synchronized_session @surface.set_alignment(chr_id, 2) end else @surface.set_alignment(@script_side, 2) end when 'default' @surface.reset_alignment() end elsif args[0, 2] == ['set', 'autoscroll'] and argc > 2 case args[2] when 'disable' @balloon.set_autoscroll(false) when 'enable' @balloon.set_autoscroll(true) else #pass ## FIXME end elsif args[0, 2] == ['set', 'windowstate'] and argc > 2 case args[2] when 'minimize' @surface.window_iconify(true) ##elsif args[2] == '!minimize': ## @surface.window_iconify(false) when 'stayontop' @surface.window_stayontop(true) when '!stayontop' @surface.window_stayontop(false) end elsif args[0, 2] == ['set', 'trayicon'] and argc > 2 ## FIXME: tasktrayicon path = File.join(get_prefix(), args[2]) if File.exist?(path) @status_icon.set_from_file(path) # XXX end if argc > 3 text = args[3] unless text.empty? @status_icon.set_has_tooltip(true) @status_icon.set_tooltip_text(text) else @status_icon.set_has_tooltip(false) end else @status_icon.set_has_tooltip(false) end @status_icon.set_visible(true) elsif args[0, 2] == ['set', 'wallpaper'] and argc > 2 path = File.join(get_prefix(), args[2]) opt = nil if argc > 3 # possible picture_options value: # "none", "wallpaper", "centered", "scaled", "stretched" options = { 'center' => 'centered', 'tile' => 'wallpaper', 'stretch' => 'stretched' } unless options.include?(args[3]) opt = nil else opt = options[args[3]] end end if opt.nil? opt = 'centered' # default end if File.exist?(path) if RbConfig::CONFIG['host_os'] =~ /linux/ # 'posix' # for GNOME3 gsettings = Gio::Settings.new( 'org.gnome.desktop.background') gsettings.set_string('picture-uri', ['file://', path].join('')) gsettings.set_string('picture-options', opt) else #pass # not implemented yet end end elsif args[0, 2] == ['set', 'otherghosttalk'] and argc > 2 if args[2] == 'true' @__listening['OnOtherGhostTalk'] = true elsif args[2] == 'false' @__listening['OnOtherGhostTalk'] = false else #pass ## FIXME end elsif args[0, 2] == ['set', 'othersurfacechange'] and argc > 2 if args[2] == 'true' @__listening['OnOtherSurfaceChange'] = true elsif args[2] == 'false' @__listening['OnOtherSurfaceChange'] = false else #pass ## FIXME end elsif args[0, 2] == ['set', 'balloonoffset'] and argc > 3 begin x = Integer(args[2]) y = Integer(args[3]) rescue ArgumentError #pass else @surface.set_balloon_offset(@script_side, [x, y]) end elsif args[0] == 'sound' and argc > 1 command = args[1] return if @audio_player.nil? if command == 'stop' @audio_player.set_state(Gst::State::NULL) @audio_loop = false elsif command == 'play' and argc > 2 filename = args[2] filename = Home.get_normalized_path(filename) path = File.join(get_prefix(), 'ghost/master', filename) if File.file?(path) @audio_player.set_state(Gst::State::NULL) @audio_player.set_property( 'uri', 'file://' + URI.escape(path)) @audio_loop = false @audio_player.set_state(Gst::State::PLAYING) end elsif command == 'cdplay' and argc > 2 @audio_player.set_state(Gst::State::NULL) begin track = Integer(args[2]) rescue ArgumentError return end @audio_player.set_property( 'uri', 'cdda://' + track.to_s) @audio_loop = false @audio_player.set_state(Gst::State::PLAYING) elsif command == 'loop' and argc > 2 filename = args[2] filename = Home.get_normalized_path(filename) path = File.join(get_prefix(), 'ghost/master', filename) if File.file?(path) @audio_player.set_state(Gst::State::NULL) @audio_player.set_property( 'uri', 'file://' + URI.escape(path)) @audio_loop = true @audio_player.set_state(Gst::State::PLAYING) end elsif command == 'wait' return if @audio_loop # nothing to do if @audio_player.get_state(timeout=Gst::SECOND)[1] == Gst::State::PLAYING @script_mode = WAIT_MODE end elsif command == 'pause' if @audio_player.get_state(timeout=Gst::SECOND)[1] == Gst::State::PLAYING @audio_player.set_state(Gst::State::PAUSED) end elsif command == 'resume' if @audio_player.get_state(timeout=Gst::SECOND)[1] == Gst::State::PAUSED @audio_player.set_state(Gst::State::PLAYING) end else #pass ## FIXME end elsif args[0] == '*' @balloon.append_sstp_marker(@script_side) elsif args[0] == 'quicksession' and argc > 1 if args[1] == 'true' ## FIXME: '1'でも可 @quick_session = true elsif args[1] == 'false' ## FIXME: '0'でも可 @quick_session = false else #pass ## FIXME end elsif args[0] == 'bind' and argc > 2 category = args[1] name = args[2] if argc < 4 flag = 'toggle' else flag = args[3] end bind = @surface.window[@script_side].bind # XXX for key in bind.keys group = bind[key][0].split(',', 2) next if category != group[0] next if not name.empty? and name != group[1] if ['true', '1'].include?(flag) next if bind[key][1] elsif ['false', '0'].include?(flag) next unless bind[key][1] else # 'toggle' #pass end @surface.toggle_bind([@script_side, key]) end else #pass ## FIXME end end def __yen___c(args) @balloon.open_communicatebox() end def __yen___t(args) @balloon.open_teachbox() end def __yen_v(args) raise_surface(@script_side) end def __yen_f(args) return if args.length != 2 ## FIXME tag = nil if args[0] == 'sup' if args[1] == 'true' tag = '' else tag = '' end elsif args[0] == 'sub' if args[1] == 'true' tag = '' else tag = '' end elsif args[0] == 'strike' if ['true', '1', 1].include?(args[1]) tag = '' else tag = '' end elsif args[0] == 'underline' if ['true', '1', 1].include?(args[1]) tag = '' else tag = '' end else #pass ## FIXME end unless tag.nil? @balloon.append_meta(@script_side, tag) end end SCRIPT_TAG = { '\e' => "__yen_e", '\y' => "__yen_e", '\z' => "__yen_e", '\0' => "__yen_0", '\h' => "__yen_0", '\1' => "__yen_1", '\u' => "__yen_1", '\p' => "__yen_p", '\4' => "__yen_4", '\5' => "__yen_5", '\s' => "__yen_s", '\b' => "__yen_b", '\_b' => "__yen__b", '\n' => "__yen_n", '\c' => "__yen_c", '\w' => "__yen_w", '\_w' => "__yen__w", '\t' => "__yen_t", '\_q' => "__yen__q", '\_s' => "__yen__s", '\_e' => "__yen__e", '\q' => "__yen_q", '\URL' => "__yen_URL", '\_a' => "__yen__a", '\x' => "__yen_x", '\a' => "__yen_a", # Obsolete: only for old SHIORI '\i' => "__yen_i", '\j' => "__yen_j", '\-' => "__yen_minus", '\+' => "__yen_plus", '\_+' => "__yen__plus", '\m' => "__yen_m", '\&' => "__yen_and", '\_m' => "__yen__m", '\_u' => "__yen__u", '\_v' => "__yen__v", '\8' => "__yen_8", '\_V' => "__yen__V", '\!' => "__yen_exclamation", '\__c' => "__yen___c", '\__t' => "__yen___t", '\v' => "__yen_v", '\f' => "__yen_f", '\C' => nil # dummy } def interpret_script() unless @script_wait.nil? return if Time.new.to_f < @script_wait @script_wait = nil end unless @processed_text.empty? @balloon.show(@script_side) balloon_win = @balloon.get_window(@script_side) surface_win = @surface.get_window(@script_side) balloon_win.window.restack(surface_win.window, true) @balloon.append_text(@script_side, @processed_text[0]) @processed_text = @processed_text[1..-1] surface_id = get_surface_id(@script_side) count = @balloon.get_text_count(@script_side) if @surface.invoke_talk(@script_side, surface_id, count) @balloon.reset_text_count(@script_side) end script_speed = @parent.handle_request( 'GET', 'get_preference', 'script_speed') if script_speed > 0 @script_wait = (Time.new.to_f + script_speed * 0.02) end return end node = @processed_script.shift @script_position = node[-1] case node[0] when Script::SCRIPT_TAG name, args = node[1], node[2..-2] if SCRIPT_TAG.include?(name) and \ Sakura.method_defined?(SCRIPT_TAG[name]) method(SCRIPT_TAG[name]).call(args) else #pass ## FIMXE end when Script::SCRIPT_TEXT text = expand_meta(node[1]) unless @anchor.nil? @anchor[1] = [@anchor[1], text].join('') end script_speed = @parent.handle_request( 'GET', 'get_preference', 'script_speed') if not @quick_session and script_speed >= 0 @processed_text = text else @balloon.append_text(@script_side, text) end end end def reset_script(reset_all: false) if reset_all @script_mode = BROWSE_MODE unless @script_finally.empty? for proc_obj in @script_finally proc_obj.call(:flag_break => true) end @script_finally = [] end @script_post_proc = [] @__current_script = '' end @processed_script = [] @processed_text = '' @script_position = 0 @time_critical_session = false @quick_session = false @lock_repaint = false # SSP compat set_synchronized_session(:list => [], :reset => true) @balloon.set_autoscroll(true) reset_idle_time() end def set_synchronized_session(list: [], reset: false) if reset @synchronized_session = [] elsif list.empty? unless @synchronized_session.empty? @synchronized_session = [] else @synchronized_session = [0, 1] end else @synchronized_session = list end @balloon.synchronize(@synchronized_session) end def expand_meta(text_node) buf = [] for chunk in text_node if chunk[0] == Script::TEXT_STRING buf << chunk[1] elsif chunk[1] == '%month' buf << @current_time[4].to_s elsif chunk[1] == '%day' buf << @current_time[3].to_s elsif chunk[1] == '%hour' buf << @current_time[2].to_s elsif chunk[1] == '%minute' buf << @current_time[1].to_s elsif chunk[1] == '%second' buf << @current_time[0].to_s elsif ['%username', '%c'].include?(chunk[1]) buf << get_username() elsif chunk[1] == '%selfname' buf << get_selfname() elsif chunk[1] == '%selfname2' buf << get_selfname2() elsif chunk[1] == '%keroname' buf << get_keroname() elsif chunk[1] == '%friendname' buf << get_friendname() elsif chunk[1] == '%screenwidth' left, top, scrn_w, scrn_h = get_workarea buf << scrn_w.to_s elsif chunk[1] == '%screenheight' left, top, scrn_w, scrn_h = get_workarea buf << scrn_h.to_s elsif chunk[1] == '%et' buf << @current_time[5].to_s[-1] + '万年' elsif chunk[1] == '%wronghour' wrongtime = (Time.new.to_f + [-2, -1, 1, 2].sample * 3600) buf << time.localtime(wrongtime)[3].to_s elsif chunk[1] == '%exh' buf << get_uptime().to_s elsif ['%ms', '%mz', '%ml', '%mc', '%mh', \ '%mt', '%me', '%mp', '%m?'].include?(chunk[1]) buf << getword(["\\", chunk[1][1..-1]].join('')) elsif chunk[1] == '%dms' buf << getdms() else # %c, %songname buf << chunk[1] end end return buf.join('') end ### SEND SSTP/1.3 ### def _send_sstp_handle(data) return if IO.select([], [@sstp_handle], [], 0).nil? begin @sstp_handle.send([data, "\n"].join('')) rescue SystemCallError => e #pass end end def write_sstp_handle(data) return if @sstp_handle.nil? _send_sstp_handle(['+', data].join('')) ##Logging::Logging.debug('write_sstp_handle(' + data.to_s + ')') end def close_sstp_handle() return if @sstp_handle.nil? _send_sstp_handle('-') ##Logging::Logging.debug('close_sstp_handle()') begin @sstp_handle.close() rescue SystemCallError => e #pass end @sstp_handle = nil end def close(reason: 'user') if busy() if reason == 'user' Gdk.beep() ## FIXME return else # shutdown if @updateman.is_active() @updateman.interrupt() end end end reset_script(:reset_all => true) enqueue_event('OnClose', reason) end def about() if busy() Gdk.beep() ## FIXME return end start_script(Version.VERSION_INFO) @balloon.hide_sstp_message() end def __update() return if @updateman.is_active() homeurl = getstring('homeurl') if homeurl.empty? start_script( ['\t\h\s[0]', _("I'm afraid I don't have Network Update yet."), '\e'].join('')) @balloon.hide_sstp_message() return end ghostdir = get_prefix() Logging::Logging.info('homeurl = ' + homeurl) Logging::Logging.info('ghostdir = ' + ghostdir) @updateman.start(homeurl, ghostdir) end def network_update() if busy() Gdk.beep() ## FIXME return end __update() end end class VanishDialog include GetText bindtextdomain("ninix-aya") def initialize @parent = nil # dummy @dialog = Gtk::Dialog.new @dialog.signal_connect('delete_event') do |a| next true # XXX end @dialog.set_title('Vanish') @dialog.set_modal(true) @dialog.set_resizable(false) @dialog.set_window_position(Gtk::WindowPosition::CENTER) @label = Gtk::Label.new(label=_('Vanish')) content_area = @dialog.content_area content_area.add(@label) @label.show() @dialog.add_button("_Yes", Gtk::ResponseType::YES) @dialog.add_button("_No", Gtk::ResponseType::NO) @dialog.signal_connect('response') do |w, e| next response(w, e) end end def set_responsible(parent) @parent = parent end def set_message(message) @label.set_text(message) end def show() @dialog.show() end def ok() @dialog.hide() @parent.handle_request('NOTIFY', 'notify_vanish_selected') return true end def cancel() @dialog.hide() @parent.handle_request('NOTIFY', 'notify_vanish_canceled') return true end def response(widget, response) func = {Gtk::ResponseType::YES.to_i => "ok", Gtk::ResponseType::NO.to_i => "cancel", Gtk::ResponseType::DELETE_EVENT.to_i => "cancel", } method(func[response]).call() return true end end class ReadmeDialog def initialize @parent = nil # dummy @dialog = Gtk::Dialog.new @dialog.signal_connect('delete_event') do |a| next true # XXX end @dialog.set_title('Readme.txt') @dialog.set_modal(false) @dialog.set_resizable(false) @dialog.set_window_position(Gtk::WindowPosition::CENTER) @label = Gtk::Label.new @label.show() @textview = Gtk::TextView.new @textview.set_editable(false) @textview.set_cursor_visible(false) @textview.show() scroll = Gtk::ScrolledWindow.new(nil, nil) scroll.set_policy(Gtk::PolicyType::AUTOMATIC, Gtk::PolicyType::AUTOMATIC) scroll.add(@textview) scroll.show() vbox = Gtk::Box.new(orientation=Gtk::Orientation::VERTICAL) vbox.set_size_request(720, 460) vbox.show() vbox.pack_start(@label, :expand => false, :fill => true, :padding => 0) vbox.pack_start(scroll, :expand => true, :fill => true, :padding => 0) content_area = @dialog.content_area content_area.add(vbox) @dialog.add_button("_Close", Gtk::ResponseType::CLOSE) @dialog.signal_connect('response') do |w, e| next response(w, e) end end def set_responsible(parent) @parent = parent end def show(name, base_path) @label.set_text(name) path = File.join(base_path, 'readme.txt') if File.exist?(path) f = open(path) text = f.read() text = text.force_encoding('CP932').encode("UTF-8", :invalid => :replace, :undef => :replace) # XXX @textview.buffer.set_text(text) @dialog.show() end end def response(widget, response) func = {Gtk::ResponseType::CLOSE.to_i => "hide", Gtk::ResponseType::DELETE_EVENT.to_i => "hide", } widget.method(func[response]).call() return true end end end ninix-aya-5.0.9/lib/ninix/ngm.rb0000644000175000017500000006633513416507430014633 0ustar shyshy# -*- coding: utf-8 -*- # # Copyright (C) 2001-2004 by MATSUMURA Namihiko # Copyright (C) 2004-2019 by Shyouzou Sugitani # # This program is free software; you can redistribute it and/or modify it # under the terms of the GNU General Public License (version 2) as # published by the Free Software Foundation. It is distributed in the # hope that it will be useful, but WITHOUT ANY WARRANTY; without even the # implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR # PURPOSE. See the GNU General Public License for more details. # require "net/http" require "uri" require "open-uri" require "fileutils" require 'gettext' require "gtk3" require_relative "home" require_relative "pix" require_relative "install" require_relative "logging" module NGM # 注意: # - このURLを本ゴーストマネージャクローン以外の用途で使う場合は, # 「できるだけ事前に」AQRS氏に連絡をお願いします. # (「何かゴーストマネージャ」のページ: http://www.aqrs.jp/ngm/) # - アクセスには帯域を多く使用しますので, # 必ず日時指定の差分アクセスをし余分な負荷をかけないようお願いします. # (差分アクセスの方法については本プログラムの実装が参考になると思います.) MASTERLIST = 'http://www.aqrs.jp/cgi-bin/ghostdb/request2.cgi' # 10000以上のIDを持つデータは仮登録 IDLIMIT = 10000 ELEMENTS = [ 'Name', 'SakuraName', 'KeroName', 'GhostType', 'HPUrl', 'HPTitle', 'Author', 'PublicUrl', 'ArchiveUrl', 'ArchiveSize', 'ArchiveTime', 'Version', 'AIName', 'SurfaceSetFlg', 'KisekaeFlg', 'AliasName', 'SakuraSurfaceList', 'KeroSurfaceList', 'DeleteFlg', 'NetworkUpdateTime', 'AcceptName', 'NetworkUpdateFlg', 'SakuraPreviewMD5', 'KeroPreviewMD5', 'ArchiveMD5', 'InstallDir', 'ArchiveName', 'UpdateUrl', 'AnalysisError', 'InstallCount'] class Catalog_xml #public methods def initialize(datadir) @data = {} @url = {} @cgi = {} @datadir = datadir FileUtils.mkdir_p(@datadir) unless Dir.exists?(@datadir) @last_update = '1970-01-01 00:00:00' load_MasterList() end # data handling functions def get(entry, key) entry[key] end def data @data end # updates etc def network_update#(updatehook) last_update = @last_update @last_update = Time.now.strftime('%Y-%m-%d %H:%M:%S') unless @cgi.empty? priority = @cgi.keys.sort url = @cgi[priority[-1]][-1] else url = MASTERLIST end begin uri = URI.parse(url) uri.query = URI.encode_www_form( {time: "\"#{last_update}\"", charset: 'UTF-8'}) open(uri) {|f| import_from_fileobj(f) } rescue return ## FIXME end save_MasterList() end # private methods def load_MasterList begin open(File.join(@datadir, 'MasterList.xml'), 'r') {|f| for _ in import_from_fileobj(f) #pass end } rescue # IOError return end end def save_MasterList open(File.join(@datadir, 'MasterList.xml'), 'w') {|f| export_to_fileobj(f) } end def get_encoding(line) Regexp.new('<\?xml version="1.0" encoding="(.+)" \?>').match(line)[1] end def create_entry(node) entry = {} for key, text in node fail "assert" unless ELEMENTS.include?(key) entry[key] = text end return entry end def import_from_fileobj(fileobj) line0 = fileobj.readline() encoding = get_encoding(line0) unless Encoding.name_list.include?(encoding) fail SystemExit('Unsupported encoding {0}'.format(repr(encoding))) end nest = 0 new_entry = {} set_id = nil node = [] re_list = Regexp.compile('') re_setid = Regexp.compile('') re_set = Regexp.compile('') re_priority = Regexp.compile('(.+)') re_misc = Regexp.compile('<(.+)>(.+)') for line in fileobj while Gtk.events_pending? Gtk.main_iteration() end next if line.empty? line = line.force_encoding(encoding).encode("UTF-8", :invalid => :replace, :undef => :replace) m = re_list.match(line) unless m.nil? nest += 1 next end m = re_setid.match(line) unless m.nil? nest += 1 set_id = m[1].to_i next end m = re_set.match(line) unless m.nil? nest -= 1 new_entry[set_id] = create_entry(node) node = [] next end m = re_priority.match(line) unless m.nil? g = m priority = g[1].to_i url = g[2] if @cgi.include?(priority) @cgi[priority] << url else @cgi[priority] = [url] end next end m = re_misc.match(line) unless m.nil? g = m unless set_id.nil? key = g[1] text = g[2] text = text.sub(''', '\'') text = text.sub('"', '"') text = text.sub('>', '>') text = text.sub('<', '<') text = text.sub('&', '&') node << [key, text] else key = g[1] text = g[2] #assert key in ['LastUpdate', 'NGMVersion', # 'SakuraPreviewBaseUrl', # 'KeroPreviewBaseUrl', # 'ArcMD5BaseUrl', 'NGMUpdateBaseUrl'] case key when 'LastUpdate' @last_update = text when 'NGMVersion' version = text.to_f if version < 0.51 return else @version = version end when 'SakuraPreviewBaseUrl', 'KeroPreviewBaseUrl', 'ArcMD5BaseUrl', 'NGMUpdateBaseUrl' @url[key] = text end end end end @data.update(new_entry) end def dump_entry(entry, fileobj) for key in ELEMENTS if entry.include?(key) and not entry[key].nil? text = entry[key] text = text.gsub("&", "&") text = text.gsub("<", "<") text = text.gsub(">", ">") text = text.gsub("\"", """) text = text.gsub("'", "''") else text = '' # fileobj.write(' <{0}/>\n'.format(key)) end fileobj.write([" <", key, ">", text, "\n"].join("")) end end def export_to_fileobj(fileobj) fileobj.write("\n") fileobj.write("\n") fileobj.write(["", @last_update, "\n"].join("")) for key in ["SakuraPreviewBaseUrl", "KeroPreviewBaseUrl", "ArcMD5BaseUrl", "NGMUpdateBaseUrl"] if @url.include?(key) fileobj.write(["<", key, ">", @url[key], "\n"].join("")) else fileobj.write(["<", key, ">\n"].join("")) end end fileobj.write(["", @version, "\n"].join("")) key_list = @cgi.keys.sort key_list.reverse! for priority in key_list for url in @cgi[priority] fileobj.write( ["", url, "\n"].join("")) end end ids = @data.keys.sort for set_id in ids fileobj.write(["\n"].join("")) entry = @data[set_id] dump_entry(entry, fileobj) fileobj.write("\n") end fileobj.write("\n") end end class Catalog < Catalog_xml TYPE = ['Sakura', 'Kero'] def image_filename(set_id, side) p = (TYPE[side] + "_" + set_id.to_s + ".png") d = File.join(@datadir, p) if File.exists?(d) return d else return nil end end def retrieve_image(set_id, side)#, updatehook) p = [TYPE[side], '_', set_id, '.png'].join('') d = File.join(@datadir, p) unless File.exists?(d) if side.zero? url = @url['SakuraPreviewBaseUrl'] else url = @url['KeroPreviewBaseUrl'] end begin open(d, "wb") do |file| open([url, p].join("")) do |data| file.write(data.read) end end rescue return ## FIXME end end end end class SearchDialog include GetText def initialize @parent = nil @dialog = Gtk::Dialog.new @dialog.signal_connect('delete_event') do |a| next true # XXX end @dialog.set_modal(true) @dialog.set_window_position(Gtk::WindowPosition::CENTER) label = Gtk::Label.new(label=_('Search for')) content_area = @dialog.content_area content_area.add(label) @pattern_entry = Gtk::Entry.new() @pattern_entry.set_size_request(300, -1) content_area.add(@pattern_entry) content_area.show_all() @dialog.add_button("_OK", Gtk::ResponseType::OK) @dialog.add_button("_Cancel", Gtk::ResponseType::CANCEL) @dialog.signal_connect('response') do |w, r| next response(w, r) end end def set_responsible(parent) @parent = parent end def set_pattern(text) @pattern_entry.set_text(text) end def get_pattern @pattern_entry.text end def hide @dialog.hide() end def show(default: nil) set_pattern(default) unless default.nil? @dialog.show() end def ok word = get_pattern() @parent.handle_request('NOTIFY', 'search', word) hide() end def cancel hide() end def response(widget, response) case response when Gtk::ResponseType::OK ok() when Gtk::ResponseType::CANCEL cancel() when Gtk::ResponseType::DELETE_EVENT cancel() else # should not reach here end return true end end class UI include GetText def initialize @parent = nil @opened = false @textview = [nil, nil] @darea = [nil, nil] @surface = [nil, nil] @info = nil @button = {} @url = {} @search_word = '' @search_dialog = SearchDialog.new() @search_dialog.set_responsible(self) create_dialog() end def set_responsible(parent) @parent = parent end def handle_request(event_type, event, *arglist) #assert event_type in ['GET', 'NOTIFY'] handlers = { } if handlers.include?(event) result = handlers[event].call #(*arglist) else begin result = public_send(event, *arglist) rescue result = @parent.handle_request( event_type, event, *arglist) end end return result if event_type == 'GET' end def create_dialog @window = Gtk::Window.new() @window.set_title(_('Ghost Manager')) @window.set_resizable(false) @window.signal_connect('delete_event') do |a| next close() end @window.set_window_position(Gtk::WindowPosition::CENTER) @window.gravity = Gdk::Gravity::CENTER accelgroup = Gtk::AccelGroup.new() @window.add_accel_group(accelgroup) menubar = Gtk::MenuBar.new() item = Gtk::MenuItem.new(:label => _('_File'), :use_underline => true) menubar.add(item) menu = Gtk::Menu.new() item.set_submenu(menu) item = Gtk::MenuItem.new(:label => _('Search(_F)'), :use_underline => true) item.signal_connect('activate') do |a, b| open_search_dialog() end item.add_accelerator('activate', accelgroup, Gdk::Keyval::KEY_f, Gdk::ModifierType::CONTROL_MASK, Gtk::AccelFlags::VISIBLE) menu.add(item) item = Gtk::MenuItem.new(:label => _('Search Forward(_S)'), :use_underline => true) item.signal_connect('activate') do |a, b| search_forward() end item.add_accelerator('activate', accelgroup, Gdk::Keyval::KEY_F3, Gdk::ModifierType.new(), Gtk::AccelFlags::VISIBLE) menu.add(item) item = Gtk::SeparatorMenuItem.new() menu.add(item) item = Gtk::MenuItem.new(:label => _('Settings(_O)'), :use_underline => true) item.signal_connect('activate') do |a, b| @parent.handle_request('NOTIFY', 'open_preference_dialog') end menu.add(item) item = Gtk::SeparatorMenuItem.new() menu.add(item) item = Gtk::MenuItem.new(:label => _('DB Network Update(_N)'), :use_underline => true) item.signal_connect('activate') do |a, b| network_update() end menu.add(item) item = Gtk::SeparatorMenuItem.new() menu.add(item) item = Gtk::MenuItem.new(:label => _('Close(_X)'), :use_underline => true) item.signal_connect('activate') do |a, b| close() end menu.add(item) item = Gtk::MenuItem.new(:label => _('_View'), :use_underline => true) menu = Gtk::Menu.new item.set_submenu(menu) item = Gtk::MenuItem.new(:label => _('Mask(_M)'), :use_underline => true) item.signal_connect('activate') do |a, b| @parent.handle_request('NOTIFY', 'open_mask_dialog') end menu.add(item) item = Gtk::MenuItem.new(:label => _('Reset to Default(_Y)'), :use_underline => true) item.signal_connect('activate') do |a, b| @parent.handle_request('NOTIFY', 'reset_to_default') end menu.add(item) item = Gtk::MenuItem.new(:label => _('Show All(_Z)'), :use_underline => true) item.signal_connect('activate') do |a, b| @parent.handle_request('NOTIFY', 'show_all') end menu.add(item) item = Gtk::MenuItem.new(:label => _('_Archive'), :use_underline => true) menubar.add(item) item = Gtk::MenuItem.new(:label => _('_Help'), :use_underline => true) menubar.add(item) menubar.show_all vbox = Gtk::Box.new(orientation=Gtk::Orientation::VERTICAL) @window.add(vbox) vbox.show() vbox.pack_start(menubar, :expand => false, :fill => false, :padding => 0) separator = Gtk::Separator.new(:horizontal) vbox.pack_start(separator, :expand => false, :fill => true, :padding => 0) separator.show() hbox = Gtk::Box.new(orientation=Gtk::Orientation::HORIZONTAL) vbox.pack_start(hbox, :expand => false, :fill => true, :padding => 10) hbox.show() @surface_area_sakura = create_surface_area(0) hbox.pack_start(@surface_area_sakura, :expand => false, :fill => true, :padding => 10) @surface_area_kero = create_surface_area(1) hbox.pack_start(@surface_area_kero, :expand => false, :fill => true, :padding => 10) @info_area = create_info_area() hbox.pack_start(@info_area, :expand => false, :fill => true, :padding => 10) box = Gtk::ButtonBox.new(orientation=Gtk::Orientation::HORIZONTAL) box.set_layout_style(Gtk::ButtonBoxStyle::SPREAD) vbox.pack_start(box, :expand => false, :fill => true, :padding => 4) box.show() button = Gtk::Button.new(:label => _('Previous')) button.signal_connect('clicked') do |b, w=self| w.show_previous() next true end box.add(button) button.show() @button['previous'] = button button = Gtk::Button.new(:label => _('Next')) button.signal_connect('clicked') do |b, w=self| w.show_next() next true end box.add(button) button.show() @button['next'] = button @statusbar = Gtk::Statusbar.new() vbox.pack_start(@statusbar, :expand => false, :fill => true, :padding => 0) @statusbar.show() end def network_update @window.set_sensitive(false) @parent.handle_request('NOTIFY', 'network_update')#, updatehook) update() @window.set_sensitive(true) end def open_search_dialog @search_dialog.show(:default => @search_word) end def search(word) unless word.nil? or word.empty? @search_word = word if @parent.handle_request('GET', 'search', word) update() else #pass ## FIXME end end end def search_forward unless @search_word.empty? if @parent.handle_request('GET', 'search_forward', @search_word) update() else #pass ## FIXME end end end def show_next @parent.handle_request('NOTIFY', 'go_next') update() end def show_previous @parent.handle_request('NOTIFY', 'previous') update() end def create_surface_area(side) #assert side in [0, 1] vbox = Gtk::Box.new(orientation=Gtk::Orientation::VERTICAL) vbox.show() textview = Gtk::TextView.new() textview.set_editable(false) textview.set_size_request(128, 16) vbox.pack_start(textview, :expand => false, :fill => true, :padding => 0) textview.show() @textview[side] = textview darea = Gtk::DrawingArea.new() vbox.pack_start(darea, :expand => false, :fill => true, :padding => 0) darea.set_events(Gdk::EventMask::EXPOSURE_MASK) darea.signal_connect('draw') do |w, c| redraw(w, c, side) next true end darea.show() @darea[side] = darea return vbox end def redraw(widget, cr, side) unless @surface[side].nil? cr.set_source(@surface[side], 0, 0) cr.set_operator(Cairo::OPERATOR_OVER) cr.paint() else cr.set_operator(Cairo::OPERATOR_CLEAR) cr.paint() end end def update_surface_area for side in [0, 1] if side.zero? target = 'SakuraName' else target = 'KeroName' end name = @parent.handle_request('GET', 'get', target) textbuffer = @textview[side].buffer textbuffer.set_text(name.to_s) filename = @parent.handle_request('GET', 'get_image_filename', side) darea = @darea[side] darea.realize() unless filename.nil? or filename.empty? begin surface = Pix.create_surface_from_file(filename) rescue surface = nil else w = surface.width h = surface.height darea.set_size_request(w, h) end else surface = nil end @surface[side] = surface darea.queue_draw() end end def create_info_area vbox = Gtk::Box.new(orientation=Gtk::Orientation::VERTICAL) vbox.show() hbox = Gtk::Box.new(orientation=Gtk::Orientation::HORIZONTAL) box = Gtk::ButtonBox.new(orientation=Gtk::Orientation::HORIZONTAL) box.set_layout_style(Gtk::ButtonBoxStyle::SPREAD) box.show() button = Gtk::Button.new(:label => _('Install')) button.signal_connect( 'clicked') do |b, w=self| w.handle_request('NOTIFY', 'install_current') next true end box.add(button) button.show() @button['install'] = button button = Gtk::Button.new(:label => _('Update')) button.signal_connect( 'clicked') do |b, w=self| w.handle_request('NOTIFY', 'update_current') next true end box.add(button) button.show() @button['update'] = button hbox.pack_start(box, :expand => true, :fill => true, :padding => 10) vbox2 = Gtk::Box.new(orientation=Gtk::Orientation::VERTICAL) hbox.pack_start(vbox2, :expand => false, :fill => true, :padding => 0) vbox2.show() button = Gtk::Button.new(:label => '') # with GtkLabel button.set_relief(Gtk::ReliefStyle::NONE) @url['HP'] = [nil, button.child] vbox2.pack_start(button, :expand => false, :fill => true, :padding => 0) button.signal_connect( 'clicked') do |b| webbrowser.open(@url['HP'][0]) next true end button.show() button = Gtk::Button.new(:label => '') button.set_relief(Gtk::ReliefStyle::NONE) button.set_use_underline(true) @url['Public'] = [nil, button.child] vbox2.pack_start(button, :expand => false, :fill => true, :padding => 0) button.signal_connect( 'clicked') do |b| webbrowser.open(@url['Public'][0]) next true end button.show() vbox.pack_start(hbox, :expand => false, :fill => true, :padding => 0) hbox.show() textview = Gtk::TextView.new() textview.set_editable(false) textview.set_size_request(256, 144) vbox.pack_start(textview, :expand => false, :fill => true, :padding => 0) textview.show() @info = textview return vbox end def update_info_area info_list = [[_('Author:'), 'Author'], [_('ArchiveTime:'), 'ArchiveTime'], [_('ArchiveSize:'), 'ArchiveSize'], [_('NetworkUpdateTime:'), 'NetworkUpdateTime'], [_('Version:'), 'Version'], [_('AIName:'), 'AIName']] text = '' text = [text, @parent.handle_request('GET', 'get', 'Name'), "\n"].join('') for item in info_list text = [text, item[0], @parent.handle_request('GET', 'get', item[1]), "\n"].join('') end text = [text, @parent.handle_request('GET', 'get', 'SakuraName'), _('SurfaceList:'), @parent.handle_request('GET', 'get', 'SakuraSurfaceList'), "\n"].join('') text = [text, @parent.handle_request('GET', 'get', 'KeroName'), _('SurfaceList:'), @parent.handle_request('GET', 'get', 'KeroSurfaceList'), "\n"].join('') textbuffer = @info.buffer textbuffer.set_text(text) url = @parent.handle_request('GET', 'get', 'HPUrl') text = @parent.handle_request('GET', 'get', 'HPTitle') @url['HP'][0] = url label = @url['HP'][1] label.set_markup('' + text.to_s + '') url = @parent.handle_request('GET', 'get', 'PublicUrl') text = [@parent.handle_request('GET', 'get', 'Name'), _(' Web Page')].join('') @url['Public'][0] = url label = @url['Public'][1] label.set_markup('' + text.to_s + '') target_dir = File.join( @parent.handle_request('GET', 'get_home_dir'), 'ghost', @parent.handle_request('GET', 'get', 'InstallDir')) @button['install'].set_sensitive( (not File.directory?(target_dir) and @parent.handle_request('GET', 'get', 'ArchiveUrl') != 'No data')) @button['update'].set_sensitive( (File.directory?(target_dir) and @parent.handle_request('GET', 'get', 'GhostType') == 'ゴースト' and @parent.handle_request('GET', 'get', 'UpdateUrl') != 'No data')) end def update update_surface_area() update_info_area() @button['next'].set_sensitive( @parent.handle_request('GET', 'exist_next')) @button['previous'].set_sensitive( @parent.handle_request('GET', 'exist_previous')) end def show return if @opened update() @window.show() @opened = true end def close @window.hide() @opened = false return true end end class NGM def initialize @parent = nil @current = 0 @opened = false @home_dir = Home.get_ninix_home() @catalog = Catalog.new(File.join(@home_dir, 'ngm/data')) @installer = Install::Installer.new() @ui = UI.new() @ui.set_responsible(self) end def set_responsible(parent) @parent = parent end def handle_request(event_type, event, *arglist) #assert event_type in ['GET', 'NOTIFY'] handlers = { 'get_home_dir' => lambda {|*a| return @home_dir }, 'network_update' => lambda {|*a| return network_update }, 'get' => lambda {|*a| return get(a[0]) } } if handlers.include?(event) result = handlers[event].call(*arglist) else begin result = public_send(event, *arglist) rescue result = @parent.handle_request(event_type, event, *arglist) end end return result if event_type == 'GET' end def get(element) if @catalog.data.include?(@current) entry = @catalog.data[@current] text = @catalog.get(entry, element) return text unless text.nil? end return 'No data' end def get_image_filename(side) return @catalog.image_filename(@current, side) end def search(word, set_id: 0) while set_id < IDLIMIT if @catalog.data.include?(set_id) entry = @catalog.data[set_id] for element in ['Name', 'SakuraName', 'KeroName', 'Author', 'HPTitle'] text = @catalog.get(entry, element) if text.nil? or text.empty? next end if text.include?(word) @current = set_id return true end end end set_id += 1 end return false end def search_forward(word) return search(word, :set_id => @current + 1) end def open_preference_dialog #pass end def network_update#(updatehook) @catalog.network_update#(updatehook) for set_id in @catalog.data.keys for side in [0, 1] @catalog.retrieve_image(set_id, side)#, updatehook) end end end def open_mask_dialog #pass end def reset_to_default #pass end def show_all #pass end def go_next next_index = (@current + 1) if next_index < IDLIMIT and @catalog.data.include?(next_index) @current = next_index end end def exist_next next_index = (@current + 1) return (next_index < IDLIMIT and @catalog.data.include?(next_index)) end def previous previous = (@current - 1) #assert previous < IDLIMIT if @catalog.data.include?(previous) @current = previous end end def exist_previous previous = (@current - 1) #assert previous < IDLIMIT return @catalog.data.include?(previous) end def show_dialog @ui.show() end def install_current begin filetype, target_dir = @installer.install(get('ArchiveUrl'), Home.get_ninix_home()) rescue target_dir = nil end assert filetype == 'ghost' unless target_dir.nil? @parent.handle_request('NOTIFY', 'add_sakura', target_dir) end end def update_current @parent.handle_request('NOTIFY', 'update_sakura', get('Name'), 'NGM') end end end ninix-aya-5.0.9/lib/ninix/makoto.rb0000644000175000017500000000367513416507430015342 0ustar shyshy# -*- coding: utf-8 -*- # # Copyright (C) 2001, 2002 by Tamito KAJIYAMA # Copyright (C) 2004-2019 by Shyouzou Sugitani # # This program is free software; you can redistribute it and/or modify it # under the terms of the GNU General Public License (version 2) as # published by the Free Software Foundation. It is distributed in the # hope that it will be useful, but WITHOUT ANY WARRANTY; without even the # implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR # PURPOSE. See the GNU General Public License for more details. # module Makoto def self.execute(s) buf = [] i = 0 j = s.length while i < j i, text = expand(s, i) buf << text end buf.join end def self.expand(s, start) segments = [] repeat_count = nil validity = 0 i = start j = s.length buf = [] while i < j if s[i] == "\\" if i + 1 < j and '(|)'.include?(s[i + 1]) buf << s[i + 1] else buf << s[i..i + 1] end i += 2 elsif s[i] == '(' if validity.zero? segments << buf.join buf = [] i += 1 else i, text = expand(s, i) buf << text end validity = 1 elsif s[i] == '|' and validity > 0 segments << buf.join buf = [] i += 1 elsif s[i] == ')' and validity > 0 segments << buf.join i += 1 if i < j and '123456789'.include?(s[i]) repeat_count = s[i].to_i i += 1 end validity = 2 break else buf << s[i] i += 1 end end case validity when 2 expanded = segments[1..segments.length-1].sample unless repeat_count.nil? expanded = (expanded * rand(repeat_count)) end return i, segments[0] + expanded when 0 return j, buf.join else return j, s[start..s.length-1] end end end ninix-aya-5.0.9/lib/ninix/alias.rb0000644000175000017500000000636613416507430015141 0ustar shyshy# -*- coding: utf-8 -*- # # Copyright (C) 2001, 2002 by Tamito KAJIYAMA # Copyright (C) 2004-2019 by Shyouzou Sugitani # # This program is free software; you can redistribute it and/or modify it # under the terms of the GNU General Public License (version 2) as # published by the Free Software Foundation. It is distributed in the # hope that it will be useful, but WITHOUT ANY WARRANTY; without even the # implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR # PURPOSE. See the GNU General Public License for more details. # require_relative "config" require_relative "logging" module Alias def self.fatal(error) Logging::Logging.error('alias.rb: ' + error.to_s) NConfig.null_config end def self.create_from_file(path) buf = [] File::open(path, 'rb') {|f| while line = f.gets line = line.strip buf << line unless line.empty? end } create_from_buffer(buf) end def self.create_from_buffer(buf) re_alias = Regexp.new('\A(sakura|kero|char[0-9]+)\.surface\.alias\z') dic = NConfig::Config.new i, j = 0, buf.length while i < j line = buf[i] i += 1 next if line.length.zero? match = re_alias.match(line) unless match.nil? name = line table = {} begin while true if i < j line = buf[i] i += 1 else fail ValueError('unexpedted end of file') end line = line.gsub(0x81.chr + 0x40.chr, "").strip() next if line.length.zero? break if line == '{' fail ValueError('open brace not found') end while true if i < j line = buf[i] i += 1 else fail ValueError('unexpected end of file') end line = line.gsub(0x81.chr + 0x40.chr, "").strip() next if line.length.zero? break if line == '}' line = line.split(',', 2) if line.length == 2 key = line[0].strip values = line[1].strip else fail 'malformed line found' end if !values.empty? and \ values.start_with?('[') and values.end_with?(']') table[key] = [] for value in values[1, values.length - 2].split(',', 0) begin value = Integer(value).to_s rescue #pass end table[key] << value end else fail 'malformed line found' end end rescue => e return fatal(e.message) end dic[name] = table else line = line.split(',', 2) if line.length == 2 key = line[0].strip value = line[1].strip else return fatal('malformed line found') end if key == 'makoto' if !value.empty? and \ value.start_with('[') and value.end_with(']') value = value[1, value.length - 2].split(',', 0) else value = [value] end end dic[key] = value end end dic end end ninix-aya-5.0.9/lib/ninix/version.rb0000644000175000017500000000236313416507430015526 0ustar shyshy# -*- coding: utf-8 -*- # # Copyright (C) 2005-2019 by Shyouzou Sugitani # # This program is free software; you can redistribute it and/or modify it # under the terms of the GNU General Public License (version 2) as # published by the Free Software Foundation. It is distributed in the # hope that it will be useful, but WITHOUT ANY WARRANTY; without even the # implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR # PURPOSE. See the GNU General Public License for more details. # require 'gettext' module Version include GetText bindtextdomain("ninix-aya") def self.NUMBER '5.0.9' end def self.CODENAME 'power cycle' end def self.VERSION "#{self.NUMBER} (#{self.CODENAME})" end def self.VERSION_INFO '\h\s[0]\w8ninix-aya ' .concat("#{self.VERSION}") .concat('\n') .concat(_('Are igai No Nanika with "Nin\'i" for X')) .concat('\n') .concat('\_q') .concat('Copyright (c) 2001, 2002 Tamito KAJIYAMA\n') .concat('Copyright (c) 2002-2006 MATSUMURA Namihiko\n') .concat('Copyright (c) 2002-2019 Shyouzou Sugitani\n') .concat('Copyright (c) 2002, 2003 ABE Hideaki\n') .concat('Copyright (c) 2003-2005 Shun-ichi TAHARA\e') end end ninix-aya-5.0.9/lib/ninix/communicate.rb0000644000175000017500000001126213416507430016343 0ustar shyshy# -*- coding: utf-8 -*- # # communicate.rb - ghost-to-ghost communication mechanism # Copyright (C) 2002-2019 by Shyouzou Sugitani # Copyright (C) 2002, 2003 by MATSUMURA Namihiko # # This program is free software; you can redistribute it and/or modify it # under the terms of the GNU General Public License (version 2) as # published by the Free Software Foundation. It is distributed in the # hope that it will be useful, but WITHOUT ANY WARRANTY; without even the # implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR # PURPOSE. See the GNU General Public License for more details. # require_relative "logging" module Communicate class Communicate def initialize @ghosts = {} end def rebuild_ghostdb(sakura, name: '', s0: 0, s1: 10) @ghosts.delete(sakura) @ghosts[sakura] = [name, s0, s1] if not name.nil? end def get_otherghostname(name) @ghosts.values.select {|value| value[0] != name }.join(1.chr) end def notify_all(event, references) @ghosts.each_key do |sakura| sakura.enqueue_event(event, *references) end end ON_OTHER_EVENT = { 'OnBoot' => 'OnOtherGhostBooted', 'OnFirstBoot' => 'OnOtherGhostBooted', 'OnClose' => 'OnOtherGhostClosed', 'OnGhostChanged' => 'OnOtherGhostChanged', 'OnSurfaceChange' => 'OnOtherSurfaceChange', 'OnVanishSelected' => 'OnOtherGhostVanished', 'OnOverlap' => 'OnOtherOverlap', 'OnOffscreen' => 'OnOtherOffscreen' } def notify_other(sakura_key, event, name, selfname, shell_name, flag_break, communicate, sstp, notranslate, script, references) return if script.empty? and not ON_OTHER_EVENT.include?(event) on_other_event = nil if ON_OTHER_EVENT.include?(event) if flag_break and ['OnClose', 'OnVanishSelected'].include?(event) # NOP else on_other_event = ON_OTHER_EVENT[event] end end if on_other_event.nil? and not script.empty? on_other_event = 'OnOtherGhostTalk' end return if on_other_event.nil? args = case on_other_event when 'OnOtherGhostBooted' [selfname, script, name] when 'OnOtherGhostClosed' [selfname, script, name] when 'OnOtherGhostChanged' [references[0], selfname, references[1], script, references[2], name, references[7], shell_name] when 'OnOtherSurfaceChange' side, new_id, new_w, new_h = references[2].split(',', 4) prev_id = references[3] [name, selfname, side, new_id, prev_id, references[4]] when 'OnOtherGhostVanished' [selfname, script, name, shell_name] when 'OnOtherOverlap' [] ## FIXME when 'OnOtherOffscreen' [] ## FIXME when 'OnOtherGhostTalk' flags = '' if flag_break if !flags.empty? flags = (flags + ',') flags = (flags + 'break') end end if not sstp.nil? ## FIXME: owned, remote if !flags.empty? flags = (flags + ',') end flags = (flags + 'sstp-send') end if notranslate if !flags.empty? flags = (flags + ',') end flags = (flags + 'notranslate') end refs = references.each {|value| value.to_s}.join(1.chr) Logging::Logging.debug( "NOTIFY OTHER: " \ "#{on_other_event}, #{name}, #{selfname}, #{flags}, " \ "#{event}, #{script}, #{refs}") [name, selfname, flags, event, script, refs] else # XXX: should not reach here return end for sakura in @ghosts.keys() next if sakura.key == sakura_key if not communicate.nil? if communicate == '__SYSTEM_ALL_GHOST__' sakura.enqueue_event('OnCommunicate', selfname, script) next elsif communicate.include?(1.chr) to = name.split(1.chr, 0) if to.include?(@ghosts[sakura][0]) sakura.enqueue_event('OnCommunicate', selfname, script) next end else if @ghosts[sakura][0] == communicate sakura.enqueue_event('OnCommunicate', selfname, script) next end end end if sakura.is_listening(on_other_event) sakura.enqueue_event(on_other_event, *args) end end end end end ninix-aya-5.0.9/lib/ninix/config.rb0000644000175000017500000000364513416507430015312 0ustar shyshy# -*- coding: utf-8 -*- # # Copyright (C) 2001, 2002 by Tamito KAJIYAMA # Copyright (C) 2003-2019 by Shyouzou Sugitani # # This program is free software; you can redistribute it and/or modify it # under the terms of the GNU General Public License (version 2) as # published by the Free Software Foundation. It is distributed in the # hope that it will be useful, but WITHOUT ANY WARRANTY; without even the # implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR # PURPOSE. See the GNU General Public License for more details. # require_relative "logging" module NConfig class Config < Hash def get(name, default: nil) keylist = (name.is_a?(Array) ? name : [name]) key = keylist.find {|x| keys.include?(x) } key.nil? ? default : self[key] end end def self.create_from_file(path) has_bom = File.open(path) {|f| f.read(3) } == "\xEF\xBB\xBF" charset = has_bom ? 'UTF-8' : 'CP932' buf = [] File.open(path, has_bom ? 'rb:BOM|UTF-8' : 'rb') {|f| while line = f.gets line = line.strip buf << line unless line.empty? end } return create_from_buffer(buf, :charset => charset) end def self.create_from_buffer(buf, charset: 'CP932') dic = Config.new buf.each do |line| line = line.force_encoding(charset).encode( "UTF-8", :invalid => :replace, :undef => :replace) key, value = line.split(",", 2) next if key.nil? or value.nil? key.strip! case key when 'charset' value.strip! if Encoding.name_list.include?(value) charset = value else Logging::Logging.error('Unsupported charset ' + value) end when 'refreshundeletemask', 'icon', 'cursor', 'shiori', 'makoto' dic[key] = value else dic[key] = value.strip end end return dic end def self.null_config() NConfig::Config.new end end ninix-aya-5.0.9/lib/ninix/install.rb0000644000175000017500000005606213416507430015514 0ustar shyshy# -*- coding: utf-8 -*- # # install.rb - an installer module for ninix # Copyright (C) 2001, 2002 by Tamito KAJIYAMA # Copyright (C) 2002, 2003 by MATSUMURA Namihiko # Copyright (C) 2002-2019 by Shyouzou Sugitani # Copyright (C) 2003 by Shun-ichi TAHARA # # This program is free software; you can redistribute it and/or modify it # under the terms of the GNU General Public License (version 2) as # published by the Free Software Foundation. It is distributed in the # hope that it will be useful, but WITHOUT ANY WARRANTY; without even the # implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR # PURPOSE. See the GNU General Public License for more details. # require "tmpdir" require "zip" require "open-uri" require "fileutils" require "gtk3" require_relative "home" require_relative "logging" require_relative "version" module Install class InstallError < StandardError #pass end def self.fatal(error) Logging::Logging.error(error) # XXX fail InstallError.new(error) end class Installer def initialize @dialog = Gtk::MessageDialog.new(:type => Gtk::MessageType::QUESTION, :buttons_type => :yes_no) @select_dialog = Gtk::Dialog.new() @select_dialog.add_button("_Cancel", Gtk::ResponseType::REJECT) @select_dialog.add_button("_OK", Gtk::ResponseType::ACCEPT) ls = Gtk::ListStore.new(Integer, String) tv = Gtk::TreeView.new(model=ls) renderer = Gtk::CellRendererText.new() col0 = Gtk::TreeViewColumn.new('No.', renderer, :text => 0) col1 = Gtk::TreeViewColumn.new('Path', renderer, :text => 1) tv.append_column(col0) tv.append_column(col1) sw = Gtk::ScrolledWindow.new() sw.set_vexpand(true) sw.set_policy(Gtk::PolicyType::AUTOMATIC, Gtk::PolicyType::AUTOMATIC) sw.add(tv) sw.show_all() # XXX @treeview = tv label = Gtk::Label.new(label='Multiple candidates found.\nSelect the path name of the supplement target.') ## FIXME: gettext ##label.set_use_markup(True) content_area = @select_dialog.content_area content_area.add(label) label.show() content_area.add(sw) @select_dialog.set_title('Select the target') ## FIXME: gettext @select_dialog.set_default_size(-1, 200) end def check_archive(filename) # check archive format Install.fatal('unknown archive format') unless ['.nar', '.zip'].include?(File.extname(filename).downcase) end def extract_files(filename) # extract files from the archive tmpdir = Dir.mktmpdir('ninix-aya') FileUtils.remove_entry_secure(tmpdir) begin FileUtils.mkdir_p(tmpdir) rescue Install.fatal('cannot make temporary directory') end url = nil if filename.start_with?('http:') or filename.start_with?('ftp:') url = filename filename = download(filename, tmpdir) if filename.nil? FileUtils.remove_entry_secure(tmpdir) Install.fatal('cannot download the archive file') end else check_archive(filename) end begin zf = Zip::File.new(filename) for entry in zf name = entry.name next if entry.directory? path = File.join(tmpdir, name) dname, fname = File.split(path) FileUtils.mkdir_p(dname) unless Dir.exists?(dname) buf = zf.read(name) of = open(path, 'wb') of.write(buf) of.close() end zf.close() rescue FileUtils.remove_entry_secure(tmpdir) Install.fatal('cannot extract files from the archive') end Dir.glob(tmpdir) { |path| if File.directory?(path) st_mode = File.stat(path).mode File.chmod(st_mode | 128 | 256 | 64, path) end if File.file?(path) st_mode = File.stat(path).mode File.chmod(st_mode | 128 | 256, path) end } rename_files(tmpdir) return tmpdir end def get_file_type(tmpdir) errno = 0 # check the file type inst = Home.read_install_txt(tmpdir) if inst.nil? if File.exists?(File.join(tmpdir, 'kinoko.ini')) filetype = 'kinoko' else Install.fatal('cannot read install.txt from the archive') end else filetype = inst.get('type') end case filetype when 'ghost' unless Dir.exists?(File.join(tmpdir, 'ghost', 'master')) filetype = 'ghost.inverse' end when 'ghost with balloon' filetype = 'ghost.inverse' when 'shell', 'shell with balloon', 'supplement' if inst.include?('accept') filetype = 'supplement' else filetype = 'shell.inverse' end when 'balloon' # pass when 'skin' filetype = 'nekoninni' when 'katochan' # pass when 'kinoko' # pass else errno = 2 # unsupported file type end if ['shell.inverse', 'ghost.inverse'].include?(filetype) errno = 2 # unsupported file type end return filetype, errno end def install(filename, homedir) errno = 0 begin tmpdir = extract_files(filename) rescue errno = 1 end return nil, nil, nil, errno unless errno.zero? begin filetype, errno = get_file_type(tmpdir) rescue errno = 4 filetype = nil end unless errno.zero? FileUtils.remove_entry_secure(tmpdir) return filetype, nil, nil, errno end begin case filetype when "ghost" target_dirs, names, errno = install_ghost(filename, tmpdir, homedir) when "supplement" target_dirs, names, errno = install_supplement(filename, tmpdir, homedir) when "balloon" target_dirs, names, errno = install_balloon(filename, tmpdir, homedir) when "kinoko" target_dirs, names, errno = install_kinoko(filename, tmpdir, homedir) when "nekoninni" target_dirs, names, errno = install_nekoninni(filename, tmpdir, homedir) when "katochan" target_dirs, names, errno = install_katochan(filename, tmpdir, homedir) else # should not reach here end rescue target_dirs = nil names = nil errno = 5 ensure FileUtils.remove_entry_secure(tmpdir) end return filetype, target_dirs, names, errno end def download(url, basedir) #Logging::Logging.debug('downloading ' + url) begin ifile = open(url) rescue # IOError return nil end #Logging::Logging.debug( # '(size = ' + ifile.length.to_s + ' bytes)') arcdir = Home.get_archive_dir() FileUtils.mkdir_p(arcdir) unless Dir.exists?(arcdir) basedir = arcdir filename = File.join(basedir, File.basename(url)) begin ofile = open(filename, 'wb') while true data = ifile.read(4096) break if data.nil? # EOF ofile.write(data) end rescue # IOError, SystemCallError return nil end ofile.close() ifile.close() # check the format of the downloaded file check_archive(filename) begin zf = Zip::File.new(filename) rescue return nil ensure zf.close() end return filename end def rename_files(basedir) return if RUBY_PLATFORM.downcase =~ /mswin(?!ce)|mingw|cygwin|bccwin/ # XXX Dir.foreach(basedir) { |filename| next if /\A\.+\z/ =~ filename filename2 = filename.downcase path = File.join(basedir, filename2) if filename != filename2 File.rename(File.join(basedir, filename), path) end rename_files(path) if File.directory?(path) } end def list_all_directories(top, basedir) dirlist = [] Dir.foreach(File.join(top, basedir)) { |path| next if /\A\.+\z/ =~ path if File.directory?(File.join(top, basedir, path)) dirlist += list_all_directories(top, File.join(basedir, path)) dirlist << File.join(basedir, path) end } return dirlist end def remove_files_and_dirs(target_dir, mask) path = File.absolute_path(target_dir) return unless File.directory?(path) Dir.foreach(path) { |filename| next if /\A\.+\z/ =~ filename remove_files(mask, path, filename) } dirlist = list_all_directories(path, '') dirlist.sort! dirlist.reverse! for name in dirlist current_path = File.join(path, name) if File.directory?(current_path) head, tail = File.split(current_path) if not mask.include?(tail) and Dir.entries(current_path).reject{|entry| entry =~ /\A\.{1,2}\z/}.empty? FileUtils.remove_entry_secure(current_path) end end end end def remove_files(mask, top_dir, name) path = File.join(top_dir, name) if File.directory?(path) or mask.include?(name) # pass else File.delete(path) end end def lower_files(top_dir) return if RUBY_PLATFORM.downcase =~ /mswin(?!ce)|mingw|cygwin|bccwin/ # XXX n = 0 Dir.foreach(top_dir) { |filename| next if /\A\.+\z/ =~ filename filename2 = filename.downcase path = File.join(top_dir, filename2) if filename != filename2 File.rename(File.join(top_dir, filename), path) Logging::Logging.info( 'renamed ' + File.join(top_dir, filename)) n += 1 end n += lower_files(path) if File.directory?(path) } return n end def confirm(message) @dialog.set_markup(message) response = @dialog.run() @dialog.hide() return response == Gtk::ResponseType::YES end def confirm_overwrite(path, type_string) return confirm(['Overwrite "', path, '"(', type_string, ')?'].join('')) end def confirm_removal(path, type_string) return confirm(['Remove "', path, '"(', type_string, ')?'].join('')) end def confirm_refresh(path, type_string) return confirm(['Remove "', path, '"(', type_string, ') to Refresh Install?'].join('')) end def select(candidates) fail "assert" unless candidates.length >= 1 return candidates[0] if candidates.length == 1 ls = @treeview.get_model() ls.clear() for i, item in enumerate(candidates) ls << [i, item] end ts = @treeview.get_selection() ts.select_iter(ls.get_iter_first()) response = @select_dialog.run() @select_dialog.hide() return nil if response != Gtk::ResponseType::ACCEPT model, it = ts.get_selected() return candidates[model.get_value(it, 0)] end def install_ghost(archive, tmpdir, homedir) # find install.txt inst = Home.read_install_txt(tmpdir) Install.fatal('install.txt not found') if inst.nil? target_dir = inst.get('directory') Install.fatal('"directory" not found in install.txt') if target_dir.nil? prefix = File.join(homedir, 'ghost', target_dir) ghost_src = File.join(tmpdir, 'ghost', 'master') shell_src = File.join(tmpdir, 'shell') ghost_dst = File.join(prefix, 'ghost', 'master') shell_dst = File.join(prefix, 'shell') filelist = [] ##filelist << [File.join(tmpdir, 'install.txt'), ## File.join(prefix, 'install.txt')] # XXX readme_txt = File.join(tmpdir, 'readme.txt') if File.exists?(readme_txt) filelist << [readme_txt, File.join(prefix, 'readme.txt')] end thumbnail_png = File.join(tmpdir, 'thumbnail.png') thumbnail_pnr = File.join(tmpdir, 'thumbnail.pnr') if File.exists?(thumbnail_png) filelist << [thumbnail_png, File.join(prefix, 'thumbnail.png')] elsif File.exists?(thumbnail_pnr) filelist << [thumbnail_pnr, File.join(prefix, 'thumbnail.pnr')] end for path in list_all_files(ghost_src, '') filelist << [File.join(ghost_src, path), File.join(ghost_dst, path)] end ghost_name = inst.get('name') # find shell for path in list_all_files(shell_src, '') filelist << [File.join(shell_src, path), File.join(shell_dst, path)] end # find balloon balloon_dir = inst.get('balloon.directory') balloon_name = nil unless balloon_dir.nil? balloon_dir = Home.get_normalized_path(balloon_dir) balloon_dst = File.join(homedir, 'balloon', balloon_dir) balloon_src = inst.get('balloon.source.directory') unless balloon_src.nil? balloon_src = Home.get_normalized_path(balloon_src) else balloon_src = balloon_dir end inst_balloon = Home.read_install_txt( File.join(tmpdir, balloon_src)) if Dir.exists?(balloon_dst) and \ not confirm_removal(balloon_dst, 'balloon') # pass # don't install balloon else if Dir.exists?(balloon_dst) # uninstall older versions of the balloon remove_files_and_dirs(balloon_dst, []) end balloon_list = [] for path in list_all_files(File.join(tmpdir, balloon_src), '') balloon_list << [File.join(tmpdir, balloon_src, path), File.join(balloon_dst, path)] end install_files(balloon_list) unless inst_balloon.nil? balloon_name = inst_balloon.get('name') end end end if Dir.exists?(prefix) inst_dst = Home.read_install_txt(prefix) unless inst.get('refresh', :default => 0).to_i.zero? # uninstall older versions of the ghost if confirm_refresh(prefix, 'ghost') mask = [] for path in inst.get('refreshundeletemask', :default => '').split(':', 0) mask << Home.get_normalized_path(path) end mask << 'HISTORY' remove_files_and_dirs(prefix, mask) else return nil, nil, 3 end else unless confirm_overwrite(prefix, 'ghost') return nil, nil, 3 end end end # install files Logging::Logging.info('installing ' + archive + ' (ghost)') install_files(filelist) # create SETTINGS path = File.join(prefix, 'SETTINGS') unless File.exists?(path) begin f = open(path, 'w') unless balloon_dir.nil? f.write(["balloon_directory, ", balloon_dir, "\n"].join('')) end rescue # IOError, SystemCallError => e Logging::Logging.error('cannot write ' + path) ensure f.close() end if balloon_dir.nil? # XXX balloon_dir = '' end end return [[target_dir, balloon_dir], [ghost_name, balloon_name], 0] end def install_supplement(archive, tmpdir, homedir) inst = Home.read_install_txt(tmpdir) if not inst.nil? and inst.include?('accept') Logging::Logging.info('searching supplement target ...') candidates = [] begin dirlist = Dir.entries(File.join(homedir, 'ghost')).reject{|entry| entry =~ /\A\.{1,2}\z/} rescue SystemCallError dirlist = [] end for dirname in dirlist path = File.join(homedir, 'ghost', dirname) if File.exists?(File.join(path, 'shell', 'surface.txt')) next # ghost.inverse(obsolete) end desc = Home.read_descript_txt( File.join(path, 'ghost', 'master')) if not desc.nil? and desc.get('sakura.name') == inst.get('accept') candidates << dirname end end if candidates.empty? Logging:Logging.info('not found') return nil, nil, 4 else target = select(candidates) if target.nil? return nil, nil, 4 end path = File.join(homedir, 'ghost', target) if inst.include?('directory') if inst.get('type') == 'shell' path = File.join(path, 'shell', inst['directory']) else unless inst.include?('type') Logging::Logging.error('supplement type not specified') else Logging::Logging.error('unsupported supplement type: ' + inst['type']) end return nil, nil, 4 end end Logging::Logging.info('found') unless Dir.exists?(path) FileUtils.mkdir_p(path) end File.delete(File.join(tmpdir, 'install.txt')) distutils.dir_util.copy_tree(tmpdir, path) return target, inst.get('name'), 0 end end end def install_balloon(archive, srcdir, homedir) # find install.txt inst = Home.read_install_txt(srcdir) if inst.nil? Install.fatal('install.txt not found') end target_dir = inst.get('directory') if target_dir.nil? Install.fatal('"directory" not found in install.txt') end dstdir = File.join(homedir, 'balloon', target_dir) filelist = [] for path in list_all_files(srcdir, '') filelist << [File.join(srcdir, path), File.join(dstdir, path)] end ##filelist << [File.join(srcdir, 'install.txt'), ## File.join(dstdir, 'install.txt')] if Dir.exists?(dstdir) inst_dst = Home.read_install_txt(dstdir) unless inst.get('refresh', :default => 0).to_i.zero? # uninstall older versions of the balloon if confirm_refresh(dstdir, 'balloon') mask = [] for path in inst.get('refreshundeletemask', :default => '').split(':', 0) mask << Home.get_normalized_path(path) end remove_files_and_dirs(dstdir, mask) else return nil, nil, 3 end else unless confirm_overwrite(dstdir, 'balloon') return nil, nil, 3 end end end # install files Logging::Logging.info('installing ' + archive + ' (balloon)') install_files(filelist) return target_dir, inst.get('name'), 0 end def uninstall_kinoko(homedir, name) begin dirlist = Dir.entries(File.join(homedir, 'kinoko')).reject{|entry| entry =~ /\A\.{1,2}\z/} rescue SystemCallError return end for subdir in dirlist path = File.join(homedir, 'kinoko', subdir) kinoko = Home.read_kinoko_ini(path) if kinoko.nil? next end kinoko_name = kinoko['title'] if kinoko_name == name kinoko_dir = File.join(homedir, 'kinoko', subdir) if confirm_removal(kinoko_dir, 'kinoko') FileUtils.remove_entry_secure(kinoko_dir) end end end end def install_kinoko(archive, srcdir, homedir) # find kinoko.ini kinoko = Home.read_kinoko_ini(srcdir) if kinoko.nil? Install.fatal('failed to read kinoko.ini') end kinoko_name = kinoko['title'] unless kinoko['extractpath'].nil? dstdir = File.join(homedir, 'kinoko', kinoko['extractpath']) else dstdir = File.join(homedir, 'kinoko', File.basename(archive)[0,-4]) end # find files filelist = [] Dir.foreach(srcdir) { |filename| next if /\A\.+\z/ =~ filename path = File.join(srcdir, filename) if File.file?(path) filelist << [path, File.join(dstdir, filename)] end } # uninstall older versions of the kinoko uninstall_kinoko(homedir, kinoko_name) # install files Logging::Logging.info('installing ' + archive + ' (kinoko)') install_files(filelist) return dstdir, [kinoko_name, kinoko['ghost'], kinoko['category']], 0 end def uninstall_nekoninni(homedir, dir) nekoninni_dir = File.join(homedir, 'nekodorif', 'skin', dir) return unless Dir.exists?(nekoninni_dir) if confirm_removal(nekoninni_dir, 'nekodorif skin') FileUtils.remove_entry_secure(nekoninni_dir) end end def install_nekoninni(archive, srcdir, homedir) # find install.txt inst = Home.read_install_txt(srcdir) if inst.nil? Install.fatal('install.txt not found') end target_dir = inst.get('directory') if target_dir.nil? Install.fatal('"directory" not found in install.txt') end dstdir = File.join(homedir, 'nekodorif', 'skin', target_dir) # find files filelist = [] Dir.foreach(srcdir) { |filename| next if /\A\.+\z/ =~ filename path = File.join(srcdir, filename) if File.file?(path) filelist << [path, File.join(dstdir, filename)] end } # uninstall older versions of the skin uninstall_nekoninni(homedir, target_dir) # install files Logging::Logging.info('installing ' + archive + ' (nekodorif skin)') install_files(filelist) return target_dir, inst.get('name'), 0 end def uninstall_katochan(homedir, target_dir) katochan_dir = File.join(homedir, 'nekodorif', 'katochan', target_dir) return unless Dir.exists?(katochan_dir) if confirm_removal(katochan_dir, 'nekodorif katochan') FileUtils.remove_entry_secure(katochan_dir) end end def install_katochan(archive, srcdir, homedir) # find install.txt inst = Home.read_install_txt(srcdir) if inst.nil? Install.fatal('install.txt not found') end target_dir = inst.get('directory') if target_dir.nil? Install.fatal('"directory" not found in install.txt') end dstdir = File.join(homedir, 'nekodorif', 'katochan', target_dir) # find files filelist = [] Dir.foreach(srcdir) { |filename| next if /\A\.+\z/ =~ filename path = File.join(srcdir, filename) if File.file?(path) filelist << [path, File.join(dstdir, filename)] end } # uninstall older versions of the skin uninstall_katochan(homedir, target_dir) # install files Logging::Logging.info('installing ' + archive + ' (nekodorif katochan)') install_files(filelist) return target_dir, inst.get('name'), 0 end def list_all_files(top, target_dir) filelist = [] Dir.foreach(File.join(top, target_dir)) { |path| next if /\A\.+\z/ =~ path if File.directory?(File.join(top, target_dir, path)) filelist += list_all_files( top, File.join(target_dir, path)) else filelist << File.join(target_dir, path) end } return filelist end def install_files(filelist) for from_path, to_path in filelist dirname, filename = File.split(to_path) FileUtils.mkdir_p(dirname) unless Dir.exists?(dirname) FileUtils.copy(from_path, to_path) end end end end ninix-aya-5.0.9/lib/ninix/dll/0000755000175000017500000000000013416507430014263 5ustar shyshyninix-aya-5.0.9/lib/ninix/dll/satori.rb0000644000175000017500000025452013416507430016121 0ustar shyshy# -*- coding: utf-8 -*- # # satori.rb - a "里々" compatible Shiori module for ninix # Copyright (C) 2002 by Tamito KAJIYAMA # Copyright (C) 2002, 2003 by MATSUMURA Namihiko # Copyright (C) 2002-2019 by Shyouzou Sugitani # Copyright (C) 2003, 2004 by Shun-ichi TAHARA # # This program is free software; you can redistribute it and/or modify it # under the terms of the GNU General Public License (version 2) as # published by the Free Software Foundation. It is distributed in the # hope that it will be useful, but WITHOUT ANY WARRANTY; without even the # implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR # PURPOSE. See the GNU General Public License for more details. # # TODO: # - φエスケープ: 特殊記号の無効化, replaceの無効化など # - イベントを単語群で定義できるように # - 文と単語群の重複回避 # - ≫ # - コミュニケート # - 内部呼び出し: 単語の追加, sync # - $トーク中のなでられ反応 # - $BalloonOffset0, $BalloonOffset1 # - マルチキャラクタ require "pathname" require_relative "../home" require_relative "../logging" module Satori NODE_TEXT = 1 NODE_REF = 2 NODE_SIDE = 3 NODE_ASSIGNMENT = 4 NODE_JUMP = 5 NODE_SEARCH = 6 NODE_CHOICE = 7 NODE_CALL = 8 NODE_SAORI = 9 NODE_OR_EXPR = 20 NODE_AND_EXPR = 21 NODE_COMP_EXPR = 22 NODE_ADD_EXPR = 23 NODE_MUL_EXPR = 24 NODE_POW_EXPR = 25 NODE_UNARY_EXPR = 26 def self.encrypt(s) buf = [] t = s.length p = ((t + 1) / 2).to_i for n in 0..p-1 buf << s[n] if s[p..-1].length > n buf << s[-n - 1] end end return buf end def self.decrypt(s) buf = [] t = s.length for n in 0.step(t-1, 2) buf << s[n..-1][0..0] # XXX end if (t % 2).zero? p = 1 else p = 2 end for n in p.step(t-1, 2) buf << s[-n..-1][0..0] # XXX end return buf.join('') end def self.list_dict(top_dir) buf = [] begin dir_list = Pathname(top_dir).children().map {|x| x.relative_path_from(Pathname(top_dir)).to_path } rescue #except OSError: dir_list = [] end for filename in dir_list basename = File.basename(filename, '.*') ext = File.extname(filename) ext = ext.downcase if (filename.downcase.start_with?('dic') and \ ['.txt', '.sat'].include?(ext)) or \ ['replace.txt', 'replace_after.txt', 'satori_conf.txt', 'satori_conf.sat'].include?(filename.downcase) # XXX buf << File.join(top_dir, filename) end end return buf end class Filter def initialize(rules) @rules = [] for pat, rep in rules @rules << [pat, rep] end end def apply(text) for pat, rep in @rules text = text.gsub(pat, rep) end return text end end ### PARSER ### def self.read_tab_file(path, encrypted: false) lineno = 0 buf = [] open(path, 'rb') do |f| for line in f lineno += 1 if line.end_with?("\r\n") line = line[0..-3] elsif line.end_with?("\r") or line.end_with?("\n") line = line[0..-2] end if encrypted line = Satori.decrypt(Satori.decrypt(line)) end begin line = line.force_encoding('CP932').encode('utf-8', :invalid => :replace, :undef => :replace) rescue => e #except UnicodeError as e: Logging::Logging.debug('satori.py: ' + e.to_s + ' in ' + path.to_s + ' (line ' + lineno.to_s + ')') next end begin old, new = line.split("\t", 2) rescue #except ValueError: Logging::Logging.debug('satori.py: invalid line in ' + path.to_s + ' (line ' + lineno.to_s + ')') next end unless old.nil? or new.nil? buf << [old, new] end end end return buf end class Parser attr_reader :anchor_filter, :talk, :word def initialize @talk = {} @word = {} @variable = [] @parenthesis = 0 @replace_filter = Filter.new([]) @anchor_list = [] @anchor_filter = Filter.new(@anchor_list) @is_anchor = false @saori = [] @separator = ["\1", ',', ',', '、', '、'] @count = {'Talk' => 0, 'NoNameTalk' => 0, 'EventTalk' => 0, 'OtherTalk' => 0, 'Words' => 0, 'Word' => 0, 'Variable' => 0, 'Anchor' => 0, 'Parenthesis' => 0, 'Parentheres' => 0, ## XXX 'Line' => 0,} end def set_saori(saori_list) @saori = saori_list end def get_count(name) if @count.include?(name) return @count[name] else return 0 end end def load_replace_file(path) @replace_filter = Filter.new(Satori.read_tab_file(path)) end def get_dict return @talk, @word end def read(path) basename = File.basename(path, '.*') ext = File.extname(path) ext = ext.downcase if ext == '.sat' encrypted = true else encrypted = false end filename = File.basename(path) if filename.downcase.start_with?('dicanchor') # XXX @is_anchor = true else @is_anchor = false end open(path, 'rb') do |f| read_file(f, :path => path, :encrypted => encrypted) end if @is_anchor @anchor_filter = Filter.new(@anchor_list) end end def read_file(f, path: nil, encrypted: false) lineno = 0 linelist = [] line_buffer = nil # XXX phi_escape = {} # key = lineno: [position] parser = nil # XXX for line in f if line_buffer.nil? lineno += 1 phi_escape[lineno] = [] end if line.end_with?("\r\n") line = line[0..-3] elsif line.end_with?("\r") or line.end_with?("\n") line = line[0..-2] end if encrypted line = Satori.decrypt(Satori.decrypt(line)) end begin line = line.force_encoding('CP932').encode('utf-8', :invalid => :replace, :undef => :replace) rescue => e #except UnicodeError as e: if path.nil? Logging::Logging.debug('satori.py: ' + e.to_s + ' (line ' + lineno.to_s + ')') else Logging::Logging.debug('satori.py: ' + e.to_s + ' in ' + path.to_s + ' (line ' + lineno.to_s + ')') end next end unless line_buffer.nil? line = [line_buffer, line].join('') line_buffer = nil end pos = 0 while line[pos..-1].count('φ') >0 pos = line.index('φ', pos) if pos == (line.length - 1) line_buffer = line[0..-2] break else phi_escape[lineno] << pos if pos.zero? line = line[1..-1] else line = [line[0..pos-1], line[pos + 1..-1]].join('') end end end next unless line_buffer.nil? pos = 0 while line[pos..-1].count('#') > 0 pos = line.index('#', pos) unless phi_escape[lineno].include?(pos) ## FIXME if pos.zero? line = "" else line = line[0..pos-1] end break end end if line.nil? or line.empty? next end if line.start_with?('*') and not phi_escape[lineno].include?(0) unless linelist.empty? method(parser).call(linelist, phi_escape) end parser = 'parse_talk' linelist = [lineno, line] elsif line.start_with?('@') and not phi_escape[lineno].include?(0) unless linelist.empty? method(parser).call(linelist, phi_escape) end parser = 'parse_word_group' linelist = [lineno, line] elsif not linelist.empty? # apply replace.txt line = @replace_filter.apply(line) ## FIXME: phi_escape linelist << line end end # for no in phi_escape: # if phi_escape[no]: # print('PHI:', no, phi_escape[no]) # end # end unless linelist.empty? method(parser).call(linelist, phi_escape) end @count['Line'] = @count['Line'] + lineno talk = 0 eventtalk = 0 for key in @talk.keys value = @talk[key] number = value.length talk += number if key.start_with?('On') eventtalk += number end end @count['Talk'] = talk @count['EventTalk'] = eventtalk if @talk.include?('') @count['NoNameTalk'] = @talk[''].length end @count['OtherTalk'] = (@count['Talk'] \ - @count['NoNameTalk'] \ - @count['EventTalk']) @count['Words'] = @word.length word = 0 for value in @word.values() word += value.length end @count['Word'] = word @count['Anchor'] = @anchor_list.length @count['Variable'] = @variable.length @count['Parenthesis'] = @parenthesis @count['Parentheres'] = @parenthesis end def parse_talk(linelist, phi_escape) lineno = linelist[0] buf = [] line = linelist[1] fail "assert" unless line.start_with?('*') name = line[1..-1] while linelist.length > 3 and not linelist[-1] linelist.delete_at(-1) end prev = '' num_open = 0 num_close = 0 for n in 2..linelist.length-1 line = linelist[n] num_open += line.count('(') ### FIXME: φ num_close += line.count(')') ### FIXME: φ if num_open > 0 and num_open != num_close if n == (linelist.length - 1) Logging::Logging.debug( 'satori.py: syntax error (unbalanced parens)') else prev = [prev, linelist[n]].join('') next end else num_open = 0 num_close = 0 end line = [prev, linelist[n]].join('') prev = '' current_lineno = (lineno + n - 2) if not line.empty? and line[0] == '$' and not phi_escape[current_lineno].include?(0) node = parse_assignment(line) unless node.nil? buf << node end elsif not line.empty? and line[0] == '>' and not phi_escape[current_lineno].include?(0) node = parse_jump(line) unless node.nil? buf << node end elsif not line.empty? and line[0] == '≫' and not phi_escape[current_lineno].include?(0) node = parse_search(line) unless node.nil? buf << node end elsif not line.empty? and line[0] == '_' and not phi_escape[current_lineno].include?(0) node = parse_choice(line) unless node.nil? buf << node end else nodelist = parse_talk_word(line) unless nodelist.nil? buf.concat(nodelist) end end end unless buf.empty? if @talk.include?(name) talk_list = @talk[name] else talk_list = @talk[name] = [] end talk_list << buf if @is_anchor @anchor_list << [name, "\\_a[" + name.to_s + ']' + name.to_s + "\\_a"] end end end def parse_word_group(linelist, phi_escape) lineno = linelist[0] buf = [] line = linelist[1] fail "assert" unless line.start_with?('@') name = line[1..-1] prev = '' num_open = 0 num_close = 0 for n in 2..linelist.length-1 line = linelist[n] num_open += line.count('(') ### FIXME: φ num_close += line.count(')') ### FIXME: φ if num_open > 0 and num_open != num_close if n == (linelist.length - 1) Logging::Logging.debug( 'satori.py: syntax error (unbalanced parens)') else prev = [prev, linelist[n]].join('') next end else num_open = 0 num_close = 0 end line = [prev, linelist[n]].join('') prev = '' next if line.empty? word = parse_word(line) unless word.nil? buf << word end end unless buf.empty? if @word.include?(name) word_list = @word[name] else word_list = @word[name] = [] end word_list.concat(buf) end end def parse_assignment(line) fail "assert" unless line[0] == '$' break_flag = false for n in 1..line.length-1 if ["\t", ' ', ' ', '='].include?(line[n]) ### FIXME: φ break_flag = true break end end unless break_flag Logging::Logging.debug('satori.py: syntax error (expected a tab or equal)') return nil end name_str = line[1..n-1] #.join('') # XXX name = parse_word(line[1..n-1]) if line[n] == '=' ### FIXME: φ n += 1 value = parse_expression(line[n..-1]) else sep = line[n] while n < line.length and line[n] == sep n += 1 end value = parse_word(line[n..-1]) end if name_str == '引数区切り削除' sep = line[n..-1] #.join('') # XXX if @separator.include?(sep) @separator.delete(sep) end return nil elsif name_str == '引数区切り追加' sep = line[n..-1] #.join('') # XXX unless @separator.include?(sep) @separator << sep end return nil end unless @variable.include?(name) @variable << name end return [NODE_ASSIGNMENT, name, value] end def parse_jump(line) fail "assert" unless line[0] == '>' break_flag = false for n in 1..line.length-1 if line[n] == "\t" ### FIXME: φ break_flag = true break end end unless break_flag n = line.length end target = parse_word(line[1..n-1]) while n < line.length and line[n] == "\t" ### FIXME: φ n += 1 end if n < line.length condition = parse_expression(line[n..-1]) else condition = nil end return [NODE_JUMP, target, condition] end def parse_search(line) return [NODE_SEARCH] end def parse_choice(line) fail "assert" unless line[0] == '_' break_flag = false for n in 1..line.length-1 if line[n] == "\t" ### FIXME: φ break_flag = true break end end unless break_flag n = line.length end label = parse_word(line[1..n-1]) while n < line.length and line[n] == "\t" ### FIXME: φ n += 1 end if n < line.length id_ = parse_word(line[n..-1]) else id_ = nil end return [NODE_CHOICE, label, id_] end def parse_talk_word(line) buf = parse_word(line) buf << [NODE_TEXT, ["\\n"]] return buf end def parse_word(line) buffer_ = [] text = [] while not line.nil? and not line.empty? if line[0] == ':' ### FIXME: φ unless text.empty? buffer_ << [NODE_TEXT, text] text = [] end buffer_ << [NODE_SIDE, [line[0]]] line = line[1..-1] elsif line[0] == '(' ### FIXME: φ @parenthesis += 1 unless text.empty? buffer_ << [NODE_TEXT, text] text = [] end line = parse_parenthesis(line, buffer_) else text << line[0] line = line[1..-1] end end unless text.empty? buffer_ << [NODE_TEXT, text] end return buffer_ end def find_close(text, position) nest = 0 current = position while text[current..-1].count(')') > 0 pos_new = text.index(')', current) break if pos_new.zero? nest = text[position..pos_new-1].count('(') - text[position..pos_new-1].count(')') if nest > 0 current = (pos_new + 1) else current = pos_new break end end return current end def split_(text, sep) buf = [] pos_end = -1 while true position = text.index('(', pos_end + 1) if position.nil? or position == -1 ll = text[pos_end + 1..-1].split(sep, -1) if buf.length > 0 last = buf.pop() buf << [last, ll[0]].join('') unless ll[1..-1].nil? buf.concat(ll[1..-1]) end else buf.concat(ll) end break else if position.zero? ll = [""] else ll = text[pos_end + 1..position-1].split(sep, -1) end pos_end = find_close(text, position + 1) last = ll.pop() ll << [last, text[position..pos_end]].join('') if buf.length > 0 last = buf.pop() buf << [last, ll[0]].join('') unless ll[1..-1].nil? buf.concat(ll[1..-1]) end else buf.concat(ll) end end end buf = buf.map {|x| x.strip() } return buf end def parse_parenthesis(line, buf) text_ = [] fail "assert" unless line[0] == '(' line = line[1..-1] ## FIXME depth = 1 count = 1 while not line.nil? and not line.empty? if line[0] == ')' ### FIXME: φ depth -= 1 if text_.length != count # () text_ << '' end if depth.zero? line = line[1..-1] break end text_ << line[0] line = line[1..-1] elsif line[0] == '(' ### FIXME: φ depth += 1 count += 1 text_ << line[0] line = line[1..-1] else text_ << line[0] line = line[1..-1] end end unless text_.empty? sep = nil pos = text_.length for c in @separator ### FIXME: φ next if text_.count(c).zero? if text_.index(c) < pos pos = text_.index(c) sep = c end end unless sep.nil? list_ = split_(text_.join(''), sep) ## FIXME else list_ = [text_.join('')] end if ['単語の追加', 'sync', 'loop', 'call', 'set', 'remember', 'nop', '合成単語群', 'when', 'times', 'while', 'for'].include?(list_[0]) or list_[0].end_with?('の数') function = list_[0] args = [] if list_.length > 1 for i in 1..list_.length-1 args << parse_expression(list_[i]) end end buf << [NODE_CALL, function, args] return line elsif @saori.include?(list_[0]) function = list_[0] args = [] if list_.length > 1 for i in 1..list_.length # XXX: parse as text args << parse_word(list_[i]) end end buf << [NODE_SAORI, function, args] return line else if list_.length > 1 # XXX nodelist = [[NODE_TEXT, ['(']]] nodelist.concat(parse_word(text_)) nodelist << [NODE_TEXT, [')']] buf << [NODE_REF, nodelist] return line else nodelist = [[NODE_TEXT, ['(']]] nodelist.concat(parse_word(text_)) nodelist << [NODE_TEXT, [')']] buf << [NODE_REF, nodelist] return line end end end buf << [NODE_TEXT, ['']] # XXX return line end def parse_expression(line) default = [[NODE_TEXT, line[0..-1]]] begin line, buf = get_or_expr(line) rescue #except ValueError as e: return default end return default unless line.nil? or line.empty? return buf end def get_or_expr(line) line, and_expr = get_and_expr(line) buf = [NODE_OR_EXPR, and_expr] while not line.nil? and not line.empty? and ['|', '|'].include?(line[0]) ### FIXME: φ line = line[1..-1] if not line.nil? and not line.empty? and ['|', '|'].include?(line[0]) ### FIXME: φ line = line[1..-1] else fail ValueError('broken OR operator') end line, and_expr = get_and_expr(line) buf << and_expr end if buf.length == 2 return line, buf[1] end return line, [buf] end def get_and_expr(line) line, comp_expr = get_comp_expr(line) buf = [NODE_AND_EXPR, comp_expr] while not lin.nil? and not line.empty? and ['&', '&'].include?(line[0]) ### FIXME: φ line = line[1..-1] if not line.nil? and not line.empty? and ['&', '&'].include?(line[0]) ### FIXME: φ line = line[1..-1] else fail ValueError('broken AND operator') end line, comp_expr = get_comp_expr(line) buf << comp_expr end if buf.length == 2 return line, buf[1] end return line, [buf] end def get_comp_expr(line) line, buf = get_add_expr(line) if not line.nil? and not line.empty? and ['<', '<'].include?(line[0]) ### FIXME: φ line = line[1..-1] op = '<' if not line.nil? and not line.empty? and ['=', '='].include?(line[0]) ### FIXME: φ line = line[1..-1] op = '<=' end line, add_expr = get_add_expr(line) return line, [[NODE_COMP_EXPR, buf, op, add_expr]] elsif not line.nil? and not line.empty? and ['>', '>'].include?(line[0]) ### FIXME: φ line = line[1..-1] op = '>' if not line.nil? and not line.empty? and ['=', '='].include?(line[0]) ### FIXME: φ line = line[1..-1] op = '>=' end line, add_expr = get_add_expr(line) return line, [[NODE_COMP_EXPR, buf, op, add_expr]] elsif not line.nil? and not line.empty? and ['=', '='].include?(line[0]) ### FIXME: φ line = line[1..-1] if not line.nil? and not line.empty? and ['=', '='].include?(line[0]) ### FIXME: φ line = line[1..-1] else fail ValueError('broken EQUAL operator') end line, add_expr = get_add_expr(line) return line, [[NODE_COMP_EXPR, buf, '==', add_expr]] elsif not line.nil? and not line.empty? and ['!', '!'].include?(line[0]) ### FIXME: φ line = line[1..-1] if not line.nil? and not line.empty? and ['=', '='].include?(line[0]) ### FIXME: φ line = line[1..-1] else fail ValueError('broken NOT EQUAL operator') end line, add_expr = get_add_expr(line) return line, [[NODE_COMP_EXPR, buf, '!=', add_expr]] end return line, buf end def get_add_expr(line) line, mul_expr = get_mul_expr(line) buf = [NODE_ADD_EXPR, mul_expr] while not line.nil? and not line.empty? and ['+', '+', '-', '−', '-'].include?(line[0]) ### FIXME: φ if ['+', '+'].include?(line[0]) buf << '+' else buf << '-' end line = line[1..-1] line, mul_expr = get_mul_expr(line) buf << mul_expr end if buf.length == 2 return line, buf[1] end return line, [buf] end def get_mul_expr(line) line, pow_expr = get_pow_expr(line) buf = [NODE_MUL_EXPR, pow_expr] while not line.nil? and not line.empty? and \ ['*', '*', '×', '/', '/', '÷', '%', '%'].include?(line[0]) ### FIXME: φ if ['*', '*', '×'].include?(line[0]) buf << '*' elsif ['/', '/', '÷'].include?(line[0]) buf << '/' else buf << '%' end line = line[1..-1] line, pow_expr = get_pow_expr(line) buf << pow_expr end if buf.length == 2 return line, buf[1] end return line, [buf] end def get_pow_expr(line) line, unary_expr = get_unary_expr(line) buf = [NODE_POW_EXPR, unary_expr] while not line.nil? and not line.empty? and ['^', '^'].include?(line[0]) ### FIXME: φ line = line[1..-1] line, unary_expr = get_unary_expr(line) buf << unary_expr end if buf.length == 2 return line, buf[1] end return line, [buf] end def get_unary_expr(line) if not line.nil? and not line.empty? and ['-', '−', '-'].include?(line[0]) ### FIXME: φ line = line[1..-1] line, unary_expr = get_unary_expr(line) return line, [[NODE_UNARY_EXPR, '-', unary_expr]] end if not line.nil? and not line.empty? and ['!', '!'].include?(line[0]) ### FIXME: φ line = line[1..-1] line, unary_expr = get_unary_expr(line) return line, [[NODE_UNARY_EXPR, '!', unary_expr]] end if not line.nil? and not line.empty? and line[0] == '(' ### FIXME: φ line = line[1..-1] line, buf = get_or_expr(line) if not line.nil? and not line.empty? and line[0] == ')' ### FIXME: φ line = line[1..-1] else fail ValueError('expected a close paren') end return line, buf end return get_factor(line) end Operators = [ '|', '|', '&', '&', '<', '<', '>', '>', '=', '=', '!', '!', '+', '+', '-', '−', '-', '*', '*', '×', '/', '/', '÷', '%', '%', '^', '^', '(', ')'] def get_factor(line) buf = [] while not line.nil? and not line.empty? and not Operators.include?(line[0]) if not line.nil? and not line.empty? and line[0] == '(' ### FIXME: φ line = parse_parenthesis(line, buf) next end text = [] while not line.nil? and not line.empty? and not Operators.include?(line[0]) and line[0] != '(' ### FIXME: φ text << line[0] line = line[1..-1] end unless text.empty? buf << [NODE_TEXT, text] end end if buf.empty? fail ValueError('expected a constant') end return line, buf end def print_nodelist(node_list, depth: 0) for node in node_list indent = (' ' * depth) case node[0] when NODE_TEXT temp = node[1].map {|x| x.encode('utf-8', :invalid => :replace, :undef => :replace) }.join('') print([indent, 'NODE_TEXT "' + temp + '"'].join(''), "\n") when NODE_REF print([indent, 'NODE_REF'].join(''), "\n") print_nodelist(node[1], :depth => depth + 1) when NODE_CALL print([indent, 'NODE_CALL'].join(''), "\n") for i in 0..node[2].length-1 print_nodelist(node[2][i], :depth => depth + 1) end when NODE_SAORI print([indent, 'NODE_SAORI'].join(''), "\n") for i in 0..node[2].length-1 print_nodelist(node[2][i], :depth => depth + 1) end when NODE_SIDE print([indent, 'NODE_SIDE'].join(''), "\n") when NODE_ASSIGNMENT print([indent, 'NODE_ASSIGNMENT'].join(''), "\n") print([indent, 'variable'].join(''), "\n") print_nodelist(node[1], :depth => depth + 1) print([indent, 'value'].join(''), "\n") print_nodelist(node[2], :depth => depth + 1) when NODE_JUMP print([indent, 'NODE_JUMP'].join(''), "\n") print([indent, 'name'].join(''), "\n") print_nodelist(node[1], :depth => depth + 1) unless node[2].nil? print([indent, 'condition'].join(''), "\n") print_nodelist(node[2], :depth => depth + 1) end when NODE_SEARCH print([indent, 'NODE_SEARCH'].join(''), "\n") when NODE_CHOICE print([indent, 'NODE_CHOICE'].join(''), "\n") print([indent, 'label'].join(''), "\n") print_nodelist(node[1], :depth => depth + 1) unless node[2].nil? print([indent, 'id'].join(''), "\n") print_nodelist(node[2], :depth => depth + 1) end when NODE_OR_EXPR print([indent, 'NODE_OR_EXPR'].join(''), "\n") print_nodelist(node[1], :depth => depth + 1) for i in 2..node.length-1 print([indent, 'op ||'].join(''), "\n") print_nodelist(node[i], :depth => depth + 1) end when NODE_AND_EXPR print([indent, 'NODE_ADD_EXPR'].join(''), "\n") print_nodelist(node[1], :depth => depth + 1) for i in 2..node.length-1 print([indent, 'op &&'].join(''), "\n") print_nodelist(node[i], :depth => depth + 1) end when NODE_COMP_EXPR print([indent, 'NODE_COMP_EXPR'].join(''), "\n") print_nodelist(node[1], :depth => depth + 1) print([indent, 'op'].join(''), node[2], "\n") print_nodelist(node[3], :depth => depth + 1) when NODE_ADD_EXPR print([indent, 'NODE_ADD_EXPR'].join(''), "\n") print_nodelist(node[1], :depth => depth + 1) for i in 2.step(node.length-1, 2) print([indent, 'op'].join(''), node[i], "\n") print_nodelist(node[i + 1], :depth => depth + 1) end when NODE_MUL_EXPR print([indent, 'NODE_MUL_EXPR'].join(''), "\n") print_nodelist(node[1], :depth => depth + 1) for i in 2.step(node.length-1, 2) print([indent, 'op'].join(''), node[i], "\n") print_nodelist(node[i + 1], :depth => depth + 1) end when NODE_POW_EXPR print([indent, 'NODE_POW_EXPR'].join(''), "\n") print_nodelist(node[1], :depth => depth + 1) for i in 2..node.length-1 print([indent, 'op ^'].join(''), "\n") print_nodelist(node[i], :depth => depth + 1) end when NODE_UNARY_EXPR print([indent, 'NODE_UNARY_EXPR'].join(''), "\n") print([indent, 'op'].join(''), node[1], "\n") print_nodelist(node[2], :depth => depth + 1) else fail RuntimeError('should not reach here') end end end end # expression := or_expr # or_expr := and_expr ( op_op and_expr )* # or_op := "||" # and_expr := comp_expr ( and_op comp_expr )* # and_op := "&&" # comp_expr := add_expr ( comp_op add_expr )? # comp_op := "<" | ">" | "<=" | ">=" | "==" | "!=" # add_expr := mul_expr ( add_op mul_expr )* # add_op := "+" | "−" # mul_expr := pow_expr ( mul_op pow_expr )* # mul_op := "×" | "÷" | "*" | "/" | "%" # pow_expr := unary_expr ( pow_op unary_expr )* # pow_op := "^" # unary_expr := unary_op unary_expr | "(" or_expr ")" | factor # unary_op := "−" | "!" # factor := ( constant | reference )* ### INTERPRETER ### class SATORI DBNAME = 'satori_savedata.txt' EDBNAME = 'satori_savedata.sat' def initialize(satori_dir: nil) satori_init(:satori_dir => satori_dir) end def satori_init(satori_dir: nil) @satori_dir = satori_dir @dbpath = File.join(satori_dir, DBNAME) @saori_function = {} @parser = Parser.new() reset() end def reset @word = {} @talk = {} @variable = {} @replace_filter = Filter.new([]) @reset_surface = true @mouse_move_count = {} @mouse_wheel_count = {} @touch_threshold = 60 @touch_timeout = 2 @current_surface = [0, 10] @default_surface = [0, 10] @add_to_surface = [0, 0] @newline = "\\n[half]" @newline_script = '' @save_interval = 0 @save_timer = 0 @url_list = {} @boot_script = nil @script_history = ([nil] * 64) @wait_percent = 100 @random_talk = -1 @reserved_talk = {} @silent_time = 0 @choice_id = nil @choice_label = nil @choice_number = nil @timer = {} @time_start = nil @runtime = 0 # accumulative @runcount = 1 # accumulative @folder_change = false @saori_value = {} end def load buf = [] for path in Satori.list_dict(@satori_dir) filename = File.basename(path) case filename when 'replace.txt' @parser.load_replace_file(path) when 'replace_after.txt' load_replace_file(path) when 'satori_conf.txt', 'satori_conf.sat' load_config_file(path) else buf << path end end for path in buf @parser.read(path) end @talk, @word = @parser.get_dict() load_database() @time_start = Time.new get_event_response('OnSatoriLoad') @boot_script = get_event_response('OnSatoriBoot') end def load_config_file(path) parser = Parser.new() parser.read(path) talk, word = parser.get_dict() for nodelist in (talk.include?('初期化') ? talk['初期化'] : []) expand(nodelist) end end def load_replace_file(path) @replace_filter = Filter.new(Satori.read_tab_file(path)) end def load_database if @variable['セーブデータ暗号化'] == '有効' encrypted = true @dbpath = File.join(@satori_dir, EDBNAME) else encrypted = false @dbpath = File.join(@satori_dir, DBNAME) end begin database = Satori.read_tab_file(@dbpath, :encrypted => encrypted) rescue #except IOError: database = [] end for name, value in database if name.start_with?('b\'') or name.start_with?('b"') next # XXX: 文字コード変換に問題のあったバージョン対策 end assign(name, value) end end def save_database if @variable['セーブデータ暗号化'] == '有効' encrypted = true @dbpath = File.join(@satori_dir, EDBNAME) else encrypted = false @dbpath = File.join(@satori_dir, DBNAME) end begin open(@dbpath, 'wb') do |f| for name in @variable.keys value = @variable[name] if ['前回終了時サーフェス0', '前回終了時サーフェス1', 'デフォルトサーフェス0', 'デフォルトサーフェス1'].include?(name) next end line = (name.to_s + "\t" + value.to_s) line = line.encode('CP932', :invalid => :replace, :undef => :replace) if encrypted line = Satori.encrypt(Satori.encrypt(line)).join('') end f.write(line) f.write("\r\n") end for side in [0, 1] name = ('デフォルトサーフェス' + side.to_s) value = to_zenkaku(@default_surface[side].to_s) line = (name.to_s + "\t" + value.to_s) line = line.encode('CP932', :invalid => :replace, :undef => :replace) if encrypted line = Satori.encrypt(Satori.encrypt(line)).join('') end f.write(line) f.write("\r\n") end for side in [0, 1] name = ('前回終了時サーフェス' + side.to_s) value = to_zenkaku(@current_surface[side].to_s) line = (name.to_s + "\t" + value.to_s) line = line.encode('CP932', :invalid => :replace, :undef => :replace) if encrypted line = Satori.encrypt(Satori.encrypt(line)).join('') end f.write(line) f.write("\r\n") end name = '起動回数' value = to_zenkaku(@runcount.to_s) line = (name.to_s + "\t" + value.to_s) line = line.encode('CP932', :invalid => :replace, :undef => :replace) if encrypted line = Satori.encrypt(Satori.encrypt(line)).join('') end f.write(line) f.write("\r\n") for name in @timer.keys value = to_zenkaku(@timer[name]) line = (name.to_s + "\t" + value.to_s) line = line.encode('CP932', :invalid => :replace, :undef => :replace) if encrypted line = Satori.encrypt(Satori.encrypt(line)).join('') end f.write(line) f.write("\r\n") end for name in @reserved_talk.keys value = to_zenkaku(@reserved_talk[name]) line = (['次から', value, '回目のトーク'].join('') + "\t" + name.to_s) line = line.encode('CP932', :invalid => :replace, :undef => :replace) if encrypted line = Satori.encrypt(Satori.encrypt(line)).join('') end f.write(line) f.write("\r\n") end end rescue #except IOError: Logging::Logging.debug('satori.py: cannot write ' + @dbpath.to_s) return end end def finalize get_event_response('OnSatoriUnload') accumulative_runtime = (@runtime + get_runtime()) assign('単純累計秒', to_zenkaku(accumulative_runtime)) save_database() end # SHIORI/1.0 API def getaistringrandom get_script('') end def getaistringfromtargetword(word) '' end def getdms '' end def getword(word_type) '' end # SHIORI/2.2 API EVENT_MAP = { 'OnFirstBoot' => '初回', 'OnBoot' => '起動', 'OnClose' => '終了', 'OnGhostChanging' => '他のゴーストへ変更', 'OnGhostChanged' => '他のゴーストから変更', 'OnVanishSelecting' => '消滅指示', 'OnVanishCancel' => '消滅撤回', 'OnVanishSelected' => '消滅決定', 'OnVanishButtonHold' => '消滅中断', } def get_event_response(event, ref0: nil, ref1: nil, ref2: nil, ref3: nil, ref4: nil, ref5: nil, ref6: nil, ref7: nil) @event = event @reference = [ref0, ref1, ref2, ref3, ref4, ref5, ref6, ref7] tail = '\e' case event when 'OnUpdateReady' begin ref0 = (Integer(ref0) + 1).to_s @reference[0] = ref0 rescue #pass end when 'OnMouseMove' key = [ref3, ref4] # side, part count, timestamp = (@mouse_move_count.include?(key) ? @mouse_move_count[key] : [0, 0]) if (Time.now - timestamp).to_i > @touch_timeout count = 0 end count += 1 if count >= @touch_threshold event = (ref3.to_s + ref4.to_s + 'なでられ') count = 0 end @mouse_move_count[key] = [count, Time.now] when 'OnMouseWheel' key = [ref3, ref4] # side, part count, timestamp = (@mouse_wheel_count.include?(key) ? @mouse_wheel_count[key] : [0, 0]) if (Time.now - timestamp).to_i > 2 count = 0 end count += 1 if count >= 2 event = (ref3.to_s + ref4.to_s + 'ころころ') count = 0 end @mouse_wheel_count[key] = [count, Time.now] when 'OnSecondChange' @silent_time += 1 if @save_interval > 0 @save_timer -= 1 if @save_timer <= 0 save_database() @save_timer = @save_interval end end if ref3 != '0' # cantalk # check random talk timer if @random_talk.zero? reset_random_talk_interval() elsif @random_talk > 0 @random_talk -= 1 if @random_talk.zero? event = get_reserved_talk() unless event.nil? @reference[0] = to_zenkaku(1) else @reference[0] = to_zenkaku(0) end @reference[1] = event script = get_script('OnTalk') unless script.nil? @script_history.shift @script_history. << script return script end @reference[0] = ref0 @reference[1] = ref1 end end end # check user-defined timers for name in @timer.keys() count = @timer[name] - 1 if count > 0 @timer[name] = count elsif ref3 != '0' # cantalk @timer.delete(name) event = name[0..-7] break end end when 'OnSurfaceChange' @current_surface[0] = ref0 @current_surface[1] = ref1 when 'OnChoiceSelect' @choice_id = ref0 @choice_label = ref1 @choice_number = ref2 unless @talk.include?('OnChoiceSelect') event = ref0 end when 'OnChoiceEnter' @choice_id = ref1 @choice_label = ref0 @choice_number = ref2 when 'OnAnchorSelect' if @talk.include?(ref0) event = ref0 end when 'sakura.recommendsites', 'sakura.portalsites', 'kero.recommendsites' return get_url_list(event) when 'OnRecommandedSiteChoice' script = get_url_script(ref0, ref1) unless script.nil? @script_history.shift @script_history << script end return script when *EVENT_MAP if ['OnBoot', 'OnGhostChanged'].include?(event) unless @boot_script.nil? script = @boot_script @boot_script = nil @script_history.shift @script_history << script return script end end if ['OnClose', 'OnGhostChanging'].include?(event) if event == 'OnClose' tail = '\-\e' end script = get_script('OnSatoriClose', :tail => tail) unless script.nil? @script_history.shift @script_history << script return script end end unless @talk.include?(event) event = EVENT_MAP[event] end end script = get_script(event, :tail => tail) unless script.nil? @script_history.shift @script_history << script end return script end # SHIORI/2.4 API def teach(word) name = @variable['教わること'] unless name.nil? @variable[name] = word script = get_script([name, 'を教えてもらった'].join('')) @script_history.shift @script_history << script return script end return nil end # SHIORI/2.5 API def getstring(name) word = @word[name] return expand(word.sample) unless word.nil? return nil end # internal def get_reserved_talk reserved = nil for key in @reserved_talk.keys @reserved_talk[key] -= 1 if @reserved_talk[key] <= 0 reserved = key end end unless reserved.nil? @reserved_talk.delete(reserved) else reserved = '' end return reserved end Re_reservation = Regexp.new('\A次から(([[:digit:]])+)(〜(([[:digit:]])+))?回目のトーク') def assign(name, value) if name.end_with?('タイマ') if @talk.include?(name[0..-4]) add_timer(name, value) end elsif name == '全タイマ解除' if value == '実行' delete_all_timers() end elsif name == '辞書リロード' if value == '実行' reload() end elsif name == '手動セーブ' if value == '実行' save_database() end elsif not Re_reservation.match(name).nil? return nil if value.nil? or value.empty? match = Re_reservation.match(name) number = to_integer(match[1]) number = Array(number..to_integer(match[4])).sample unless match[4].nil? while true break_flag = false for key in @reserved_talk.keys if self.reserved_talk[key] == number number += 1 break_flag = true break end end break unless break_flag end @reserved_talk[value] = number elsif name == '次のトーク' return nil if value.nil? or value.empty? number = 1 while true break_flag = false for key in @reserved_talk.keys if self.reserved_talk[key] == number number += 1 break_flag = true break end end break unless break_flag end @reserved_talk[value] = number elsif name == 'トーク予約のキャンセル' if value == '*' @reserved_talk = {} elsif @reserved_talk.include?(value) @reserved_talk.delete(value) end elsif name == '起動回数' @runcount = (to_integer(value) + 1) elsif value.nil? or value.empty? if @variable.include?(name) @variable.delete(name) end else @variable[name] = value if ['喋り間隔', '喋り間隔誤差'].include?(name) reset_random_talk_interval() elsif name == '教わること' return '\![open,teachbox]' elsif name == '会話時サーフェス戻し' if value == '有効' @reset_surface = true elsif value == '無効' @reset_surface = false end elsif name == 'デフォルトサーフェス0' value = to_integer(value) @default_surface[0] = value unless value.nil? elsif name == 'デフォルトサーフェス1' value = to_integer(value) @default_surface[1] = value unless value.nil? elsif name == 'サーフェス加算値0' value = to_integer(value) unless value.nil? @default_surface[0] = value @add_to_surface[0] = value end elsif name == 'サーフェス加算値1' value = to_integer(value) unless value.nil? @default_surface[1] = value @add_to_surface[1] = value end elsif name == '単純累計秒' @runtime = to_integer(value) elsif name == 'なでられ反応回数' @touch_threshold = to_integer(value) elsif name == 'なでられ持続秒数' @touch_timeout = to_integer(value) elsif name == 'スコープ切り換え時' @newline = value elsif name == 'さくらスクリプトによるスコープ切り換え時' @newline_script = value elsif name == '自動挿入ウェイトの倍率' if value.end_with?('%') value = value[0..-2] elsif value.end_with?('%') value = value[0..-2] end value = to_integer(value) if not value.nil? and value >= 0 and value <= 1000 @wait_percent = value end elsif name == '自動セーブ間隔' @save_interval = to_integer(value) @save_timer = @save_interval elsif name == '辞書フォルダ' @folder_change = true end end return nil end def change_folder value = @variable['辞書フォルダ'] dir_list = value.split(',', -1) @parser = Parser.new() @parser.set_saori(@saori_function.keys()) for path in Satori.list_dict(@satori_dir) filename = File.basename(path) if filename == 'replace.txt' @parser.load_replace_file(path) elsif filename == 'replace_after.txt' load_replace_file(path) end end buf = [] for dir_ in dir_list dir_ = Home.get_normalized_path(dir_) dict_dir = File.join(@satori_dir, dir_) unless File.directory?(dict_dir) Logging::Logging.debug('satori.py: cannot read ' + dict_dir.to_s) next end for path in Satori.list_dict(dict_dir) filename = File.basename(path) if filename == 'replace.txt' ## XXX @parser.load_replace_file(path) elsif filename == 'replace_after.txt' ## XXX load_replace_file(path) elsif ['satori_conf.txt', 'satori_conf.sat'].include?(filename) #pass else buf << path end end end for path in buf @parser.read(path) end @talk, @word = @parser.get_dict() end def reload finalize() reset() load() end def reset_random_talk_interval interval = get_integer('喋り間隔', :error => 'ignore') if interval.nil? or interval.zero? @random_talk = -1 return end rate = get_integer('喋り間隔誤差', :error => 'ignore') if rate.nil? rate = 0.1 else rate = ([[rate, 1].max, 100].min / 100.0) end diff = (interval * rate).to_i @random_talk = Array(interval - diff..interval + diff).sample end def add_timer(name, value) count = to_integer(value) if count.nil? or count.zero? if @timer.include?(name) @timer.delete(name) end else @timer[name] = count end end def delete_all_timers @timer = {} end def get_script(name, head: '\1', tail: '\e') if @reset_surface @current_reset_surface = [true, true] else @current_reset_surface = [false, false] end script = get(name, :default => nil) if not script.nil? and not script.empty? and script != "\\n" ##Logging::Logging.debug('make("' + script.encode('utf-8', :invalid => :replace, :undef => :replace) + '")') return make([head, script, tail].join('')) end return nil end def get_url_list(name) @url_list[name] = [] list_ = @talk[name] return nil if list_.nil? url_list = '' for i in (list_.length-1).step(-1, -1) nodelist = list_[i] title = '' j = 0 while j < nodelist.length node = nodelist[j] j += 1 if node[0] == NODE_TEXT if node[1] == ["\\n"] break else title = [title, node[1].join('')].join('') end else #pass end end next if title.empty? if title == '-' unless url_list.empty? url_list = [url_list, 2.chr].join('') end url_list = [url_list, title, 1.chr].join('') next end url = '' while j < nodelist.length node = nodelist[j] j += 1 if node[0] == NODE_TEXT if node[1] == ["\\n"] break else url = [url, node[1].join('')].join('') end else #pass end end next if url.empty? bannar = '' while j < nodelist.length node = nodelist[j] j += 1 if node[0] == NODE_TEXT if node[1] == ["\\n"] break else bannar = [bannar, node[1].join('')].join('') end else #pass end end if nodelist[j..-1] script = nodelist[j..-1] else script = nil end @url_list[name] << [title, url, bannar, script] unless url_list.empty? url_list = [url_list, 2.chr].join('') end url_list = [url_list, title, 1.chr, url, 1.chr, bannar].join('') end if url_list.empty? url_list = nil end return url_list end def get_url_script(title, url) script = nil if @reset_surface @current_reset_surface = [true, true] else @current_reset_surface = [false, false] end for key in @url_list.keys for item in @url_list[key] if item[0] == title and item[1] == url unless item[3].nil? script = expand(item[3]) if not script.nil? and not script.empty? and script != "\\n" script = make(['\1', script, '\e'].join('')) break end end end end end return script end Redundant_tags = [ [Regexp.new('(\\\\[01hu])+(\\\\[01hu])'), 2], #lambda {|m| m[2] }], [Regexp.new('(\\n)+(\\e|$)'), 2], #lambda {|m| m[2] }], [Regexp.new('(\\e)+'), 1], #lambda {|m| m[1] }], ] Re_newline = Regexp.new('((\\n)*)(\\e)') Re_0 = Regexp.new('\\\\[0h]') Re_1 = Regexp.new('\\\\[1u]') Re_wait_after = Regexp.new('\A、|。|,|.') Re_wait_before = Regexp.new('\A\\\\[01hunce]') Re_tag = Regexp.new('\A\\\\[ehunjcxtqzy*v0123456789fmia!&+\-\-\-]|' \ '\\\\[sb][0-9]?|\\w[0-9]|\\_[wqslvVbe+cumna]|' \ '\\__[ct]|\\URL') def make(script) return nil if script.nil? # make anchor buf = [] i = 0 while true match = Re_tag.match(script, i) if not match.nil? and match.begin(0) == i start = match.begin(0) end_ = match.end(0) if start > 0 buf << @parser.anchor_filter.apply(script[i..start-1]) end buf << script[start..end_-1] i = end_ else buf << @parser.anchor_filter.apply(script[i..-1]) break end end script = buf.join('') # apply replace_after.txt script = @replace_filter.apply(script) # remove redundant tags for pattern, replace in Redundant_tags #script, count = pattern.subn(replace, script) match = pattern.match(script) script = script.sub(pattern, match[replace]) unless match.nil? end # remove redundant newline tags match = Re_newline.match(script) unless match.nil? tag = match[3] if tag == '\e' if match.begin(0).zero? script = script[match.end(0)..-1] else script = [script[0..match.begin(0)-1], tag, script[match.end(0)..-1]].join('') end else fail RuntimeError('should not reach here') end end # insert newline i = 1 while true match = Re_0.match(script, i) unless match.nil? end_ = match.end(0) match = Re_0.match(script, end_) unless match.nil? start = match.begin(0) if start < @newline.length or \ script[start - @newline.length..start-1] != @newline script = (script[0..match.end(0)-1] + @newline_script + script[match.end(0)..-1]) end else break end i = end_ else break end end i = 1 while true match = Re_1.match(script, i) unless match.nil? end_ = match.end(0) match = Re_1.match(script, end_) unless match.nil? start = match.begin(0) if start < @newline.length or \ script[start - @newline.length..start-1] != @newline script = (script[0..match.end(0)-1] + @newline_script + script[match.end(0)..-1]) end else break end i = end_ else break end end # insert waits buf = [] n = 0 i, j = 0, script.length while i < j match = Re_wait_after.match(script, i) if not match.nil? and match.begin(0).zero? # FIXME buf << match[0] buf.concat(make_wait(n)) n = 0 i = match.end(0) next end match = Re_wait_before.match(script, i) if not match.nil? and match.begin(0).zero? # FIXME buf.concat(make_wait(n)) buf << match[0] n = 0 i = match.end(0) next end if script[i] == '[' pos = script.index(']', i) if pos > i buf << script[i..pos] i = (pos + 1) next end end match = Re_tag.match(script, i) if not match.nil? and match.begin(0).zero? # FIXME buf << script[i..match.end(0)-1] i = match.end(0) else buf << script[i] n += 3 i += 1 end end return buf.join('') end def make_wait(ms) buf = [] n = ((ms + 25) * @wait_percent / 100 / 50).to_i while n > 0 buf << '\w' + [n, 9].min.to_s n -= 9 end return buf end def get(name, default: '') result = @talk[name] return default if result.nil? return expand(result.sample) end def expand(nodelist, caller_history: nil, side: 1) return '' if nodelist.nil? buf = [] history = [] talk = false newline = [nil, nil] for node in nodelist case node[0] when NODE_REF unless caller_history.nil? value = get_reference(node[1], caller_history, side) else value = get_reference(node[1], history, side) end unless value.nil? or value.empty? talk = true buf << value history << value unless caller_history.nil? caller_history << value end end when NODE_CALL function = node[1] args = node[2] value = call_function( function, args, (not caller_history.nil?) ? caller_history : history, side) unless value.nil? or value.empty? talk = true buf << value history << value unless caller_history.nil? caller_history << value end end when NODE_SAORI if @variable['SAORI引数の計算'] == '無効' expand_only = true else expand_only = false end unless caller_history.nil? value = call_saori( node[1], calc_args(node[2], caller_history, :expand_only => expand_only), caller_history, side) else value = call_saori( node[1], calc_args(node[2], history, :expand_only => expand_only), history, side) end unless value.nil? or value.empty? talk = true buf << value history << value unless caller_history.nil? caller_history << value end end when NODE_TEXT buf.concat([node[1]]) talk = true when NODE_SIDE if talk newline[side] = @newline else newline[side] = nil end talk = false if side.zero? side = 1 else side = 0 end buf << "\\" + side.to_s if @current_reset_surface[side] buf << '\s[' + @default_surface[side].to_s + ']' @current_reset_surface[side] = false end unless newline[side].nil? buf << newline[side].to_s end when NODE_ASSIGNMENT value = expand(node[2]) result = assign(expand(node[1]), value) unless result.nil? buf << result end when NODE_JUMP if node[2].nil? or \ not ['0', '0'].include?(expand(node[2])) target = expand(node[1]) if target == 'OnTalk' @reference[1] = get_reserved_talk() if @reference[1] @reference[0] = to_zenkaku(1) else @reference[0] = to_zenkaku(0) end end script = get(target, :default => nil) if not script.nil? and not script.empty? and script != "\\n" buf << ['\1', script].join('') break end end when NODE_SEARCH ## FIXME buf << '' when NODE_CHOICE label = expand(node[1]) if node[2].nil? id_ = label else id_ = expand(node[2]) end buf << '\q[' + label + ',' + id_ + ']\n' talk = true when NODE_OR_EXPR break_flag = false for i in 1..node.length-1 unless ['0', '0'].include?(expand(node[i])) buf << '1' break_flag = true break end end unless break_flag buf << '0' end when NODE_AND_EXPR break_flag = false for i in 1..node.length-1 if ['0', '0'].include?(expand(node[i])) buf << '0' break_flag = true break end end unless break_flag buf << '1' end when NODE_COMP_EXPR operand1 = expand(node[1]) operand2 = expand(node[3]) n1 = to_integer(operand1) n2 = to_integer(operand2) if not (n1.nil? or n2.nil?) operand1 = n1 operand2 = n2 elsif n1.nil? and n2.nil? and \ ['<', '>', '<=', '>='].include?(node[2]) operand1 = operand1.length operand2 = operand2.length end case node[2] when '==' buf << to_zenkaku(operand1 == operand2 ? 1 : 0) when '!=' buf << to_zenkaku(operand1 != operand2 ? 1 : 0) when '<' buf << to_zenkaku(operand1 < operand2 ? 1 : 0) when '>' buf << to_zenkaku(operand1 > operand2 ? 1 : 0) when '<=' buf << to_zenkaku(operand1 <= operand2 ? 1 : 0) when '>=' buf << to_zenkaku(operand1 >= operand2 ? 1 : 0) else fail RuntimeError('should not reach here') end when NODE_ADD_EXPR value_str = expand(node[1]) value = to_integer(value_str) for i in 2.step(node.length-1, 2) operand_str = expand(node[i + 1]) operand = to_integer(operand_str) if node[i] == '-' if value.nil? or operand.nil? value_str = value_str.gsub(operand_str, '') value = nil else value -= operand value_str = to_zenkaku(value) end next end value = 0 if value.nil? operand = 0 if operand.nil? if node[i] == '+' value += operand else fail RuntimeError('should not reach here') end end if value.nil? buf << value_str else buf << to_zenkaku(value) end when NODE_MUL_EXPR value_str = expand(node[1]) value = to_integer(value_str) for i in 2.step(node.length-1, 2) operand_str = expand(node[i + 1]) operand = to_integer(operand_str) if node[i] == '*' if value.nil? and operand.nil? value_str = '' value = nil elsif value.nil? value_str *= operand value = nil elsif operand.nil? value_str *= operand_str value = nil else value *= operand end next end value = 0 if value.nil? operand = 0 if operand.nil? if node[i] == '/' value = (value / operand).to_i elsif node[i] == '%' value = (value % operand) else fail RuntimeError('should not reach here') end end if value.nil? buf << value_str else buf << to_zenkaku(value) end when NODE_POW_EXPR value = to_integer(expand(node[1])) value = 0 if value.nil? for i in 2..node.length-1 operand = to_integer(expand(node[i])) operand = 0 if operand.nil? value **= operand end buf << to_zenkaku(value) when NODE_UNARY_EXPR value = expand(node[2]) if node[1] == '-' value = to_integer(value) value = 0 if value.nil? value = -value elsif node[1] == '!' value = (['0', '0'].include?(value) ? 1 : 0) else fail RuntimeError('should not reach here') end buf << to_zenkaku(value) else fail RuntimeError('should not reach here') end end return buf.join('').strip() end Re_random = Regexp.new('\A乱数((-|−|+|[-+])?([[:digit:]])+)~((-|−|+|[-+])?([[:digit:]])+)') Re_is_empty = Regexp.new('\A(変数|文|単語群)「(.*)」の存在') Re_n_reserved = Regexp.new('\A次から(([[:digit:]])+)回目のトーク') Re_is_reserved = Regexp.new('\Aトーク「(.*)」の予約有無') def get_reference(nodelist, history, side) key = expand(nodelist[1..-2], :caller_history => history) if not key.nil? and ['R', 'R'].include?(key[0]) n = to_integer(key[1..-1]) if not n.nil? and 0 <= n and n < @reference.length ## FIXME return '' if @reference[n].nil? return @reference[n].to_s end elsif key and ['S', 'S'].include?(key[0]) n = to_integer(key[1..-1]) unless n.nil? if @saori_value.include?(key) return '' if @saori_value[key].nil? return @saori_value[key].to_s else return '' end end elsif not key.nil? and ['H', 'H'].include?(key[0]) ##Logging::Logging.debug(['["', history.join('", "'), '"]'].join('')) n = to_integer(key[1..-1]) if not n.nil? and 1 <= n and n < history.length + 1 ## FIXME return history[n-1] end end n = to_integer(key) unless n.nil? return '\s[' + (n + @add_to_surface[side]).to_s + ']' end if @word.include?(key) return expand(@word[key].sample, :caller_history => history, :side => side) elsif @talk.include?(key) @reference = [nil] * 8 return expand(@talk[key].sample, :side => 1) elsif @variable.include?(key) return @variable[key] elsif @timer.include?(key) return to_zenkaku(@timer[key]) elsif is_reserved(key) return get_reserved(key) elsif not Re_random.match(key).nil? match = Re_random.match(key) i = to_integer(match[1]) j = to_integer(match[4]) if i < j return to_zenkaku(Array(i..j).sample) else return to_zenkaku(Array(j..i).sample) end elsif not Re_n_reserved.match(key).nil? match = Re_n_reserved.match(key) number = to_integer(match[1]) for key in @reserved_talk.keys if @reserved_talk[key] == number return key end end return '' elsif not Re_is_reserved.match(key).nil? match = Re_is_reserved.match(key) name = match[1] if @reserved_talk.include?(name) return to_zenkaku(1) else return to_zenkaku(0) end elsif not Re_is_empty.match(key).nil? match = Re_is_empty.match(key) type_ = match[1] name = match[2] case type_ when '変数' if @variable.include?(name) return to_zenkaku(1) else return to_zenkaku(0) end when '文' if @talk.include?(name) return to_zenkaku(1) else return to_zenkaku(0) end when '単語群' if @word.include?(name) return to_zenkaku(1) else return to_zenkaku(0) end end end return '(' + key.to_s + ')' end def calc_args(args, history, expand_only: false) buf = [] for i in 0..args.length-1 value = expand(args[i], :caller_history => history) line = value ## FIXME if expand_only or line.empty? buf << value elsif ['-', '-', '−', '+', '+'].include?(line[0]) and line.length == 1 # XXX buf << value elsif NUMBER.include?(line[0]) begin ## FIXME line, expr = @parser.get_add_expr(line) result = to_integer(expand(expr, :caller_history => history)).to_s if result.nil? buf << value else buf << result end rescue buf << value end else buf << value end end return buf end def call_function(name, args, history, side) if name == '単語の追加' #pass ## FIXME elsif name == 'call' ref = expand(args[0], :caller_history => history) args = args[1..-1] for i in 0..args.length-1 name = ['A', to_zenkaku(i)].join('') @variable[name] = expand(args[i], :caller_history => history) end result = get_reference([[NODE_TEXT, '('], [NODE_TEXT, ref], [NODE_TEXT, ')']], history, side) for i in 0..args.length-1 name = ['A', to_zenkaku(i)].join('') @variable.delete(name) end return result elsif name == 'remember' number = to_integer(expand(args[0], :caller_history => history)) if number > 0 and number <= 64 and @script_history[-number] return @script_history[-number] else return '' end elsif name == 'loop' ref = expand(args[0], :caller_history => history) if args.length < 2 return '' elsif args.length == 2 start = 1 end_ = to_integer(expand(args[1], :caller_history => history)) + 1 step = 1 elsif args.length == 3 start = to_integer(expand(args[1], :caller_history => history)) end_ = to_integer(expand(args[2], :caller_history => history)) + 1 step = 1 elsif args.length >= 4 start = to_integer(expand(args[1], :caller_history => history)) end_ = to_integer(expand(args[2], :caller_history => history)) step = to_integer(expand(args[3], :caller_history => history)) if step > 0 end_ = (end_ + 1) elsif step < 0 end_ = (end_ - 1) else return '' # infinite loop end end name = [ref, 'カウンタ'].join('') buf = [] for i in start.step(end_-1, step) @variable[name] = to_zenkaku(i) buf << get_reference([[NODE_TEXT, '('], [NODE_TEXT, ref], [NODE_TEXT, ')']], history, side) end return buf.join('') elsif name == 'sync' ## FIXME #pass elsif name == 'set' if args.nil? name = '' else name = expand(args[0], :caller_history => history) end if args.length < 2 value = '' else value = expand(args[1], :caller_history => history) end unless name.empty? if value.empty? if @variable.include?(name) @variable.delete(name) end else @variable[name] = value end end return '' elsif name == 'nop' for i in 0..args.length-1 expand(args[i], :caller_history => history) end #pass elsif name == '合成単語群' ## FIXME: not tested words = [] for i in 0..args.length-1 ## FIXME name = expand(args[i], :caller_history => history) word = @word[name] words.concat(word) unless word.nil? end unless words.empty? return expand(words.sample) end elsif name.end_with?('の数') if ['R', 'R'].include?(name[0]) return @reference.length elsif ['A', 'A'].include?(name[0]) # len(args) #pass ## FIXME elsif ['S', 'S'].include?(name[0]) ##return @saori_value.length #pass ## FIXME #elif ['H', 'H'].include?(name[0]) #return history.length #elif ['C', 'C'].include?(name[0]) #return count.length else ## FIXME #pass end elsif name == 'when' fail "assert" unless args.length > 1 condition = expand(args[0], :caller_history => history) if ['0', '0'].include?(condition) if args.length > 2 return expand(args[2], :caller_history => history) else return '' end else return expand(args[1], :caller_history => history) end elsif name == 'times' ## FIXME print('TIMES: ', args.length, " ", args) #pass elsif name == 'while' ## FIXME print('WHILE :', args.length, " ", args) #pass elsif name == 'for' ## FIXME print('FOR :', args.length, " ", args) #pass else fail RuntimeError('should not reach here') end return '' end def call_saori(name, args, history, side) return '' end def get_runtime return (Time.new - @time_start).to_i end def get_integer(name, error: 'strict') value = @variable[name] return nil if value.nil? return to_integer(value, :error => error) end NUMBER = { '0' => '0', '0' => '0', '1' => '1', '1' => '1', '2' => '2', '2' => '2', '3' => '3', '3' => '3', '4' => '4', '4' => '4', '5' => '5', '5' => '5', '6' => '6', '6' => '6', '7' => '7', '7' => '7', '8' => '8', '8' => '8', '9' => '9', '9' => '9', '+' => '+', '+' => '+', '−' => '-', '-' => '-', '-' => '-', } def to_integer(line, error: 'strict') buf = [] for char in line.chars if NUMBER.include?(char) buf << NUMBER[char] else if ['.', '.'].include?(char) # XXX buf << '.' elsif error == 'strict' return nil end end end begin return Integer(buf.join('')) rescue #except ValueError: if buf.include?('.') # XXX return Integer(Float(buf.join(''))) end return nil end end def is_number(line) return (not to_integer(line).nil?) end ZENKAKU = { '0' => '0', '1' => '1', '2' => '2', '3' => '3', '4' => '4', '5' => '5', '6' => '6', '7' => '7', '8' => '8', '9' => '9', '+' => '+', '-' => '−', } def to_zenkaku(s) buf = s.to_s.chars for i in 0..buf.length-1 if ZENKAKU.include?(buf[i]) buf[i] = ZENKAKU[buf[i]] end end return buf.join('') end RESERVED = [ '現在年', '現在月', '現在日', '現在曜日', '現在時', '現在分', '現在秒', '起動時', '起動分', '起動秒', '累計時', '累計分', '累計秒', 'OS起動時', 'OS起動分', 'OS起動秒', '単純起動分', '単純起動秒', '単純累計分', '単純累計秒', '単純OS起動分', '単純OS起動秒', '最終トークからの経過秒', 'サーフェス0', 'サーフェス1', '選択ID', '選択ラベル', '選択番号', '予約トーク数', '起動回数', 'Sender', 'Event', 'Charset', 'Reference0', 'Reference1', 'Reference2', 'Reference3', 'Reference4', 'Reference5', 'Reference6', 'Reference7', 'countTalk', 'countNoNameTalk', 'countEventTalk', 'countOtherTalk', 'countWords', 'countWord', 'countVariable', 'countAnchor', 'countParenthesis', 'countParentheres', 'countLine', ] def is_reserved(s) return RESERVED.include?(s) end DAYOFWEEK = ['月', '火', '水', '木', '金', '土', '日'] def get_reserved(s) now = Time.now case s when '現在年' return to_zenkaku(now.year) when '現在月' return to_zenkaku(now.month) when '現在日' return to_zenkaku(now.day) when '現在時' return to_zenkaku(now.hour) when '現在分' return to_zenkaku(now.min) when '現在秒' return to_zenkaku(now.sec) when '現在曜日' return DAYOFWEEK[now.wday] end runtime = get_runtime() case s when '起動時' return to_zenkaku((runtime / 3600).to_i) when '起動分' return to_zenkaku((runtime / 60).to_i % 60) when '起動秒' return to_zenkaku(runtime % 60) when '単純起動分' return to_zenkaku((runtime / 60).to_i) when '単純起動秒' return to_zenkaku(runtime) end accumulative_runtime = @runtime + get_runtime() case s when '累計時' return to_zenkaku((accumulative_runtime / 3600).to_i) when '累計分' return to_zenkaku((accumulative_runtime / 60).to_i % 60) when '累計秒' return to_zenkaku(accumulative_runtime % 60) when '単純累計分' return to_zenkaku((accumulative_runtime / 60).to_i) when '単純累計秒' return to_zenkaku(accumulative_runtime) when '起動回数' return to_zenkaku(@runcount) when '最終トークからの経過秒' return to_zenkaku(@silent_time) when 'サーフェス0' return @current_surface[0].to_i when 'サーフェス1' return @current_surface[1].to_i when '選択ID' return '' if self.choice_id.nil? return @choice_id when '選択ラベル' return '' if @choice_label.nil? return @choice_label when '選択番号' return '' if @choice_number.nil? return to_zenkaku(@choice_number) when '予約トーク数' return to_zenkaku(@reserved_talk.length) when 'Sender' return 'ninix' when 'Event' return @event when 'Charset' return 'UTF-8' when /\AReference/ n = s[9..-1].to_i return '' if @reference[n].nil? return @reference[n].to_s when /\Acount/ return to_zenkaku(@parser.get_count(s[5..-1])) end return '?'.encode('utf-8', :invalid => :replace, :undef => :replace) end end class Shiori < SATORI def initialize(dll_name) @dll_name = dll_name @saori = nil @saori_function = {} end def use_saori(saori) @saori = saori end def load(dir: nil) satori_init(:satori_dir => dir) @saori_library = SatoriSaoriLibrary.new(@saori, self) super() return 1 end def load_config_file(path) parser = Parser.new() parser.read(path) talk, word = parser.get_dict() for nodelist in (talk.include?('初期化') ? talk['初期化'] : []) expand(nodelist) end @saori_function = {} for nodelist in (word.include?('SAORI') ? word['SAORI'] : []) if nodelist[0][0] == NODE_TEXT list_ = nodelist[0][1].join('').split(',', -1) if list_.length >= 2 and not list_[0].nil? and not list_[1].nil? head, tail = File.split(list_[1]) saori_dir = File.join(@satori_dir, head) result = @saori_library.load(list_[1], saori_dir) unless result.zero? @saori_function[list_[0]] = list_[1..-1] else @saori_function[list_[0]] = nil ##Logging::Logging.error('satori.py: cannot load ' + list_[1].to_s) end end end end @parser.set_saori(@saori_function.keys()) end def reload finalize() reset() load(@satori_dir) end def unload finalize @saori_library.unload() end def find(top_dir, dll_name) result = 0 unless Satori.list_dict(top_dir).empty? result = 100 end return result end def show_description Logging::Logging.info( "Shiori: SATORI compatible module for ninix\n" \ " Copyright (C) 2002 by Tamito KAJIYAMA\n" \ " Copyright (C) 2002, 2003 by MATSUMURA Namihiko\n" \ " Copyright (C) 2002-2019 by Shyouzou Sugitani\n" \ " Copyright (C) 2003, 2004 by Shun-ichi TAHARA") end def request(req_string) if @folder_change change_folder() @folder_change = false end header = req_string.encode('UTF-8', :invalid => :replace, :undef => :replace).split(/\r?\n/, 0) req_header = {} line = header.shift unless line.nil? line = line.strip() req_list = line.split(nil, -1) if req_list.length >= 2 command = req_list[0].strip() protocol = req_list[1].strip() end for line in header line = line.strip() next if line.empty? next unless line.include?(':') key, value = line.split(':', 2) key.strip! value.strip! begin value = Integer(value) rescue value = value.to_s end req_header[key] = value end end result = '' to = nil if req_header.include?('ID') if req_header['ID'] == 'dms' result = getdms() elsif req_header['ID'] == 'OnAITalk' result = getaistringrandom() elsif ["\\ms", "\\mz", "\\ml", "\\mc", "\\mh", \ "\\mt", "\\me", "\\mp", "\\m?"].include?(req_header['ID']) result = getword(req_header['ID']) elsif req_header['ID'] == 'otherghostname' ## FIXME ##otherghost = [] ##for n in 0..127 ## if req_header.include?(['Reference', n.to_s].join('')) ## otherghost << req_header[['Reference', ## n.to_s].join('')].join('')) ## end ##end ##result = self.otherghostname(otherghost) #pass elsif req_header['ID'] == 'OnTeach' if req_header.include?('Reference0') teach(req_header['Reference0']) end else result = getstring(req_header['ID']) if result.nil? ref = [] for n in 0..7 if req_header.include?(['Reference', n.to_s].join('')) ref << req_header[ ['Reference', n.to_s].join('')] else ref << nil end end ref0, ref1, ref2, ref3, ref4, ref5, ref6, ref7 = ref result = get_event_response( req_header['ID'], :ref0 => ref0, :ref1 => ref1, :ref2 => ref2, :ref3 => ref3, :ref4 => ref4, :ref5 => ref5, :ref6 => ref6, :ref7 => ref7) end end if result.nil? result = '' end to = nil ##communicate_to() ## FIXME end unless result.empty? @silent_time = 0 end result = ("SHIORI/3.0 200 OK\r\n" \ "Sender: Satori\r\n" \ "Charset: UTF-8\r\n" \ "Value: " + result.to_s + "\r\n") unless to.nil? result = [result, "Reference0: " + to.to_s + "\r\n"].join('') end result = [result, "\r\n"].join('') return result.encode('UTF-8', :invalid => :replace, :undef => :replace) end def call_saori(name, args, history, side) if not @saori_function.include?(name) or \ @saori_function[name].nil? return '' end saori_statuscode = '' saori_header = [] saori_value = {} saori_protocol = '' req = ("EXECUTE SAORI/1.0\r\n" \ "Sender: Satori\r\n" \ "SecurityLevel: local\r\n" \ "Charset: Shift_JIS\r\n") ## XXX default_args = @saori_function[name][1..-1] n = default_args.length for i in 0..default_args.length-1 req = [req, "Argument" + i.to_s + ": " + default_args[i].to_s + "\r\n"].join('') end for i in 0..args.length-1 argument = args[i] unless argument.nil? req = [req, "Argument#{(i + n)}: #{argument}\r\n"].join('') end end req = [req, "\r\n"].join('') response = @saori_library.request( @saori_function[name][0], req.encode('CP932', :invalid => :replace, :undef => :replace)) header = response.split(/\r?\n/, 0) unless header.empty? line = header.shift line = line.force_encoding('CP932').encode("UTF-8", :invalid => :replace, :undef => :replace).strip() if line.include?(' ') saori_protocol, saori_statuscode = line.split(' ', 2) saori_protocol.strip! saori_statuscode.strip! end for line in header line = line.force_encoding('CP932').encode("UTF-8", :invalid => :replace, :undef => :replace).strip() next if line.empty? next unless line.include?(':') key, value = line.split(':', 2) key = key.strip() unless key.empty? saori_header << key saori_value[key] = value.strip() end end end for key in saori_value.keys next unless key.start_with?('Value') begin i = Integer(key[5..-1]) rescue next end name = ['S', to_zenkaku(i)].join('') @saori_value[name] = saori_value[key] # overwrite end if saori_value.include?('Result') return saori_value['Result'] else return '' end end end class SatoriSaoriLibrary def initialize(saori, satori) @saori_list = {} @saori = saori @satori = satori end def load(name, top_dir) result = 0 if @saori and not @saori_list.include?(name) module_ = @saori.request(name) unless module_.nil? @saori_list[name] = module_ end end if @saori_list.include?(name) result = @saori_list[name].load(:dir => top_dir) end return result end def unload for key in @saori_list.keys() @saori_list[key].unload() end return nil end def request(name, req) result = '' # FIXME if not name.nil? and @saori_list.include?(name) result = @saori_list[name].request(req) end return result end end end ninix-aya-5.0.9/lib/ninix/dll/gomi.rb0000644000175000017500000000775413416507430015560 0ustar shyshy# -*- coding: utf-8 -*- # # gomi.rb - a gomi.dll compatible Saori module for ninix # Copyright (C) 2012-2019 by Shyouzou Sugitani # # This program is free software; you can redistribute it and/or modify it # under the terms of the GNU General Public License (version 2) as # published by the Free Software Foundation. It is distributed in the # hope that it will be useful, but WITHOUT ANY WARRANTY; without even the # implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR # PURPOSE. See the GNU General Public License for more details. # require "optparse" require "gio2" require_relative "../dll" module Gomi class Saori < DLL::SAORI def initialize if ENV.include?('XDG_DATA_HOME') @xdg_data_home = ENV['XDG_DATA_HOME'] else @xdg_data_home = File.join(ENV['HOME'], '.local' , 'share') end @home_trash = File.join(@xdg_data_home, 'Trash') super() end def setup @parser = OptionParser.new begin @notify = Gio::DBusProxy.new( Gio::BusType::SESSION, 0, nil, 'org.gnome.Nautilus', '/org/gnome/Nautilus', 'org.gnome.Nautilus.FileOperations', nil) return 1 rescue @notify = nil return 0 end end def get_volume_trash vm = Gio::VolumeMonitor.get() result = [] for m in vm.mounts mp = m.default_location.path next if mp.nil? volume_trash = File.join(mp, '.Trash', Process::UID.eid.to_s) unless File.exist?(volume_trash) volume_trash = File.join(mp, '.Trash-' + Process::UID.eid.to_s) end next unless File.exist?(volume_trash) result << volume_trash end return result end def get_dir_size(dir_name) file_count = 0 dir_size = 0 for file in Dir.glob(File.join(dir_name, '**', '*')) file_count += 1 if File.file?(file) dir_size += File.size(file) end end return [file_count, dir_size] end def empty_trash(path) Dir.foreach(File.join(path, 'info')) { |info| next if info == '.' or info == '..' trash = info[0..-'.trashinfo'.length-1] filepath = File.join(path, 'files', trash) infopath = File.join(path, 'info', info) if File.file?(filepath) or File.symlink?(filepath) File.delete(filepath) File.delete(infopath) elsif File.directory?(filepath) FileUtils.rm_rf(filepath) File.delete(infopath) end } end def execute(argument) return RESPONSE[400] if argument.nil? or argument.empty? args = @parser.getopts( argument[0].split(nil, 0), 'enVafqsvw:', 'empty', 'number-of-items', 'version', 'asynchronous', 'force', 'quiet', 'silent', 'verbose', 'hwnd:') return RESPONSE[400] if @notify.nil? if args['number-of-items'] or args['n'] file_count, dir_size = get_dir_size(@home_trash) for volume_trash in get_volume_trash() count, size = get_dir_size(volume_trash) file_count += count dir_size += size end return ["SAORI/1.0 200 OK\r\n", "Result: ", file_count.to_s.encode('ascii', :invalid => :replace, :undef => :replace), "\r\n", "Reference0: ", dir_size.to_s.encode('ascii', :invalid => :replace, :undef => :replace), "\r\n\r\n"].join("") elsif args['empty'] or args['e'] if args['force'] or args['f'] empty_trash(@home_trash) for volume_trash in get_volume_trash() empty_trash(volume_trash) end else result = @notify.call_sync( 'EmptyTrash', nil, ## GLib.Variant('()', ()), Gio::DBusCallFlags::NONE, -1, nil) end return ["SAORI/1.0 200 OK\r\n", "Result: ", "1", # FIXME "\r\n\r\n"].join("") end end end end ninix-aya-5.0.9/lib/ninix/dll/mciaudio.rb0000644000175000017500000000626513416507430016413 0ustar shyshy# -*- coding: utf-8 -*- # # mciaudio.rb - a MCIAUDIO compatible Saori module for ninix # Copyright (C) 2002-2019 by Shyouzou Sugitani # Copyright (C) 2002, 2003 by MATSUMURA Namihiko # # This program is free software; you can redistribute it and/or modify it # under the terms of the GNU General Public License (version 2) as # published by the Free Software Foundation. It is distributed in the # hope that it will be useful, but WITHOUT ANY WARRANTY; without even the # implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR # PURPOSE. See the GNU General Public License for more details. # require "uri" begin require "gst" rescue LoadError Gst = nil end require_relative "../home" require_relative "../dll" require_relative "../logging" module Mciaudio class Saori < DLL::SAORI def initialize super() @player = Gst::ElementFactory.make('playbin', 'player') fakesink = Gst::ElementFactory.make('fakesink', 'fakesink') @player.set_property('video-sink', fakesink) bus = @player.bus bus.add_watch do |bus, message| on_message(bus, message) true end @filepath = nil @__sakura = nil end def need_ghost_backdoor(sakura) @__sakura = sakura end def check_import unless @__sakura.nil? or Gst.nil? return 1 else return 0 end end def finalize @player.set_state(Gst::State::NULL) @player = nil @filepath = nil return 1 end def execute(argv) return RESPONSE[400] if argv.nil? argc = argv.length case argc when 1 fail "assert" if @player.nil? case argv[0] when 'stop' @player.set_state(Gst::State::NULL) when 'play' case @player.get_state(timeout=Gst::SECOND)[1] when Gst::State::PAUSED @player.set_state(Gst::State::PLAYING) return RESPONSE[204] when Gst::State::PLAYING @player.set_state(Gst::State::PAUSED) return RESPONSE[204] end if not @filepath.nil? and File.exist?(@filepath) @player.set_property( 'uri', 'file://' + URI.escape(@filepath)) @player.set_state(Gst::State::PLAYING) end end when 2 if argv[0] == 'load' @player.set_state(Gst::State::NULL) filename = Home.get_normalized_path(argv[1]) if File.absolute_path(filename) == filename return RESPONSE[400] end @filepath = File.join(@__sakura.get_prefix(), 'ghost/master', @dir, filename) end end return RESPONSE[204] end def on_message(bus, message) return if message.nil? # XXX: workaround for Gst Version < 0.11 case message.type when Gst::MessageType::EOS @player.set_state(Gst::State::NULL) when Gst::MessageType::ERROR @player.set_state(Gst::State::NULL) err, debug = message.parse_error() Logging::Logging.error("Error: #{err}, #{debug}") end end end end ninix-aya-5.0.9/lib/ninix/dll/saori_cpuid.rb0000644000175000017500000000445313416507430017117 0ustar shyshy# -*- coding: utf-8 -*- # # saori_cpuid.rb - a saori_cpuid compatible Saori module for ninix # Copyright (C) 2003-2019 by Shyouzou Sugitani # # This program is free software; you can redistribute it and/or modify it # under the terms of the GNU General Public License (version 2) as # published by the Free Software Foundation. It is distributed in the # hope that it will be useful, but WITHOUT ANY WARRANTY; without even the # implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR # PURPOSE. See the GNU General Public License for more details. # require_relative "../dll" module Saori_cpuid class Saori < DLL::SAORI ENTRY = { 'cpu.num' => ['1', '2', '3', '5', '7', '11', '13'], 'cpu.vender' => ['Inte1', 'AM0', 'VlA'], 'cpu.name' => ['Z8O'], 'cpu.ptype' => ['Lazy'], 'cpu.family' => ['780', 'Bentium', 'A+hlon'], 'cpu.model' => ['Unknown'], 'cpu.stepping' => ['Not Genuine'], 'cpu.mmx' => ['Ready', 'Not Ready'], 'cpu.sse' => ['Ready', 'Not Ready'], 'cpu.sse2' => ['Ready', 'Not Ready'], 'cpu.tdn' => ['Ready', 'Not Ready'], 'cpu.mmx+' => ['Ready', 'Not Ready'], 'cpu.tdn+' => ['Ready', 'Not Ready'], 'cpu.clock' => ['0', '1000000'], 'cpu.clockex' => ['0.001', '1.001'], 'mem.os' => ['100', '10', '44', '50', '77', '99'], 'mem.phyt' => ['0.1', '200000000'], 'mem.phya' => ['0.00000001'], 'mem.pagt' => ['1', '4'], 'mem.paga' => ['1', '4'], 'mem.virt' => ['0'], 'mem.vira' => ['0'], } ENTRY.default=[""] def execute(argument) return RESPONSE[400] if argument.nil? or argument.empty? return RESPONSE[204] if argument.length > 1 and argument[1].zero? value = case argument[0] when 'platform'; 'ninix-aya' when 'os.name'; RbConfig::CONFIG['host_os'] when 'os.version'; "" ## FIXME: not supported yet when 'os.build'; "" ## FIXME: not supported yet else ENTRY[argument[0]].sample ## FIXME: dummy end return RESPONSE[204] if value.empty? "SAORI/1.0 200 OK\r\nResult: #{value}\r\n\r\n".encode( @charset, :invalid => :replace, :undef => :replace) end end end ninix-aya-5.0.9/lib/ninix/dll/kawari.rb0000644000175000017500000017167113416507430016103 0ustar shyshy# -*- coding: utf-8 -*- # # kawari.rb - a "華和梨" compatible Shiori module for ninix # Copyright (C) 2001, 2002 by Tamito KAJIYAMA # Copyright (C) 2002, 2003 by MATSUMURA Namihiko # Copyright (C) 2002-2019 by Shyouzou Sugitani # Copyright (C) 2003 by Shun-ichi TAHARA # # This program is free software; you can redistribute it and/or modify it # under the terms of the GNU General Public License (version 2) as # published by the Free Software Foundation. It is distributed in the # hope that it will be useful, but WITHOUT ANY WARRANTY; without even the # implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR # PURPOSE. See the GNU General Public License for more details. # require 'base64' require_relative "../home" require_relative "../logging" module Kawari ### READER ### $charset = 'CP932' # default def self.read_dict(path) open(path, 'rb') do |f| lineno = 0 buf = [] for line in f lineno = lineno + 1 position = [lineno, path] if line.start_with?('!KAWA0000') line = Kawari.decrypt(line[9..-1]).force_encoding($charset).encode("UTF-8", :invalid => :replace, :undef => :replace) else line = line.force_encoding($charset).encode("UTF-8", :invalid => :replace, :undef => :replace) end if line.strip.empty? or \ line.start_with?('#') or \ line.start_with?(':crypt') or \ line.start_with?(':endcrypt') next end unless line.include?(':') Logging::Logging.debug('kawari.rb: syntax error at line ' + position[0].to_s + ' in ' + position[1].to_s) Logging::Logging.debug(line.strip()) next end entries, phrases = line.split(':', 2) entries.strip! phrases.strip! if entries.empty? Logging::Logging.debug('kawari.rb: syntax error at line ' + position[0].to_s + ' in ' + position[1].to_s) Logging::Logging.debug(line.strip()) next end next if phrases.empty? if entries == 'locale' unless Encoding.name_list.include?(phrases) Logging::Logging.error('kawari.rb: unsupported charset ' + phrases.to_s) else $charset = phrases end end buf << [entries, phrases, position] end return buf end end def self.decrypt(data) buf = [] for c in Base64.decode64(data).each_char buf << (c.ord ^ 0xcc).chr end return buf.join('') end def self.encrypt(data) buf = [] for c in data buf << (c.ord ^ 0xcc).chr end line = ['!KAWA0000', Base64.encode64(buf.join(''))].join('') return line.gsub("\n", '') end def self.create_dict(buf) rdict = {} # rules kdict = {} # keywords for entries, phrases, position in buf parsed_entries = Kawari.parse_entries(entries) parsed_phrases = Kawari.parse_phrases(phrases) if parsed_phrases.nil? Logging::Logging.debug('kawari.rb: syntax error at line ' + position[0].to_s + ' in ' + position[1].to_s + ':') Logging::Logging.debug(phrases.strip()) next end if entries.start_with?('[') Kawari.add_phrases(kdict, parsed_entries, parsed_phrases) next end for entry in parsed_entries Kawari.add_phrases(rdict, entry, parsed_phrases) end end return rdict, kdict end def self.add_phrases(dic, entry, phrases) unless dic.include?(entry) dic[entry] = [] end for phrase in phrases unless phrase.empty? phrase[0] = phrase[0].lstrip() phrase[-1] = phrase[-1].rstrip() end dic[entry] << phrase end end def self.parse_entries(data) if data.start_with?('[') and data.end_with?(']') entries = [] i = 0 j = data.length while i < j if data[i] == '"' i, text = Kawari.parse_quotes(data, i) entries << text else i += 1 end end else entries = data.split(',', 0).map {|s| s.strip } end return entries end Re_comma = Regexp.new('\A,') def self.parse_phrases(data) buf = [] i = 0 j = data.length while i < j if data[i] == ',' i += 1 end i, phrase = Kawari.parse(data, i, :stop_pattern => Re_comma) unless phrase.empty? buf << phrase end end return buf end def self.parse(data, start, stop_pattern: nil) buf = [] i = start j = data.length while i < j break unless stop_pattern.nil? or stop_pattern.match(data[i..-1]).nil? if data[i] == '"' i, text = Kawari.parse_quotes(data, i) buf << '"' + text + '"' elsif data[i..i + 1] == '${' i, text = Kawari.parse_reference(data, i) buf << text elsif data[i..i + 1] == '$(' i, text = Kawari.parse_inline_script(data, i) buf << text elsif data[i] == '$' buf << data[i] i += 1 elsif data[i] == ';' buf << data[i] i += 1 else i, text = Kawari.parse_text(data, i, :stop_pattern => stop_pattern) buf << text end end unless buf.empty? if Kawari.is_space(buf[0]) buf.delete_at(0) else buf[0] = buf[0].lstrip() end end unless buf.empty? if Kawari.is_space(buf[-1]) buf.delete_at(-1) else buf[-1] = buf[-1].rstrip() end end return i, buf end def self.parse_quotes(data, start) buf = [] i = (start + 1) j = data.length while i < j if data[i] == '"' i += 1 break elsif data[i] == '\\' i += 1 if i < j and data[i] == '"' buf << ['\\', data[i]].join('') i += 1 else buf << '\\' end else buf << data[i] i += 1 end end return i, buf.join('') end def self.parse_reference(data, start) i = start j = data.length while i < j if data[i] == '}' i += 1 break else i += 1 end end return i, data[start..i-1] end def self.parse_inline_script(data, start) buf = ['$'] i = (start + 1) j = data.length npar = 0 while i < j #begin specification bug work-around (1/3) if data[i] == ')' buf << data[i] i += 1 break end #end if data[i] == '"' i, text = Kawari.parse_quotes(data, i) buf << '"' + text.to_s + '"' elsif data[i..i + 1] == '${' i, text = Kawari.parse_reference(data, i) buf << text elsif data[i..i + 1] == '$(' i, text = Kawari.parse_inline_script(data, i) buf << text else if data[i] == '(' npar = npar + 1 elsif data[i] == ')' npar = (npar - 1) end buf << data[i] i += 1 end break if npar.zero? end return i, buf.join('') end def self.is_space(s) s.strip.empty? end def self.parse_text(data, start, stop_pattern: nil) condition = Kawari.is_space(data[start]) i = start j = data.length while i < j break unless stop_pattern.nil? or stop_pattern.match(data[i..-1]).nil? if ['$', '"'].include?(data[i]) break elsif Kawari.is_space(data[i]) != condition break elsif data[i] == ';' if i == start i += 1 end break else i += 1 end end return i, data[start..i-1] end def self.read_local_script(path) rdict = {} kdict = {} open(path, :encoding => $charset) do |f| while line = f.gets if line.start_with?('#') rdict[line.strip()] = [f.readline().strip()] end end end return rdict, kdict end ### KAWARI ### class Kawari7 attr_reader :rdictlist, :kdictlist MAXDEPTH = 30 def initialize(prefix, pathlist, rdictlist, kdictlist) kawari_init(prefix, pathlist, rdictlist, kdictlist) end def kawari_init(prefix, pathlist, rdictlist, kdictlist) @kis_commands = { # flow controls 'if' => 'exec_new_if', 'foreach' => 'exec_foreach', 'loop' => 'exec_loop', 'while' => 'exec_while', 'until' => 'exec_until', # dictionary operators 'adddict' => 'exec_adddict', 'array' => 'exec_array', 'clear' => 'exec_clear', 'enumerate' => 'exec_enumerate', 'set' => 'exec_set', 'load' => 'exec_load', 'save' => 'exec_save', 'savecrypt' => 'exec_savecrypt', 'textload' => 'exec_textload', 'size' => 'exec_size', 'get' => 'exec_get', # list operators 'unshift' => 'exec_unshift', 'shift' => 'exec_shift', 'push' => 'exec_push', 'pop' => 'exec_pop', # counter operators 'inc' => 'exec_inc', 'dec' => 'exec_dec', # expression evaluators 'expr' => 'exec_expr', 'test' => 'exec_test', '[' => 'exec_test', 'entry' => 'exec_entry', 'eval' => 'exec_eval', # utility functions 'NULL' => 'exec_null', '?' => 'exec_choice', 'date' => 'exec_date', 'rand' => 'exec_rand', 'echo' => 'exec_echo', 'escape' => 'exec_escape', 'tolower' => 'exec_tolower', 'toupper' => 'exec_toupper', 'pirocall' => 'exec_pirocall', 'split' => 'exec_split', 'urllist' => nil, 'chr' => 'exec_chr', 'help' => nil, 'ver' => nil, 'searchghost' => nil, 'saoriregist' => nil, 'saorierase' => nil, 'callsaori' => nil, 'callsaorix' => nil, } @prefix = prefix @pathlist = pathlist @rdictlist = rdictlist @kdictlist = kdictlist @system_entries = {} @expr_parser = ExprParser.new() #begin specification bug work-around (2/3) @expr_parser.kawari = self #end @otherghost = {} get_system_entry('OnLoad') end def finalize get_system_entry('OnUnload') end # SHIORI/1.0 API def getaistringrandom get('sentence') end def getaistringfromtargetword(word) get('sentence') # XXX end def getdms getword('dms') end def getword(word_type) for delimiter in ['.', '-'] name = ['compatible', delimiter, word_type].join('') script = get(name, :default => nil) return script.strip unless script.nil? end return '' end def getstring(name) get("resource.#{name}") end # SHIORI/2.2 API def get_event_response(event, ref0: nil, ref1: nil, ref2: nil, ref3: nil, ref4: nil, ref5: nil, ref6: nil, ref7: nil) ref = [ref0, ref1, ref2,ref3, ref4, ref5, ref6, ref7].map {|r| r.to_s unless r.nil? } for i in 0..7 unless ref[i].nil? value = ref[i] @system_entries['system.Reference' + i.to_s] = value @system_entries['system-Reference' + i.to_s] = value end end script = nil if event == 'OnCommunicate' @system_entries['system.Sender'] = ref[0] @system_entries['system-Sender'] = ref[0] @system_entries['system.Sender.Path'] = 'local' # (local/unknown/external) @system_entries['system-Sender.Path'] = 'local' # (local/unknown/external) unless @system_entries.include?('system.Age') @system_entries['system.Age'] = '0' @system_entries['system-Age'] = '0' end @system_entries['system.Sentence'] = ref[1] @system_entries['system-Sentence'] = ref[1] if @otherghost.include?(ref[0]) s0, s1 = @otherghost[ref[0]] @system_entries['system.Surface'] = [s0.to_s, s1.to_s].join(',') @system_entries['system-Surface'] = [s0.to_s, s1.to_s].join(',') end script = get_system_entry('OnResponse') if script.nil? for dic in @kdictlist for entry in dic.keys break_flag = false for word in entry unless ref[1].include?(word) break_flag = true break end end unless break_flag script = expand(dic[entry].sample) break end end end end if script.nil? script = get_system_entry('OnResponseUnknown') end unless script.nil? script = script.strip() end else for delimiter in ['.', '-'] name = ['event', delimiter, event].join('') script = get(name, :default => nil) unless script.nil? script = script.strip() break end end end return script end # SHIORI/2.4 API def teach(word) @system_entries['system.Sentence'] = word @system_entries['system-Sentence'] = word return get_system_entry('OnTeach') end def get_system_entry(entry) for delimiter in ['.', '-'] name = ['system', delimiter, entry].join('') script = get(name, :default => nil) unless script.nil? return script.strip() end end return nil end def otherghostname(ghost_list) ghosts = [] for ghost in ghost_list name, s0, s1 = ghost.split(1.chr, 3) ghosts << [name, s0, s1] @otherghost[name] = [s0, s1] end otherghost_name = [] for ghost in ghosts otherghost_name << ghost[0] end @system_entries['system.OtherGhost'] = otherghost_name @system_entries['system-OtherGhost'] = otherghost_name otherghost_ex = [] for ghost in ghosts otherghost_ex << ghost.join(1.chr) end @system_entries['system.OtherGhostEx'] = otherghost_ex @system_entries['system-OtherGhostEx'] = otherghost_ex unless ghosts.empty? get_system_entry('OnNotifyOther') end return '' end def communicate_to communicate = get_system_entry('communicate') unless communicate.nil? if communicate == 'stop' @system_entries['system.Age'] = '0' @system_entries['system-Age'] = '0' communicate = nil else if @system_entries.include?('system.Age') age = (@system_entries['system.Age'].to_i + 1) @system_entries['system.Age'] = age.to_s @system_entries['system-Age'] = age.to_s else @system_entries['system.Age'] = '0' @system_entries['system-Age'] = '0' end end end clear('system.communicate') return communicate end # internal def clear(name) Logging::Logging.debug('*** clear("' + name.to_s + '")') for dic in @rdictlist dic.delete(name) if dic.include?(name) end end def get_internal_dict(name) break_flag = false for dic in @rdictlist if dic.include?(name) break_flag = true break end end unless break_flag dic = @rdictlist[0] dic[name] = [] end return dic end def unshift(name, value) Logging::Logging.debug('*** unshift("' + name.to_s + '", "' + value.to_s + '")') dic = get_internal_dict(name) i, segments = Kawari.parse(value, 0) dic[name].insert(0, segments) end def shift(name) dic = get_internal_dict(name) value = expand(dic[name].shift) Logging::Logging.debug('*** shift("' + name.to_s + '") => "' + value.to_s + '"') return value end def push(name, value) Logging::Logging.debug('*** push("' + name.to_s + '", "' + value.to_s + '")') dic = get_internal_dict(name) i, segments = Kawari.parse(value, 0) dic[name] << segments end def pop(name) dic = get_internal_dict(name) value = expand(dic[name].pop()) Logging::Logging.debug('*** pop("' + name.to_s + '") => "' + value.to_s + '"') return value end def set(name, value) clear(name) push(name, value) end def get(name, context: nil, depth: 0, default: '') return '' if depth == MAXDEPTH if not name.empty? and name.start_with?('@') segments = context[name].sample else unless name.include?('&') selection = select_simple_phrase(name) else selection = select_compound_phrase(name) end if selection.nil? Logging::Logging.debug('${{' + name.to_s + '}} not found') return default end segments, context = selection end Logging::Logging.debug([name, '=>', segments].join('')) return expand(segments, :context => context, :depth => depth) end def parse_all(data, start: 0) i, segments = Kawari.parse(data, start) return expand(segments) end def parse_sub(data, start: 0, stop_pattern: nil) i, segments = Kawari.parse(data, start, :stop_pattern => stop_pattern) return i, expand(segments) end def expand(segments, context: nil, depth: 0) buf = [] references = [] i = 0 j = segments.length while i < j segment = segments[i] if segment.empty? #pass elsif segment.start_with?('${') and segment.end_with?('}') newname = segment[2..-2] if is_number(newname) begin segment = references[Integer(newname)] rescue #except IndexError: #pass end elsif is_system_entry(newname) if ['system.OtherGhost', 'system-OtherGhost', 'system.OtherGhostEx', 'system-OtherGhostEx'].include?(newname) segment_list = @system_entries[newname] unless segment_list.empty? segment = segment_list.sample else segment = '' end elsif newname == 'system.communicate' segment = get_system_entry('communicate') else segment = (@system_entries.include?(newname) ? @system_entries[newname] : segment) end else segment = get(newname, :context => context, :depth => depth + 1) end references << segment elsif segment.start_with?('$(') and segment.end_with?(')') i, segment = eval_inline_script(segments, i) elsif segment.start_with?('"') and segment.end_with?('"') segment = segment[1..-2].gsub('\\"', '"') end buf << segment i += 1 end return buf.join('') end def atoi(s) begin return Integer(s) rescue #except ValueError: return 0 end end def is_number(s) begin Integer(s) rescue #except ValueError: return false end return true end def is_system_entry(s) return (s.start_with?('system-') or s.start_with?('system.')) end def select_simple_phrase(name) n = 0 buf = [] for d in @rdictlist if d.include?(name) c = d[name] else c = [] end n += c.length buf << [c, d] end return nil if n.zero? n = rand(0..n-1) for c, d in buf m = c.length break if n < m n -= m end return c[n], d end def select_compound_phrase(name) buf = [] for name in name.split('&', 0).map {|s| s.strip } cp_list = [] for d in @rdictlist cp_list.concat(d.include?(name) ? d[name] : []) end buf << cp_list end buf = buf.map {|x| [x.length, x] } buf.sort! buf = buf.map {|x| x[1] } candidates = [] for item in buf.shift break_flag = false for cp_list in buf unless cp_list.include?(item) break_flag = true break end end unless break_flag candidates << item end end if candidates.empty? return nil end return candidates.sample, nil end def eval_inline_script(segments, i) # check old 'if' syntax if segments[i].start_with?('$(if ') and i + 1 < segments.length and \ segments[i + 1].start_with?('$(then ') if_block = segments[i][5..-2].strip() i += 1 then_block = segments[i][7..-2].strip() if i + 1 < segments.length and segments[i + 1].start_with?('$(else ') i += 1 else_block = segments[i][7..-2].strip() else else_block = '' end if i + 1 < segments.length and segments[i + 1] == '$(endif)' i += 1 else Logging::Logging.debug('kawari.rb: syntax error: $(endif) expected') return i, '' # syntax error end return i, exec_old_if(if_block, then_block, else_block) end # execute command(s) values = [] for command in split_commands(segments[i][2..-2]) argv = parse_argument(command) argv[0] = expand(argv[0]) if argv[0] == 'silent' if argv.length == 1 values = [] else Logging::Logging.debug( ['kawari.rb: syntax error:', segments[i]].join('')) end next end handler = @kis_commands[argv[0]] begin fail RuntimeError.new('invalid command') if handler.nil? values << method(handler).call(argv) rescue => message #except RuntimeError as message: Logging::Logging.debug("kawari.rb: #{message}: #{segments[i]}") end end result = values.join('') Logging::Logging.debug(['>>>', segments[i]].join('')) Logging::Logging.debug(['"', result, '"'].join('')) return i, result end def split_commands(data) i, segments = Kawari.parse(data, 0) # find multiple commands separated by semicolons buf = [] command = [] for segment in segments if segment == ';' unless command.empty? buf << command command = [] end else command << segment end end unless command.empty? buf << command end # strip white space before and after each command for command in buf if Kawari.is_space(command[0]) command.delete_at(0) end if Kawari.is_space(command[-1]) command.delete_at(-1) end end return buf end def parse_argument(segments) buf = [[]] for segment in segments if Kawari.is_space(segment) buf << [] else buf[-1] << segment end end return buf end def exec_new_if(argv) if argv.length == 3 return exec_if(argv[1], argv[2], nil) elsif argv.length == 4 return exec_if(argv[1], argv[2], argv[3]) else fail RuntimeError.new('syntax error') end end def exec_old_if(if_block, then_block, else_block) # convert [...] into $([...]) if not if_block.empty? and if_block.start_with?('[') and if_block.end_with?(']') if_block = ['$(', if_block, ')'].join('') end # parse arguments i, if_block = Kawari.parse(if_block, 0) i, then_block = Kawari.parse(then_block, 0) unless else_block.empty? i, else_block = Kawari.parse(else_block, 0) end result = exec_if(if_block, then_block, else_block) unless else_block.empty? Logging::Logging.debug( '>>> $(if ' + if_block.join('') + ')$(then ' + then_block.join('') + ')$(else ' + else_block.join('') + ')$(endif)') else Logging::Logging.debug( '>>> $(if ' + if_block.join('') + ')$(then ' + then_block.join('') + ')$(endif)') end Logging::Logging.debug(['"', result, '"'].join('')) return result end def exec_if(if_block, then_block, else_block) if not ['', '0', 'false', 'False'].include?(expand(if_block)) return expand(then_block) elsif not else_block.empty? return expand(else_block) end return '' end def exec_foreach(argv) if argv.length != 4 fail RuntimeError.new('syntax error') end temp = expand(argv[1]) name = expand(argv[2]) buf = [] for dic in @rdictlist if dic.include?(name) for segments in dic[name] set(temp, expand(segments)) buf << expand(argv[3]) end end end clear(temp) return buf.join('') end def exec_loop(argv) if argv.length != 3 fail RuntimeError.new('syntax error') end begin n = Integer(expand(argv[1])) rescue # except ValueError: raise RuntimeError.new('invalid argument') end buf = [] for _ in 0..n-1 buf << expand(argv[2]) end return buf.join('') end def exec_while(argv) if argv.length != 3 fail RuntimeError.new('syntax error') end buf = [] while not ['', '0', 'false', 'False'].include?(expand(argv[1])) buf << expand(argv[2]) end return buf.join('') end def exec_until(argv) if argv.length != 3 fail RuntimeError.new('syntax error') end buf = [] while ['', '0', 'false', 'False'].include?(expand(argv[1])) buf << expand(argv[2]) end return buf.join('') end def exec_set(argv) if argv.length != 3 fail RuntimeError.new('syntax error') end set(expand(argv[1]), expand(argv[2])) return '' end def exec_adddict(argv) if argv.length != 3 fail RuntimeError.new('syntax error') end push(expand(argv[1]), expand(argv[2])) return '' end def exec_array(argv) # XXX experimental if argv.length != 3 fail RuntimeError.new('syntax error') end name = expand(argv[1]) n = atoi(expand(argv[2])) for d in @rdictlist c = (d.include?(name) ? d[name] : []) if n < c.length return c[n].map {|s| expand(s) }.join('') end n -= c.length end fail RuntimeError.new('invalid argument') end def exec_clear(argv) if argv.length != 2 fail RuntimeError.new('syntax error') end clear(expand(argv[1])) return '' end def exec_enumerate(argv) if argv.length != 2 fail RuntimeError.new('syntax error') end name = expand(argv[1]) return enumerate(name).map {|s| expand(s) }.join(' ') end def enumerate(name) buf = [] for dic in @rdictlist if dic.include?(name) for segments in dic[name] buf << segments end end end return buf end def exec_size(argv) if argv.length != 2 fail RuntimeError.new('syntax error') end name = expand(argv[1]) n = 0 for d in @rdictlist c = (d.include?(name) ? d[name] : []) n += c.length end return n.to_s end def exec_get(argv) # XXX experimental if argv.length != 3 fail RuntimeError.new('syntax error') end name = expand(argv[1]) n = atoi(expand(argv[2])) for d in @rdictlist c = (d.include?(name) ? d[name] : []) if n < c.length return c[n].join('') end n -= c.length end fail RuntimeError.new('invalid argument') end def exec_unshift(argv) if argv.length != 3 fail RuntimeError.new('syntax error') end unshift(expand(argv[1]), expand(argv[2])) return '' end def exec_shift(argv) if argv.length != 2 fail RuntimeError.new('syntax error') end return shift(expand(argv[1])) end def exec_push(argv) if argv.length != 3 fail RuntimeError.new('syntax error') end push(expand(argv[1]), expand(argv[2])) return '' end def exec_pop(argv) if argv.length != 2 fail RuntimeError.new('syntax error') end return pop(expand(argv[1])) end def exec_pirocall(argv) fail RuntimeError.new('syntax error') if argv.length != 2 selection = select_simple_phrase(expand(argv[1])) return '' if selection.nil? return selection[0] end def exec_split(argv) fail RuntimeError.new('syntax error') if argv.length != 4 name = expand(argv[1]) word_list = expand(argv[2]).split(expand(argv[3]), 0) n = 0 for word in word_list n += 1 entry = (name.to_s + '.' + n.to_s) set(entry, word) end set([name, '.size'].join(''), n.to_s) return '' end def get_dict_path(path) path = Home.get_normalized_path(path) fail RuntimeError.new('invalid argument') if path.nil? return path if path.start_with?('/') return File.join(@prefix, path) end def exec_load(argv) if argv.length != 2 fail RuntimeError.new('syntax error') end path = get_dict_path(expand(argv[1])) begin rdict, kdict = Kawari.create_dict(Kawari.read_dict(path)) rescue #except IOError: raise RuntimeError.new('cannot read file') end if @pathlist.include?(path) i = @pathlist.index(path) @rdictlist[i].update(rdict) @kdictlist[i].update(kdict) else @pathlist.insert(0, path) @rdictlist.insert(0, rdict) @kdictlist.insert(0, kdict) end return '' end def exec_save(argv, crypt: false) if argv.length < 2 fail RuntimeError.new('syntax error') end path = get_dict_path(expand(argv[1])) begin open(path, 'wb') do |f| f.write("#\r\n# Kawari save file\r\n#\r\n") for i in 2..argv.length-1 name = expand(argv[i]) if name.strip.empty? next end buf = [] for segments in enumerate(name) buf << segments.join('') end line = [name, ' : ', buf.join(' , ')].join('').encode($charset, :invalid => :replace, :undef => :replace) name = name.encode($charset, :invalid => :replace, :undef => :replace) if crypt line = Kawari.encrypt(line) end f.write(['# Entry ', name, "\r\n", line, "\r\n"].join('')) end end rescue #except IOError: raise RuntimeError.new('cannot write file') end return '' end def exec_savecrypt(argv) return exec_save(argv, :crypt => true) end def exec_textload(argv) if argv.length != 3 fail RuntimeError.new('syntax error') end path = get_dict_path(expand(argv[1])) begin open(path, :encoding => $charset) do |f| linelist = f.readlines() end rescue #except IOError: raise RuntimeError.new('cannot read file') end name = expand(argv[2]) n = 0 for line in linelist n += 1 entry = (name.to_s + '.' + n.to_s) if line.end_with?("\r\n") line = line[0..-3] elsif line.end_with?("\r") or line.end_with?("\n") line = line[0..-2] end if line.empty? clear(entry) else set(entry, line) end end set([name, '.size'].join(''), n.to_s) return '' end def exec_escape(argv) data = argv[1..-1].map {|s| expand(s) }.join(' ') data = data.gsub('\\', '\\\\') data = data.gsub('%', '\%') return data end def exec_echo(argv) return argv[1..-1].map {|s| expand(s) }.join(' ') end def exec_tolower(argv) return exec_echo(argv).downcase end def exec_toupper(argv) return exec_echo(argv).upcase end def exec_eval(argv) return parse_all(exec_echo(argv)) end def exec_entry(argv) if argv.length == 2 return get(expand(argv[1])) elsif argv.length == 3 result = get(expand(argv[1])) unless result.empty? return result else return expand(argv[2]) end else fail RuntimeError.new('syntax error') end end def exec_null(argv) if argv.length != 1 fail RuntimeError.new('syntax error') end return '' end def exec_chr(argv) if argv.length != 2 fail RuntimeError.new('syntax error') end num = atoi(expand(argv[1])) return num.chr if num < 256 return [((num >> 8) & 0xff).chr, (num & 0xff).chr].join('') end def exec_choice(argv) return '' if argv.length == 1 i = rand(1..argv.length-1) return expand(argv[i]) end def exec_rand(argv) if argv.length != 2 fail RuntimeError.new('syntax error') end bound = atoi(expand(argv[1])) if bound.zero? return 0.to_s elsif bound > 0 return rand(0..bound-1).to_s else return rand(bound + 1..-1).to_s end end def exec_date(argv) if argv.length == 1 format_ = '%y/%m/%d %H:%M:%S' else format_ = argv[1..-1].map {|s| expand(s) }.join(' ') end buf = [] i = 0 j = format_.length now = Time.now while i < j if format_[i] == '%' i += 1 if i < j c = format_[i] i += 1 else break end case c when 'y', 'Y' # year (4 columns) buf << sprintf("%04d", now.year) when 'm' # month (01 - 12) buf << sprintf("%02d", now.month) when 'n' # month (1 - 12) buf << now.month.to_s when 'd' # day (01 - 31) buf << sprintf("%02d", now.day) when 'e' # day (1 - 31) buf << now.day.to_s when'H' # hour (00 - 23) buf << sprintf("%02d", now.hour) when 'k' # hour (0 - 23) buf << now.hour.to_s when 'M' # minute (00 - 59) buf << sprintf("%02d", now.min) when 'N' # minute (0 - 59) buf << now.min.to_s when 'S' # second (00 - 59) buf << sprintf("%02d", now.sec) when 'r' # second (0 - 59) buf << now.sec.to_s when 'w' # weekday (0 = Sunday) buf << now.wday.to_s when 'j' # Julian day (001 - 366) buf << sprintf("%03d", now.yday) when 'J' # Julian day (1 - 366) buf << now.yday.to_s when '%' buf << '%' else buf << '%' i -= 1 end else buf << format_[i] i += 1 end end return buf.join('') end def exec_inc(argv) _inc = lambda {|value, step, bound| value += step return bound if not bound.nil? and value > bound return value } apply_counter_op(_inc, argv) return '' end def exec_dec(argv) _dec = lambda {|value, step, bound| value -= step return bound if not bound.nil? and value < bound return value } apply_counter_op(_dec, argv) return '' end def apply_counter_op(func, argv) if argv.length < 2 or argv.length > 4 fail RuntimeError.new('syntax error') end name = expand(argv[1]) value = atoi(get(name)) if argv.length >= 3 step = atoi(expand(argv[2])) else step = 1 end if argv.length == 4 bound = atoi(expand(argv[3])) else bound = nil end set(name, func.call(value, step, bound).to_s) end def exec_test(argv) if argv[0] == 'test' and argv.length == 4 or \ argv[0] == '[' and argv.length == 5 and expand(argv[4]) == ']' op1 = expand(argv[1]) op = expand(argv[2]) op2 = expand(argv[3]) else fail RuntimeError.new('syntax error') end case op when '=', '==' result = (op1 == op2).to_s when '!=' result = (op1 != op2).to_s when '<=' result = (op1 <= op2).to_s when '>=' result = (op1 >= op2).to_s when '<' result = (op1 < op2).to_s when '>' result = (op1 > op2).to_s when '-eq' result = (atoi(op1) == atoi(op2)).to_s when '-ne' result = (atoi(op1) != atoi(op2)).to_s when '-le' result = (atoi(op1) <= atoi(op2)).to_s when '-ge' result = (atoi(op1) >= atoi(op2)).to_s when '-lt' result = (atoi(op1) < atoi(op2)).to_s when '-gt' result = (atoi(op1) > atoi(op2)).to_s else fail RuntimeError.new('unknown operator') end return result end def exec_expr(argv) tree = @expr_parser.parse( argv[1..-1].map {|e| e.join('') }.join(' ')) fail RuntimeError.new('syntax error') if tree.nil? begin value = interp_expr(tree) rescue #except ExprError: raise RuntimeError.new('runtime error') end return value end def interp_expr(tree) case tree[0] when ExprParser::OR_EXPR for subtree in tree[1..-1] value = interp_expr(subtree) break unless value.empty? or ['', '0', 'false', 'False'].include?(value) end return value when ExprParser::AND_EXPR buf = [] for subtree in tree[1..-1] value = interp_expr(subtree) return '0' if value.empty? or ['', '0', 'false', 'False'].include?(value) buf << value end return buf[0] when ExprParser::CMP_EXPR op1 = interp_expr(tree[1]) op2 = interp_expr(tree[3]) if is_number(op1) and is_number(op2) op1 = Integer(op1) op2 = Integer(op2) end case tree[2] when '=', '==' return (op1 == op2).to_s when '!=' return (op1 != op2).to_s when '<=' return (op1 <= op2).to_s when '>=' return (op1 >= op2).to_s when '<' return (op1 < op2).to_s when '>' return (op1 > op2).to_s else fail RuntimeError.new('unknown operator') end when ExprParser::ADD_EXPR for i in 1.step(tree.length-1, 2) tree[i] = interp_expr(tree[i]) unless is_number(tree[i]) fail ExprError end end value = Integer(tree[1]) for i in 2.step(tree.length-1, 2) if tree[i] == '+' value += Integer(tree[i + 1]) elsif tree[i] == '-' value -= Integer(tree[i + 1]) end end return value.to_s when ExprParser::MUL_EXPR for i in 1.step(tree.length-1, 2) tree[i] = interp_expr(tree[i]) unless is_number(tree[i]) fail ExprError end end value = Integer(tree[1]) for i in 2.step(tree.length-1, 2) if tree[i] == '*' value *= Integer(tree[i + 1]) elsif tree[i] == '/' begin value /= Integer(tree[i + 1]) value = value.to_i rescue # except ZeroDivisionError: raise ExprError end elsif tree[i] == '%' begin value = value % Integer(tree[i + 1]) rescue # except ZeroDivisionError: raise ExprError end end end return value.to_s when ExprParser::STR_EXPR case tree[1] when 'length' length = get_characters(interp_expr(tree[2])).length return length.to_s when 'index' s = get_characters(interp_expr(tree[2])) c = get_characters(interp_expr(tree[3])) break_flag = false for pos in 0..s.length-1 if c.include?(s[pos]) break_flag = true break end end unless break_flag pos = 0 end return pos.to_s when 'match' begin match = Regexp.new(interp_expr(tree[3])).match(interp_expr(tree[2])) rescue #except re.error: match = nil end unless match.nil? length = match.end(0) - match.begin(0) else length = 0 end return length.to_s when 'find' s = interp_expr(tree[3]) pos = nterp_expr(tree[2]).index(s) return '' if pos.nil? or pos < 0 return s when 'findpos' s = interp_expr(tree[3]) pos = interp_expr(tree[2]).find(s); return '' if pos.nil? or pos < 0 return (pos + 1).to_s when 'substr' s = interp_expr(tree[2]) p = interp_expr(tree[3]) n = interp_expr(tree[4]) if is_number(p) and is_number(n) p = Integer(p) - 1 n = p + Integer(n) if 0 <= p and p <= n characters = get_characters(s) return characters[p..n-1].join('') end end return '' end when ExprParser::LITERAL return expand(tree[1..-1]) end end def get_characters(s) buf = [] i = 0 j = s.length while i < j buf << s[i] i += 1 end return buf end end ### EXPR PARSER ### class ExprError < StandardError # ValueError #pass end class ExprParser attr_accessor :kawari def initialize #pass @kawari = nil end def show_progress(func, buf) if buf.nil? Logging::Logging.debug(func.to_s + '() -> syntax error') else Logging::Logging.debug(func.to_s + '() -> ' + buf.to_s) end end Re_token = Regexp.new('\A([():|&*/%+-]|[<>]=?|[!=]?=|match|index|findpos|find|substr|length|quote|(\\s+))') def tokenize(data) buf = [] i = 0 j = data.length while i < j match = Re_token.match(data[i..-1]) unless match.nil? buf << match[0] i += match.end(0) else i, segments = Kawari.parse(data, i, :stop_pattern => Re_token) buf.concat(segments) end end return buf end def parse(data) @tokens = tokenize(data) begin return get_expr() rescue #except ExprError: return nil # syntax error end end # internal def done @tokens.empty? end def pop begin return @tokens.shift rescue #except IndexError: raise ExprError end end def look_ahead(index: 0) begin return @tokens[index] rescue #except IndexError: raise ExprError end end def match(s) if pop() != s fail ExprError end end def match_space unless Kawari.is_space(pop()) fail ExprError end end def check(s, index: 0) return (look_ahead(:index => index) == s) end def check_space(index: 0) return Kawari.is_space(look_ahead(:index => index)) end # tree node types OR_EXPR = 1 AND_EXPR = 2 CMP_EXPR = 3 ADD_EXPR = 4 MUL_EXPR = 5 STR_EXPR = 6 LITERAL = 7 def get_expr buf = get_or_expr() unless done() fail ExprError end show_progress('get_expr', buf) return buf end def get_or_expr buf = [OR_EXPR] while true buf << get_and_expr() if not done() and \ check_space() and check('|', :index => 1) pop() # space pop() # operator match_space() else break end end if buf.length == 2 buf = buf[1] end show_progress('get_or_expr', buf) return buf end def get_and_expr buf = [AND_EXPR] while true buf << get_cmp_expr() if not done() and \ check_space() and check('&', :index => 1) pop() # space pop() # operator match_space() else break end end if buf.length == 2 buf = buf[1] end show_progress('get_and_expr', buf) return buf end def get_cmp_expr buf = [CMP_EXPR] buf << get_add_expr() if not done() and \ check_space() and \ ['<=', '>=', '<', '>', '=', '==', '!='].include?(look_ahead(:index => 1)) pop() # space buf << pop() # operator match_space() buf << get_add_expr() end if buf.length == 2 buf = buf[1] end show_progress('get_cmp_expr', buf) return buf end def get_add_expr buf = [ADD_EXPR] while true buf << get_mul_expr() if not done() and \ check_space() and ['+', '-'].include?(look_ahead(:index => 1)) pop() # space buf << pop() # operator match_space() else break end end if buf.length == 2 buf = buf[1] end show_progress('get_add_expr', buf) return buf end def get_mul_expr buf = [MUL_EXPR] while true buf << get_mat_expr() if not done() and \ check_space() and ['*', '/', '%'].include?(look_ahead(:index => 1)) pop() # space buf << pop() # operator match_space() else break end end if buf.length == 2 buf = buf[1] end show_progress('get_mul_expr', buf) return buf end def get_mat_expr buf = [STR_EXPR] buf << get_str_expr() if not done() and \ check_space() and check(':', :index => 1) buf.insert(1, 'match') pop() # space pop() # ':' match_space() buf << get_str_expr() end if buf.length == 2 buf = buf[1] end show_progress('get_mat_expr', buf) return buf end def get_str_expr argc = 0 if check('length') argc = 1 elsif ['match', 'index', 'find', 'findpos'].include?(look_ahead()) argc = 2 elsif check('substr') argc = 3 end if argc > 0 buf = [STR_EXPR, pop()] # fuction for _ in 0..argc-1 match_space() buf << get_str_expr() end elsif check('quote') buf = [LITERAL] pop() match_space() if Re_token.match(look_ahead()) buf << pop() else buf.concat(get_str_seq()) end else buf = get_sub_expr() end show_progress('get_str_expr', buf) return buf end def get_sub_expr if check('(') pop() if check_space() pop() end buf = get_or_expr() if check_space() pop() end #begin specification bug work-around (3/3) @tokens[0] = @kawari.parse_all(@tokens[0]) #end match(')') else buf = [LITERAL] buf.concat(get_str_seq()) end show_progress('get_sub_expr', buf) return buf end def get_str_seq buf = [] while not done() and \ Re_token.match(look_ahead()).nil? buf << pop() end if buf.empty? fail ExprError end return buf end end # <<< EXPR SYNTAX >>> # expr := or-expr # or-expr := and-expr (sp or-op sp and-expr)* # or-op := '|' # and-expr := cmp-expr (sp and-op sp cmp-expr)* # and-op := '&' # cmp-expr := add-expr (sp cmp-op sp add-expr)? # cmp-op := <= | >= | < | > | == | = | != # add-expr := mul-expr (sp add-op sp mul-expr)* # add-op := '+' | '-' # mul-expr := mat-expr (sp mul-op sp mat-expr)* # mul-op := '*' | '/' | '%' # mat-expr := str-expr (sp ':' sp str-expr)? # str-expr := 'quote' sp (OPERATOR | str-seq) | # 'match' sp str-expr sp str-expr | # 'index' sp str-expr sp str-expr | # 'find' sp str-expr sp str-expr | # 'findpos' sp str-expr sp str-expr | # 'substr' sp str-expr sp str-expr sp str-expr | # 'length' sp str-expr | # sub-expr # sub-expr := '(' sp? or-expr sp? ')' | str-seq # sp := SPACE+ (white space) # str-seq := STRING+ (literal, "...", ${...}, and/or $(...)) ### API ### DICT_FILE, INI_FILE = Array(0..1) def self.list_dict(kawari_dir, saori_ini: {}) return Kawari.scan_ini(kawari_dir, 'kawari.ini', saori_ini) end def self.scan_ini(kawari_dir, filename, saori_ini) buf = [] ini_path = File.join(kawari_dir, filename) begin line_list = Kawari.read_dict(ini_path) rescue #except IOError: line_list = [] end read_as_dict = false for entry, value, position in line_list if entry == 'dict' filename = Home.get_normalized_path(value) path = File.join(kawari_dir, filename) begin open(path, 'rb') do |f| f.read(64) end rescue #except IOError as e: ##errno, message = e.args Logging::Logging.debug('kawari.rb: read error: ' + path.to_s) next end buf << [DICT_FILE, path] elsif entry == 'include' filename = Home.get_normalized_path(value) buf.concat(Kawari.scan_ini(kawari_dir, filename, saori_ini)) elsif entry == 'set' read_as_dict = true elsif ['randomseed', 'debug', 'security', 'set'].include?(entry) #pass elsif entry == 'saori' saori_list = value.split(',', 0) path = saori_list[0].strip() alias_ = saori_list[1].strip() if saori_list.length == 3 option = saori_list[2].strip() else option = 'loadoncall' end saori_ini[alias_] = [path, option] else Logging::Logging.debug('kawari.rb: unknown entry: ' + entry.to_s) end end if read_as_dict buf << [INI_FILE, ini_path] end return buf end def self.read_ini(path) buf = [] begin line_list = Kawari.read_dict(path) rescue #except IOError: line_list = [] end for entry, value, position in line_list if entry == 'set' begin entry, value = value.split(nil, 2) rescue #except ValueError: next end buf << [entry.strip(), value.strip(), position] end end return buf end class Shiori < Kawari7 def initialize(dll_name) @dll_name = dll_name @saori_list = {} @saori_ini = {} end def use_saori(saori) @saori = saori end def load(dir: nil) @kawari_dir = dir pathlist = [nil] rdictlist = [{}] kdictlist = [{}] @saori_ini = {} for file_type, path in Kawari.list_dict(@kawari_dir, :saori_ini => @saori_ini) pathlist << path if file_type == INI_FILE rdict, kdict = Kawari.create_dict(Kawari.read_ini(path)) elsif Kawari.is_local_script(path) rdict, kdict = Kawari.read_local_script(path) else rdict, kdict = Kawari.create_dict(Kawari.read_dict(path)) end rdictlist << rdict kdictlist << kdict end kawari_init(@kawari_dir, pathlist, rdictlist, kdictlist) for value in @saori_ini.values() if value[1] == 'preload' head, tail = File.split(value[0].gsub('\\', '/')) saori_load(value[0], File.join(@kawari_dir, head)) end end @kis_commands['saoriregist'] = 'exec_saoriregist' @kis_commands['saorierase'] = 'exec_saorierase' @kis_commands['callsaori'] = 'exec_callsaori' @kis_commands['callsaorix'] = 'exec_callsaorix' return 1 end def unload finalize for name in @saori_list.keys() @saori_list[name].unload() @saori_list.delete(name) end $charset = 'CP932' # reset end def find(dir, dll_name) result = 0 unless Kawari.list_dict(dir).empty? result = 200 end $charset = 'CP932' # reset return result end def show_description Logging::Logging.info( "Shiori: KAWARI compatible module for ninix\n" \ " Copyright (C) 2001, 2002 by Tamito KAJIYAMA\n" \ " Copyright (C) 2002, 2003 by MATSUMURA Namihiko\n" \ " Copyright (C) 2002-2019 by Shyouzou Sugitani\n" \ " Copyright (C) 2003 by Shun-ichi TAHARA") end def request(req_string) header = req_string.force_encoding($charset).encode("UTF-8", :invalid => :replace, :undef => :replace).split(/\r?\n/, 0) req_header = {} unless header.empty? line = header.shift line = line.strip() req_list = line.split(nil, -1) if req_list.length >= 2 command = req_list[0].strip() protocol = req_list[1].strip() end for line in header line = line.strip() next if line.empty? next unless line.include?(':') key, value = line.split(':', 2) key.strip! value.strip! begin value = Integer(value) rescue value = value.to_s end req_header[key] = value end end result = '' to = nil if req_header.include?('ID') if req_header['ID'] == 'dms' result = getdms() elsif req_header['ID'] == 'OnAITalk' result = getaistringrandom() elsif ['\\ms', '\\mz', '\\ml', '\\mc', '\\mh', \ '\\mt', '\\me', '\\mp'].include?(req_header['ID']) result = getword(req_header['ID'][1..-1]) elsif req_header['ID'] == '\\m?' result = getword('m') elsif req_header['ID'] == 'otherghostname' otherghost = [] for n in 0..127 key = ['Reference', n.to_s].join('') if req_header.include?(key) otherghost << req_header[key] end end result = otherghostname(otherghost) elsif req_header['ID'] == 'OnTeach' if req_header.include?('Reference0') teach(req_header['Reference0']) end else result = getstring(req_header['ID']) if result.empty? ref = [] for n in 0..7 key = ['Reference', n.to_s].join('') if req_header.include?(key) ref << req_header[key] else ref << nil end end ref0, ref1, ref2, ref3, ref4, ref5, ref6, ref7 = ref result = get_event_response( req_header['ID'], :ref0 => ref0, :ref1 => ref1, :ref2 => ref2, :ref3 => ref3, :ref4 => ref4, :ref5 => ref5, :ref6 => ref6, :ref7 => ref7) end end if result.nil? result = '' end to = communicate_to() end result = ("SHIORI/3.0 200 OK\r\n" \ "Sender: Kawari\r\n" \ "Charset: " + $charset.to_s + "\r\n" \ "Value: " + result.to_s + "\r\n") unless to.nil? result = [result, "Reference0: " + to.to_s + "\r\n"].join('') end result = [result, "\r\n"].join('') return result.encode($charset, :invalid => :replace, :undef => :replace) end def exec_saoriregist(kawari, argv) filename = expand(argv[1]) alias_ = expand(argv[2]) if argv.length == 4 option = expand(argv[3]) else option = 'loadoncall' end @saori_ini[alias_] = [filename, option] if @saori_ini[alias_][1] == 'preload' head, tail = File.split( @saori_ini[alias_][0].gsub('\\', '/')) saori_load(@saori_ini[alias_][0], File.join(@kawari_dir, head)) end return '' end def exec_saorierase(kawari, argv) alias_ = expand(argv[1]) if @saori_ini.include?(alias_) saori_unload(@saori_ini[alias_][0]) end return '' end def exec_callsaori(kawari, argv) alias_ = expand(argv[1]) return '' unless @saori_ini.include?(alias_) unless @saori_list.include?(@saori_ini[alias_][0]) if @saori_ini[alias_][1] == 'preload' return '' else head, tail = File.split( @saori_ini[alias_][0].gsub('\\', '/')) saori_load(@saori_ini[alias_][0], File.join(@kawari_dir, head)) end end saori_statuscode = '' saori_header = [] saori_value = {} saori_protocol = '' req = ("EXECUTE SAORI/1.0\r\n" \ "Sender: KAWARI\r\n" \ "SecurityLevel: local\r\n" \ "Charset: " + $charset.to_s + "\r\n") for i in 2..argv.length-1 req = [req, "Argument" + (i - 2).to_s + ": " + expand(argv[i]).to_s + "\r\n"].join('') end req = [req, "\r\n"].join('') response = saori_request(@saori_ini[alias_][0], req.encode($charset, :invalid => :replace, :undef => :replace)) header = response.splitlines() unless header.empty? line = header.shift line = line.strip() if line.include?(' ') saori_protocol, saori_statuscode = line.split(' ', 2) saori_protocol.strip! saori_statuscode.strip! end for line in header line = line.strip() next if line.empty? next unless line.include?(':') key, value = line.split(':', 2) key.strip! value.strip! unless key.empty? saori_header << key saori_value[key] = value end end end if saori_value.include?('Result') result = saori_value['Result'] else result = '' end if @saori_ini[alias_][1] == 'noresident' saori_unload(@saori_ini[alias_][0]) end return result end def exec_callsaorix(kawari, argv) alias_ = expand(argv[1]) entry = expand(argv[2]) return '' unless @saori_ini.include?(alias_) unless @saori_list.include?(@saori_ini[alias_][0]) if @saori_ini[alias_][1] == 'preload' return '' else head, tail = File.split( @saori_ini[alias_][0].gsub('\\', '/')) saori_load(@saori_ini[alias_][0], File.join(@kawari_dir, head)) end end saori_statuscode = '' saori_header = [] saori_value = {} saori_protocol = '' req = ("EXECUTE SAORI/1.0\r\n" \ "Sender: KAWARI\r\n" \ "SecurityLevel: local\r\n") for i in 3..argv.length-1 req = [req, "Argument" + (i -3).to_s + ": " + expand(argv[i]) + "\r\n"].join('') end req = [req, "\r\n"].join('') response = saori_request(@saori_ini[alias_][0], req.encode($charset, :invalid => :replace, :undef => :replace)) header = response.splitlines() unless header.empty? line = header.shift line = line.strip() if line.include?(' ') saori_protocol, saori_statuscode = line.split(' ', 2) saori_protocol.strip! saori_statuscode.strip1 end for line in header line = line.strip() next if line.empty? next unless line.include?(':') key, value = line.split(':', 2) key.strip! value.strip! unless key.empty? saori_header << key saori_value[key] = value end end end result = {} for key, value in saori_value if key.start_with?('Value') result[key] = value end end for key, value in result set([entry, '.', key].join(''), value) end if @saori_ini[alias_][1] == 'noresident' saori_unload(@saori_ini[alias_][0]) end return result.length end def saori_load(saori, path) result = 0 if @saori_list.keys().include?(saori) result = @saori_list[saori].load(:dir => path) else module_ = @saori.request(saori) unless module_.nil? @saori_list[saori] = module_ result = @saori_list[saori].load(:dir => path) end end return result end def saori_unload(saori) result = 0 if @saori_list.keys().include?(saori) result = @saori_list[saori].unload() end return result end def saori_request(saori, req) result = 'SAORI/1.0 500 Internal Server Error' if @saori_list.include?(saori) result = @saori_list[saori].request(req) end return result end end def self.is_local_script(path) line = '' open(path, 'rb') do |f| line = f.readline() end return line.start_with?('[SAKURA]') end end ninix-aya-5.0.9/lib/ninix/dll/ssu.rb0000644000175000017500000002642713416507430015435 0ustar shyshy# -*- coding: utf-8 -*- # # ssu.rb - a ssu compatible Saori module for ninix # Copyright (C) 2003-2019 by Shyouzou Sugitani # # This program is free software; you can redistribute it and/or modify it # under the terms of the GNU General Public License (version 2) as # published by the Free Software Foundation. It is distributed in the # hope that it will be useful, but WITHOUT ANY WARRANTY; without even the # implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR # PURPOSE. See the GNU General Public License for more details. # # TODO: # - if, switch等の引数計算(calcを使用) require_relative "../dll" require_relative "../logging" module Ssu class Saori < DLL::SAORI def initialize super() @function = {'is_empty' => ['ssu_is_empty', [0, 1]], 'is_digit' => ['ssu_is_digit', [1]], 'is_alpha' => ['ssu_is_alpha', [1]], 'iflist' => ['ssu_iflist', [nil]], 'length' => ['ssu_length', [1]], 'zen2han' => ['ssu_zen2han', [1]], 'han2zen' => ['ssu_han2zen', [1]], 'kata2hira,' => ['ssu_kata2hira', [1]], 'hira2kata' => ['ssu_hira2kata', [1]], 'sprintf' => ['ssu_sprintf', [nil]], 'calc' => ['ssu_calc', [1]], 'calc_float' => ['ssu_calc_float', [1]], 'compare_tail' => ['ssu_compare_tail', [2]], 'compare_head' => ['ssu_compare_head', [2]], 'compare' => ['ssu_compare', [2]], 'count' => ['ssu_count', [2]], 'erase_first' => ['ssu_erase_first', [2]], 'erase' => ['ssu_erase', [2]], 'replace' => ['ssu_replace', [3]], 'replace_first' => ['ssu_replace_first', [3]], 'split' => ['ssu_split', [1, 2, 3]], 'substr' => ['ssu_substr', [2, 3]], 'nswitch' => ['ssu_nswitch', [nil]], 'switch' => ['ssu_switch', [nil]], 'if' => ['ssu_if', [2, 3]], 'unless' => ['ssu_unless', [2, 3]],} end def request(req) req_type, argument, charset = evaluate_request(req) result = case req_type when nil RESPONSE[400] when 'GET Version' RESPONSE[204] when 'EXECUTE' execute(argument, charset) else RESPONSE[400] end return result end def execute(args, charset) return RESPONSE[400] if args.nil? or args.empty? return RESPONSE[400] unless @function.include?(args[0]) name = args[0] args = args[1..-1] if @function[name][1] == [nil] #pass elsif not @function[name][1].include?(args.length) return RESPONSE[400] end @value = [] result = method(@function[name][0]).call(args) return RESPONSE[204] if result.nil? or result.to_s.empty? s = "SAORI/1.0 200 OK\r\nResult: #{result}\r\n" unless @value.empty? for i in 0..@value.length-1 s = [s, "Value#{i}: #{@value[i]}\r\n"].join("") end end s = [s, "Charset: #{charset}\r\n"].join("") s = [s, "\r\n"].join("") return s.encode(charset, :invalid => :replace, :undef => :replace) end def ssu_is_empty(args) if args.empty? or args[0].empty? return 1 else return 0 end end def ssu_is_digit(args) s = args[0] for i in 0..s.length-1 if s[i] =~ /[[:digit:]]/ next else return 0 end end return 1 end def ssu_is_alpha(args) s = args[0] for i in 0..s.length-1 if s[i] =~ /[[:alpha:]]/ # /(?a:[[:alpha:]])/ next else return 0 end end return 1 end def ssu_length(args) args[0].length end def ssu_substr(args) s = args[0] if ssu_is_digit([args[1]]) == 1 start = ssu_zen2han([args[1]]).to_i return '' if start > s.length else return '' end if args.length == 2 end_ = s.length elsif args.length == 3 and ssu_is_digit([args[2]]) == 1 end_ = (start + ssu_zen2han([args[2]]).to_i) else return '' end return s[start..end_-1] end def ssu_sprintf(args) return sprintf(args[0], *args[1..-1]) end ZEN = {'0' => '0', '1' => '1', '2' => '2', '3' => '3', '4' => '4', '5' => '5', '6' => '6', '7' => '7', '8' => '8', '9' => '9', '.' => '.', '+' => '+', '−' => '-',} HAN = {'0' => '0', '1' => '1', '2' => '2', '3' => '3', '4' => '4', '5' => '5', '6' => '6', '7' => '7', '8' => '8', '9' => '9', '.' => '.', '+' => '+', '-' => "FF0D".to_i(16).chr('UTF-8'),} def ssu_zen2han(args) s = args[0] buf = '' for i in 0..s.length-1 c = s[i] if ZEN.include?(c) buf = [buf, ZEN[c]].join("") else buf = [buf, s[i]].join("") end end return buf end def ssu_han2zen(args) s = args[0] buf = '' for i in 0..s.length-1 c = s[i] if HAN.include?(c) buf = [buf, HAN[c]].join("") else buf = [buf, s[i]].join("") end end return buf end RE_CONDITION = Regexp.new('(>=|<=|>|<|==|!=|>=|<=|>|<|!=|==)') def eval_condition(left, ope, right) if ssu_is_digit([left]) == 1 and \ ssu_is_digit([right]) == 1 left = ssu_zen2han([left]).to_f right = ssu_zen2han([right]).to_f elsif ['>', '>', '>=', '>=', '<', '<', '<=', '<='].include?(ope) left = left.length right = right.length end case ope when '>', '>' (left > right) when '>=', '>=' (left >= right) when '<', '<' (left < right) when '<=', '<=' (left <= right) when '==', '==' (left == right) when '!=', '!=' (left != right) else false # 'should not reach here' end end def ssu_if(args) condition = args[0] match = RE_CONDITION.match(condition) unless match.nil? left = match.pre_match ope = match[0] right = match.post_match result = eval_condition(left, ope, right) if result return args[1] end end if args.length == 3 return args[2] else return '' end end def ssu_unless(args) condition = args[0] match = RE_CONDITION.match(condition) unless match.nil? left = match.pre_match ope = match[0] right = match.post_match result = eval_condition(left, ope, right) unless result return args[1] end end if args.length == 3 return args[2] else return '' end end def ssu_iflist(args) left = args[0] i = 1 while true break if args[i..-1].length < 2 ope_right = args[i] match = RE_CONDITION.match(ope_right) # FIXME: 左辺に演算子を入れた場合にも動作するようにする unless match.nil? next unless match.pre_match.strip.empty? ope = match[0] right = match.post_match result = eval_condition(left, ope, right) if result return args[i + 1] end end i += 2 end return '' end def ssu_nswitch(args) num = args[0] if ssu_is_digit([num]) == 1 num = ssu_zen2han([num]).to_i if 0 < num and num < args.length return args[num] end end return '' end def ssu_count(args) args[0].scan(args[1]).size end def ssu_compare(args) if args[0] == args[1] return 1 else return 0 end end def ssu_compare_head(args) s0 = args[0] s1 = args[1] if s1.start_with?(s0) return 1 else return 0 end end def ssu_compare_tail(args) s0 = args[0] s1 = args[1] if s1.end_with?(s0) return 1 else return 0 end end def ssu_erase(args) args[0].gsub(args[1], '') end def ssu_erase_first(args) tmp = args[0].partition(args[1]) return tmp[0] + tmp[2] end def ssu_replace(args) args[0].gsub(args[1], args[2]) end def ssu_replace_first(args) tmp = args[0].partition(args[1]) if tmp[1].empty? return tmp[0] else return tmp[0] + args[2] + tmp[2] end end def ssu_split(args) ## FIXME: 空要素分割 s0 = args[0] if args.length >= 2 s1 = args[1] else s1 = ' ' ## FIXME end if args.length == 3 num = args[2] if ssu_is_digit([num]) == 1 num = ssu_zen2han([num]).to_i end value_list = s0.split(s1, num + 1) else value_list = s0.split(s1, -1) end @value = value_list return value_list.length end def ssu_switch(args) left = args[0] i = 1 while true break if args[i..-1].length < 2 right = args[i] if left == right # FIXME: 左辺、右辺が数値でなくともエラーにならない return args[i + 1] end i += 2 end return '' end def ssu_kata2hira(args) ## FIXME: not supported yet '' end def ssu_hira2kata(args) ## FIXME: not supported yet '' end def ssu_calc(args) ## FIXME: not supported yet 0 end def ssu_calc_float(args) ## FIXME: not supported yet 0 end def evaluate_request(req) req_type = nil argument = [] charset = 'CP932' # default header = req.lines line = header.shift if line.nil? return req_type, argument, charset end line = line.force_encoding(charset).strip.encode('utf-8', :invalid => :replace, :undef => :replace) if line.empty? return req_type, argument, charset end for request in ['EXECUTE', 'GET Version'] if line.start_with?(request) req_type = request break end end for line in header line = line.force_encoding(charset).strip.encode('utf-8', :invalid => :replace, :undef => :replace) next if line.empty? next unless line.include?(':') key, value = line.split(':', 2) key.strip! value.strip! if key == 'Charset' if Encoding.name_list.include?(value) charset = value else Logging::Logging.warning('Unsupported charset #{value}') end end if key.start_with?('Argument') ## FIXME argument << value else next end end return req_type, argument, charset end end end ninix-aya-5.0.9/lib/ninix/dll/yaya.rb0000644000175000017500000000514613416507430015561 0ustar shyshy# -*- coding: utf-8 -*- # # yaya.rb - a (Real) YAYA loader for ninix # Copyright (C) 2004 by linjian # Copyright (C) 2004-2019 by Shyouzou Sugitani # Copyright (C) 2011 by henryhu # # This program is free software; you can redistribute it and/or modify it # under the terms of the GNU General Public License (version 2) as # published by the Free Software Foundation. It is distributed in the # hope that it will be useful, but WITHOUT ANY WARRANTY; without even the # implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR # PURPOSE. See the GNU General Public License for more details. # require "fiddle/import" require_relative "../logging" $_yaya = nil module Yaya extend Fiddle::Importer begin dlload "libaya5.so" extern "long multi_load(char *, long)" extern "int multi_unload(long)" extern "char *multi_request(long, char *, long *)" $_yaya = self rescue $_yaya = nil end class Shiori def initialize(dll_name) @dll_name = dll_name @pathdic = [] @reqdic = [] @id = nil end def find(topdir, dll_name) result = 0 unless $_yaya.nil? if File.file?(File.join(topdir, 'yaya.txt')) result = 205 elsif not dll_name.nil? and \ File.file?(File.join(topdir, [dll_name[0..-3], 'txt'].join(''))) result = 105 end end result end def show_description Logging::Logging.info( "Shiori: a (Real) YAYA loader for ninix\n" \ " Copyright (C) 2004 by linjian\n" \ " Copyright (C) 2004-2019 by Shyouzou Sugitani\n" \ " Copyright (C) 2011 by henryhu") end def load(dir: nil) @dir = dir return 0 if $_yaya.nil? if @dir.end_with?(File::SEPARATOR) topdir = @dir else topdir = [@dir, File::SEPARATOR].join() end path = Fiddle::Pointer.malloc( @dir.bytesize + 1, freefunc=nil # Yaya will free this pointer ) path[0, @dir.bytesize] = @dir @id = $_yaya.multi_load(path, @dir.bytesize) 1 end def unload $_yaya.multi_unload(@id) unless $_yaya.nil? @id = nil end def request(req_string) return '' if $_yaya.nil? # FIXME request = Fiddle::Pointer.malloc( req_string.bytesize + 1, freefunc=nil # Yaya will free this pointer ) request[0, req_string.bytesize] = req_string rlen =[req_string.bytesize].pack("l!") ret = $_yaya.multi_request(@id, request, rlen) rlen, = rlen.unpack("l!") ret[0, rlen].to_s end end end ninix-aya-5.0.9/lib/ninix/dll/textcopy.rb0000644000175000017500000000254413416507430016474 0ustar shyshy# -*- coding: utf-8 -*- # # textcopy.rb - a TEXTCOPY compatible Saori module for ninix # Copyright (C) 2002-2019 by Shyouzou Sugitani # Copyright (C) 2002, 2003 by MATSUMURA Namihiko # # This program is free software; you can redistribute it and/or modify it # under the terms of the GNU General Public License (version 2) as # published by the Free Software Foundation. It is distributed in the # hope that it will be useful, but WITHOUT ANY WARRANTY; without even the # implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR # PURPOSE. See the GNU General Public License for more details. # require "gtk3" require_relative "../dll" module Textcopy class Saori < DLL::SAORI def initialize super @clipboard = nil end def setup @clipboard = Gtk::Clipboard.get('PRIMARY') 1 end def finalize @clipboard = nil 1 end def execute(argument) return RESPONSE[400] if argument.nil? or argument.empty? or @clipboard.nil? text = argument[0] @clipboard.set_text(text) if argument.length >= 2 and not argument[1].zero? "SAORI/1.0 200 OK\r\n" \ "Result: " \ "#{text.encode(@charset, :invalid => :replace, :undef => :replace)}" \ "\r\n\r\n" else RESPONSE[204] end end end end ninix-aya-5.0.9/lib/ninix/dll/osuwari.rb0000644000175000017500000001433713416507430016311 0ustar shyshy# -*- coding: utf-8 -*- # # osuwari.rb - a Osuwari compatible Saori module for ninix # Copyright (C) 2006-2019 by Shyouzou Sugitani # # This program is free software; you can redistribute it and/or modify it # under the terms of the GNU General Public License (version 2) as # published by the Free Software Foundation. It is distributed in the # hope that it will be useful, but WITHOUT ANY WARRANTY; without even the # implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR # PURPOSE. See the GNU General Public License for more details. # # # TODO: MOVE, NOCLIP, FIX, etc. require "gtk3" require_relative "../dll" require_relative "../pix" module Osuwari class Saori < DLL::SAORI def initialize super() @timeout_id = nil @settings = {} @__sakura = nil end def need_ghost_backdoor(sakura) @__sakura = sakura end def check_import if not @__sakura.nil? return 1 else return 0 end end def setup return 1 end def execute(argument) return RESPONSE[400] if argument.nil? or argument.empty? if argument[0] == 'START' return RESPONSE[400] if argument.length < 7 begin ## FIXME fail "assert" unless ['ACTIVE', 'FIX'].include?(argument[2]) or argument[2].start_with?('@') or argument[2].start_with?('#') fail "assert" unless ['TL', 'TR', 'BL', 'BR'].include?(argument[3]) @settings['hwnd'] = argument[1] @settings['target'] = argument[2] @settings['position'] = argument[3] @settings['offset_x'] = Integer(argument[4]) @settings['offset_y'] = Integer(argument[5]) @settings['timeout'] = Integer(argument[6]) @settings['xmove'] = 0 @settings['ymove'] = 0 @settings['noclip'] = 0 if argument.length > 7 if argument[7].include?('XMOVE') @settings['xmove'] = 1 end if argument[7].include?('YMOVE') @settings['ymove'] = 1 end if argument[7].include?('NOCLIP') @settings['noclip'] = 1 end end @settings['except'] = ['DESKTOP', 'CENTER'] if argument.length > 8 #target, position = argument[8].split() # spec position, target = argument[8].split(nil, 2) # real world fail "assert" unless ['DESKTOP', 'WORKAREA'].include?(target) fail "assert" unless ['TOP', 'LEFT', 'RIGHT', 'BOTTOM'].include?(position) @settings['except'] = [target, position] end rescue return RESPONSE[400] end #@timeout_id = GLib::Timeout.add(@settings['timeout']) { do_idle_tasks } @timeout_id = GLib::Timeout.add(100) { do_idle_tasks } return RESPONSE[204] elsif argument[0] == 'STOP' GLib::Source.remove(@timeout_id) unless @timeout_id.nil? @timeout_id = nil @settings = {} return RESPONSE[204] else return RESPONSE[400] end end def do_idle_tasks return false if @timeout_id.nil? target = @settings['target'] left, top, scrn_w, scrn_h = @__sakura.get_workarea target_flag = [false, false] if target == 'ACTIVE' active_window = get_active_window() unless active_window.nil? if @__sakura.identify_window(active_window) target_flag[1] = true else rect = active_window.frame_extents target_x = rect.x target_y = rect.y target_w = rect.width target_h = rect.height target_flag[0] = true end else target_flag[1] = true end elsif target == 'FIX' ## FIXME target_x = left target_y = top target_w = scrn_w target_h = scrn_h target_flag[0] = true elsif target.start_with?('@') ## FIXME #win = get_window_by_name(target[1..-1]) #if @__sakura.identify_window(win) # return true #end #target_x, target_y = active_window.root_origin #rect = active_window.frame_extents #target_w = rect.width #target_h = rect.height #pass elsif target.start_with?('#') ## FIXME #pass else #pass # should not reach here end return target_flag[1] unless target_flag[0] pos = @settings['position'] scale = @__sakura.get_surface_scale() offset_x = (@settings['offset_x'] * scale / 100).to_i offset_y = (@settings['offset_y'] * scale / 100).to_i if @settings['hwnd'].start_with?('s') begin side = Integer(@settings['hwnd'][1..-1]) rescue return false end else begin side = Integer(@settings['hwnd']) rescue return false end end w, h = @__sakura.get_surface_size(side) case pos[0] when 'T' y = (target_y + offset_y) when 'B' y = (target_y + target_h + offset_y - h) else return false # should not reach here end case pos[1] when 'L' x = (target_x + offset_x) when 'R' x = (target_x + target_w + offset_x - w) else return false # should not reach here end if not @settings['noclip'] if x < left or x > left+ scrn_w or \ y < top or y > top + scrn_h case @settings['except'][1] when 'BOTTOM' #pass ## FIXME: not supported yet when 'TOP' #pass ## FIXME: not supported yet when 'LEFT' #pass ## FIXME: not supported yet when 'RIGTH' #pass ## FIXME: not supported yet when 'CENTER' #pass ## FIXME: not supported yet else #pass # should not reach here end end end @__sakura.set_surface_position(side, x, y) @__sakura.raise_surface(side) return true end def get_window_by_name(name) ## FIXME: not supported yet return nil end def get_active_window scrn = Gdk::Screen.default active_window = scrn.active_window return active_window end end end ninix-aya-5.0.9/lib/ninix/dll/kawari8.rb0000644000175000017500000001116513416507430016162 0ustar shyshy# -*- coding: utf-8 -*- # # kawari8.rb - a (Real) 華和梨 loader for ninix # Copyright (C) 2002, 2003 by ABE Hideaki # Copyright (C) 2002-2019 by Shyouzou Sugitani # Copyright (C) 2002, 2003 by MATSUMURA Namihiko # # This program is free software; you can redistribute it and/or modify it # under the terms of the GNU General Public License (version 2) as # published by the Free Software Foundation. It is distributed in the # hope that it will be useful, but WITHOUT ANY WARRANTY; without even the # implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR # PURPOSE. See the GNU General Public License for more details. # require "fiddle/import" require_relative "../logging" $_kawari8 = nil module Kawari8 extend Fiddle::Importer begin dlload "libshiori.so" # "_kawari8.so" extern "int so_library_init()" extern "int so_library_cleanup()" extern "unsigned int so_create(const char *, long)" extern "int so_dispose(unsigned int)" extern "const char *so_request(unsigned int, const char *, long *)" extern "void so_free(unsigned int, const char *)" result = so_library_init() unless result.zero? $_kawari8 = self else $_kawari8 = nil end rescue $_kawari8 = nil end class Shiori ##saori_list = {} def initialize(dll_name) @dll_name = dll_name @handle = 0 @req = [] end ##def use_saori(saori) ## @saori = saori ##end def find(topdir, dll_name) result = 0 result = 205 if not $_kawari8.nil? and File.file?(File.join(topdir, 'kawarirc.kis')) return result end def show_description Logging::Logging.info( "Shiori: Real Kawari8 loader for ninix\n" \ " Copyright (C) 2002, 2003 by ABE Hideaki\n" \ " Copyright (C) 2002-2019 by Shyouzou Sugitani\n" \ " Copyright (C) 2002, 2003 by MATSUMURA Namihiko") end def load(dir: nil) @dir = dir return 0 if $_kawari8.nil? unless @dir.end_with?(File::SEPARATOR) @dir = [@dir, File::SEPARATOR].join() end ##_kawari8.setcallback(self.saori_exist, ## Shiori.saori_load, ## Shiori.saori_unload, ## Shiori.saori_request) @handle = $_kawari8.so_create(@dir, @dir.bytesize) return @handle.zero? ? 0 : 1 end def unload return if $_kawari8.nil? $_kawari8.so_dispose(@handle) ##for name in list(Shiori.saori_list.keys()) ## if not name.startswith(os.fsdecode(self.dir)) ## continue ## end ## if Shiori.saori_list[name][1]: ## Shiori.saori_list[name][0].unload() ## end ## del Shiori.saori_list[name] ## # XXX ## _kawari8.setcallback(lambda *a: 0, # dummy ## Shiori.saori_load, ## Shiori.saori_unload, ## Shiori.saori_request) ##end end def request(req_string) return '' if $_kawari8.nil? # FIXME req_len = [req_string.bytesize].pack("l!") result = $_kawari8.so_request(@handle, req_string, req_len) req_len, = req_len.unpack("l!") return_val = result[0, req_len].to_s $_kawari8.so_free(@handle, result) return return_val end ##def saori_exist(self, saori): ## module = self.saori.request(saori) ## if module: ## Shiori.saori_list[saori] = [module, 0] ## return len(Shiori.saori_list) ## else: ## return 0 ##@classmethod ##def saori_load(cls, saori, path): ## result = 0 ## if saori in cls.saori_list and cls.saori_list[saori][1] == 0: ## result = cls.saori_list[saori][0].load(path) ## cls.saori_list[saori][1] = result ## return result ##@classmethod ##def saori_unload(cls, saori): ## result = 0 ## if saori in cls.saori_list and cls.saori_list[saori][1] != 0: ## result = cls.saori_list[saori][0].unload() ## cls.saori_list[saori][1] = 0 ## return result ##@classmethod ##def saori_request(cls, saori, req): ## result = b'SAORI/1.0 500 Internal Server Error' ## if saori in cls.saori_list: ## if cls.saori_list[saori][1] == 0: ## head, tail = os.path.split(os.fsencode(saori)) ## cls.saori_list[saori][1] = cls.saori_list[saori][0].load(head) ## if cls.saori_list[saori][1]: ## result = cls.saori_list[saori][0].request(req) ## return result end end ninix-aya-5.0.9/lib/ninix/dll/bln.rb0000644000175000017500000006212213416507430015366 0ustar shyshy# -*- coding: utf-8 -*- # # bln.rb - a easyballoon compatible Saori module for ninix # Copyright (C) 2002-2019 by Shyouzou Sugitani # # This program is free software; you can redistribute it and/or modify it # under the terms of the GNU General Public License (version 2) as # published by the Free Software Foundation. It is distributed in the # hope that it will be useful, but WITHOUT ANY WARRANTY; without even the # implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR # PURPOSE. See the GNU General Public License for more details. # # TODO: # - font.face require "gtk3" require_relative "../pix" require_relative "../script" require_relative "../dll" require_relative "../logging" module Bln class Saori< DLL::SAORI def initialize super() @blns = {} @__sakura = nil end def need_ghost_backdoor(sakura) @__sakura = sakura end def check_import @__sakura.nil? ? 0 : 1 end def setup @blns = read_bln_txt(@dir) unless @blns.empty? @__sakura.attach_observer(self) return 1 else return 0 end end def read_bln_txt(dir) blns = {} begin open(File.join(dir, 'bln.txt'), 'r', :encoding => 'CP932') do |f| data = {} name = '' for line in f line = line.strip() next if line.empty? next if line.start_with?('//') if line.include?('[') unless name.empty? blns[name] = [data, {}] end data = {} start = line.index('[') end_ = line.index(']') if end_.nil? end_ = line.length end name = line[start + 1..end_-1] else if line.include?(',') key, value = line.split(',', 2) key.strip! value.strip! data[key] = value end end end unless name.empty? blns[name] = [data, {}] end return blns end rescue return {} end end def finalize for name in @blns.keys data, bln = @blns[name] for bln_id in bln.keys unless bln[bln_id].nil? bln[bln_id].destroy() bln.delete(bln_id) end end end @blns = {} @__sakura.detach_observer(self) return 1 end def observer_update(event, args) if event == 'set scale' for name in @blns.keys data, bln = @blns[name] for bln_id in bln.keys unless bln[bln_id].nil? bln[bln_id].reset_scale() end end end end end def execute(argument) return RESPONSE[400] if argument.nil? or argument.empty? name = argument[0] if argument.length >= 2 text = argument[1] else text = '' end if argument.length >= 3 begin offset_x = Integer(argument[2]) rescue offset_x = 0 end else offset_x = 0 end if argument.length >= 4 begin offset_y = Integer(argument[3]) rescue offset_y = 0 end else offset_y = 0 end if argument.length >= 5 bln_id = argument[4] else bln_id = '' end if argument.length >= 6 and ['1', '2'].include?(argument[5]) update = argument[5].to_i else update = 0 end if @blns.include?(name) data, bln = @blns[name] if bln.include?(bln_id) and update.zero? bln[bln_id].destroy() bln.delete(bln_id) end unless text.empty? if update.zero? or not bln.include?(bln_id) bln[bln_id] = Balloon.new(@__sakura, @dir, data, text, offset_x, offset_y, name, bln_id) else bln[bln_id].update_script(text, update) end end @blns[name] = [data, bln] elsif name == 'clear' for name in @blns.keys data, bln = @blns[name] new_bln = {} for bln_id in bln.keys if bln[bln_id].get_state() == 'orusuban' new_bln[bln_id] = bln[bln_id] next end bln[bln_id].destroy() end @blns[name] = [data, new_bln] end end return nil end end class Balloon def initialize(sakura, dir, data, text, offset_x, offset_y, name, bln_id) @dir = dir @__sakura = sakura @name = name @id = bln_id @timeout_id = nil @data = data # XXX @window = Pix::TransparentWindow.new() @window.set_title(name) @window.set_skip_taskbar_hint(true) @window.signal_connect('delete_event') do |w, e| next delete(w, e) end if data.include?('position') @position = data['position'] else @position = 'sakura' end left, top, scrn_w, scrn_h = @__sakura.get_workarea # -1: left, 1: right if @position == 'sakura' s0_x, s0_y, s0_w, s0_h = get_sakura_status('SurfaceSakura') if (s0_x + (s0_w / 2).to_i) > (left + (scrn_w / 2).to_i) @direction = -1 else @direction = 1 end elsif @position == 'kero' s1_x, s1_y, s1_w, s1_h = get_sakura_status('SurfaceKero') if (s1_x + (s1_w / 2).to_i) > (left + (scrn_w / 2).to_i) @direction = -1 else @direction = 1 end else @direction = 1 # XXX end if ['sakura', 'kero'].include?(@position) default = data['skin'] if @direction == -1 if data.include?('skin.left') skin = data['skin.left'] else skin = default end else if data.include?('skin.right') skin = data['skin.right'] else skin = default end end else skin = data['skin'] end if skin.nil? destroy() return end path = File.join(@dir, skin.gsub("\\", '/')) begin balloon_surface = Pix.create_surface_from_file(path) rescue destroy() return end @reshape = true @balloon_surface = balloon_surface w = balloon_surface.width h = balloon_surface.height @x = @y = 0 if data.include?('offset.x') @x += (@direction * data['offset.x'].to_i) end if data.include?('offset.y') @y += data['offset.y'].to_i end if data.include?('offset.random') @x += (data['offset.random'].to_i * Random.rand(-1.0..2.0)) @y += (data['offset.random'].to_i * Random.rand(-1.0..2.0)) end @x += (@direction * offset_x.to_i) @y += offset_y.to_i @action_x = 0 @action_y = 0 @vx = 0 @vy = 0 if data.include?('disparea.left') @left = data['disparea.left'].to_i else @left = 0 end if data.include?('disparea.right') @right = data['disparea.right'].to_i else @right = w.to_i end if data.include?('disparea.top') @top = data['disparea.top'].to_i else @top = 0 end if data.include?('disparea.bottom') @bottom = data['disparea.bottom'].to_i else @bottom = h.to_i end @script = nil @darea = @window.darea @darea.set_events(Gdk::EventMask::EXPOSURE_MASK| Gdk::EventMask::BUTTON_PRESS_MASK| Gdk::EventMask::BUTTON_RELEASE_MASK| Gdk::EventMask::POINTER_MOTION_MASK| Gdk::EventMask::LEAVE_NOTIFY_MASK) @darea.signal_connect('draw') do |w, e| redraw(w, e) next true end @darea.signal_connect('button_press_event') do |w, e| next button_press(w, e) end @darea.signal_connect('button_release_event') do |w, e| next button_release(w, e) end @darea.signal_connect('motion_notify_event') do |w, e| next motion_notify(w, e) end @darea.signal_connect('leave_notify_event') do |w, e| leave_notify(w, e) next true end set_skin() set_position() @layout = nil if text != 'noscript' and \ (@right - @left) > 0 and (@bottom - @top) > 0 @script = text if data.include?('font.color') fontcolor_r = (data['font.color'][0..1].to_i(16) / 255.0) fontcolor_g = (data['font.color'][2..3].to_i(16) /255.0) fontcolor_b = (data['font.color'][4..5].to_i(16) / 255.0) @fontcolor = [fontcolor_r, fontcolor_g, fontcolor_b] else @fontcolor = [0.0, 0.0, 0.0] # XXX end default_font_size = 12 # for Windows environment if data.include?('font.size') @font_size = data['font.size'].to_i else @font_size = default_font_size end @layout = Pango::Layout.new(@darea.pango_context) @font_desc = Pango::FontDescription.new @font_desc.set_family('Sans') if data.include?('font.bold') and data['font.bold'] == 'on' @font_desc.set_weight(:bold) end @layout.set_wrap(:char) set_layout() end if data.include?('slide.vx') @slide_vx = data['slide.vx'].to_i else @slide_vx = 0 end if data.include?('slide.vy') @slide_vy = data['slide.vy'].to_i else @slide_vy = 0 end if data.include?('slide.autostop') @slide_autostop = data['slide.autostop'].to_i else @slide_autostop = 0 end if ['sinwave', 'vibrate'].include?(data['action.method']) action = data['action.method'] if data.include?('action.reference0') ref0 = data['action.reference0'].to_i else ref0 = 0 end if data.include?('action.reference1') ref1 = data['action.reference1'].to_i else ref1 = 0 end if data.include?('action.reference2') ref2 = data['action.reference2'].to_i else ref2 = 0 end if data.include?('action.reference3') ref3 = data['action.reference3'].to_i else ref3 = 0 end unless ref2.zero? @action = {'method' => action, 'ref0' => ref0, 'ref1' => ref1, 'ref2' => ref2, 'ref3' => ref3} else @action = nil end else @action = nil end @move_notify_time = nil @life_time = nil @state = '' if data.include?('life') life = data['life'] else life = 'auto' end if life == 'auto' @life_time = 16000 elsif ['infinitely', '0'].include?(life) #pass elsif life == 'orusuban' @state = 'orusuban' else begin @life_time = Integer(life) rescue #pass end end @start_time = Time.now if data.include?('startdelay') @startdelay = data['startdelay'].to_i else @startdelay = 0 end if data.include?('nooverlap') @nooverlap = 1 else @nooverlap = 0 end @talking = get_sakura_is_talking() @move_notify_time = Time.new @visible = false @x_root = nil @y_root = nil @processed_script = [] @processed_text = '' @text = '' @script_wait = nil @quick_session = false @script_parser = Script::Parser.new(:error => 'loose') begin @processed_script = @script_parser.parse(@script) rescue Script::ParserError => e @processed_script = [] Logging::Logging.error('-' * 50) Logging::Logging.error(e.format) Logging::Logging.error(@script.encode('utf-8', :invalid => :replace, :undef => :replace)) end @timeout_id = GLib::Timeout.add(10) { do_idle_tasks } end def set_position return if @window.nil? new_x = (@base_x + ((@x + @action_x + @vx) * @scale / 100.0).to_i) new_y = (@base_y + ((@y + @action_y + @vy) * @scale / 100.0).to_i) @window.move(new_x, new_y) end def set_skin return if @window.nil? @scale = get_sakura_status('SurfaceScale') w = @balloon_surface.width h = @balloon_surface.height w = [8, (w * @scale / 100).to_i].max h = [8, (h * @scale / 100).to_i].max @base_x, @base_y = get_coordinate(w, h) end def set_layout return if @window.nil? return if @layout.nil? font_size = (@font_size * 3 / 4).to_i # convert from Windows to GTK+ font_size = font_size * Pango::SCALE @font_desc.set_size(font_size) @layout.set_font_description(@font_desc) @layout.set_width(((@right - @left) * 1024).to_i) end def reset_scale return if @window.nil? set_skin() set_position() end def clickerase return (not @data.include?('clickerase') or @data['clickerase'] == 'on') # default: ON end def dragmove_horizontal return (@data.include?('dragmove.horizontal') and @data['dragmove.horizontal'] == 'on') end def dragmove_vertical return (@data.include?('dragmove.vertical') and @data['dragmove.vertical'] == 'on') end def get_coordinate(w, h) left, top, scrn_w, scrn_h = @__sakura.get_workarea s0_x, s0_y, s0_w, s0_h = get_sakura_status('SurfaceSakura') s1_x, s1_y, s1_w, s1_h = get_sakura_status('SurfaceKero') b0_x, b0_y, b0_w, b0_h = get_sakura_status('BalloonSakura') b1_x, b1_y, b1_w, b1_h = get_sakura_status('BalloonKero') x = left y = top case @position when 'lefttop' #pass when 'leftbottom' y = (top + scrn_h - h) when 'righttop' x = (left + scrn_w - w) when 'rightbottom' x = (left + scrn_w - w) y = (top + scrn_h - h) when 'center' x = (left + ((scrn_w - w) / 2).to_i) y = (top + ((scrn_h - h) / 2).to_i) when 'leftcenter' y = (top + ((scrn_h - h) / 2).to_i) when 'rightcenter' x = (left + scrn_w - w) y = (top + ((scrn_h - h) / 2).to_i) when 'centertop' x = (left + ((scrn_w - w) / 2).to_i) when 'centerbottom' x = (left + ((scrn_w - w) / 2).to_i) y = (top + scrn_h - h) when 'sakura' if @direction == 1 # right x = (s0_x + s0_w) else x = (s0_x - w) end y = s0_y when 'kero' if @direction == 1 # right x = (s1_x + s1_w) else x = (s1_x - w) end y = s1_y when 'sakurab' x = b0_x y = b0_y when 'kerob' x = b1_x y = b1_y end return x, y end def update_script(text, mode) return if @timeout_id.nil? # XXX return unless text if mode == 2 and not @script.nil? @script = [@script, text].join("") else @script = text end @processed_script = [] @processed_text = '' @text = '' @script_wait = nil @quick_session = false begin @processed_script = @script_parser.parse(@script) rescue Script::ParserError => e @processed_script = [] Logging::Logging.error('-' * 50) Logging::Logging.error(e.format) Logging::Logging.error(@script.encode('utf-8', :invalid => :replace, :undef => :replace)) end end def get_sakura_is_talking talking = false begin if @__sakura.is_talking() talking = true else talking = false end rescue #pass end return talking end def get_sakura_status(key) case key when 'SurfaceScale' result = @__sakura.get_surface_scale() when 'SurfaceSakura_Shown' if @__sakura.surface_is_shown(0) result = true else result = false end when 'SurfaceSakura' begin s0_x, s0_y = @__sakura.get_surface_position(0) s0_w, s0_h = @__sakura.get_surface_size(0) rescue s0_x, s0_y = 0, 0 s0_w, s0_h = 0, 0 end result = s0_x, s0_y, s0_w, s0_h when 'SurfaceKero_Shown' if @__sakura.surface_is_shown(1) result = true else result = false end when 'SurfaceKero' begin s1_x, s1_y = @__sakura.get_surface_position(1) s1_w, s1_h = @__sakura.get_surface_size(1) rescue s1_x, s1_y = 0, 0 s1_w, s1_h = 0, 0 end result = s1_x, s1_y, s1_w, s1_h when 'BalloonSakura_Shown' if @__sakura.balloon_is_shown(0) result = true else result = false end when 'BalloonSakura' begin b0_x, b0_y = @__sakura.get_balloon_position(0) b0_w, b0_h = @__sakura.get_balloon_size(0) rescue b0_x, b0_y = 0, 0 b0_w, b0_h = 0, 0 end result = b0_x, b0_y, b0_w, b0_h when 'BalloonKero_Shown' if @__sakura.balloon_is_shown(1) result = true else result = false end when 'BalloonKero' begin b1_x, b1_y = @__sakura.get_balloon_position(1) b1_w, b1_h = @__sakura.get_balloon_size(1) rescue b1_x, b1_y = 0, 0 b1_w, b1_h = 0, 0 end result = b1_x, b1_y, b1_w, b1_h else result = nil end return result end def do_idle_tasks return if @window.nil? s0_shown = get_sakura_status('SurfaceSakura_Shown') s1_shown = get_sakura_status('SurfaceKero_Shown') b0_shown = get_sakura_status('BalloonSakura_Shown') b1_shown = get_sakura_status('BalloonKero_Shown') sakura_talking = get_sakura_is_talking() if @state == 'orusuban' if @visible if s0_shown or s1_shown destroy() return nil end else unless s0_shown or s1_shown @start_time = Time.now @visible = true @window.show() @life_time = 300000 end end else if @visible if (@position == 'sakura' and not s0_shown) or \ (@position == 'kero' and not s1_shown) or \ (@position == 'sakurab' and not b0_shown) or \ (@position == 'kerob' and not b1_shown) or \ (@nooverlap == 1 and not @talking and sakura_talking) destroy() return nil end else if (Time.now - @start_time) >= (@startdelay * 0.001) @start_time = Time.now @visible = true @window.show() end end end if @visible unless @life_time.nil? if (Time.now - @start_time) >= (@life_time * 0.001) and \ (@processed_script.empty? and @processed_text.empty?) destroy() return nil end end unless @action.nil? if @action['method'] == 'sinwave' offset = (@action['ref1'] \ * Math.sin(2.0 * Math::PI \ * (((Time.now - \ @start_time) * 1000).to_i \ % @action['ref2']).to_f / @action['ref2'])) if @action['ref0'] == 1 @action_y = offset.to_i else @action_x = offset.to_i end elsif @action['method'] == 'vibrate' offset = ((((Time.now - @start_time) * 1000).to_i / \ @action['ref2']).to_i % 2) @action_x = (offset * @action['ref0']).to_i @action_y = (offset * @action['ref1']).to_i end end if (not @slide_vx.zero? or not @slide_vy.zero?) and \ @slide_autostop > 0 and \ (@slide_autostop * 0.001 + 0.05) <= (Time.now - @start_time) @vx = (@direction * ((@slide_autostop / 50.0 + 1) * @slide_vx).to_i) @slide_vx = 0 @vy = ((@slide_autostop / 50.0 + 1) * @slide_vy).to_i @slide_vy = 0 end unless @slide_vx.zero? @vx = (@direction * (((Time.now - @start_time) * @slide_vx) / 50 * 1000.-).to_i) end unless @slide_vy.zero? @vy = (((Time.now - @start_time) * @slide_vy) / 50 * 1000.0).to_i end set_position() unless @processed_script.empty? and @processed_text.empty? interpret_script() end end if @talking and not sakura_talking @talking = false else @talking = sakura_talking end return true end def redraw(widget, cr) @window.set_surface(cr, @balloon_surface, @scale) cr.set_operator(Cairo::OPERATOR_OVER) # restore default cr.translate(*@window.get_draw_offset) # XXX unless @layout.nil? cr.set_source_rgb(*@fontcolor) cr.move_to(@left.to_i, @top.to_i) cr.show_pango_layout(@layout) end @window.set_shape(cr, @reshape) @reshape = false end def get_state @state end def interpret_script unless @script_wait.nil? return if Time.now < @script_wait @script_wait = nil end unless @processed_text.empty? if @quick_session or @state == 'orusuban' @text = [@text, @processed_text].join("") draw_text(@text) @processed_text = '' else @text = [@text, @processed_text[0]].join("") draw_text(@text) @processed_text = @processed_text[1..-1] @script_wait = (Time.now + 0.014) end return end node = @processed_script.shift if node[0] == Script::SCRIPT_TAG name, args = node[1], node[2..-1] case name when '\n' @text = [@text, "\n"].join("") draw_text(@text) when '\w' unless args.nil? begin amount = (Integer(args[0]) * 0.05 - 0.01) rescue amount = 0 end else amount = (1 * 0.05 - 0.01) end if amount > 0 @script_wait = (Time.now + amount) end when '\b' unless args.nil? begin amount = Integer(args[0]) rescue amount = 0 end else amount = 1 end if amount > 0 @text = @text[0..-amount-1] end when '\c' @text = '' when '\_q' @quick_session = (not @quick_session) when '\l' @life_time = nil update_script('', 2) end elsif node[0] == Script::SCRIPT_TEXT text = '' for chunk in node[1] text = [text, chunk[1]].join("") end @processed_text = text end end def draw_text(text) @layout.set_text(text) @darea.queue_draw() end def button_press(widget, event) @x_root = event.x_root @y_root = event.y_root if event.event_type == Gdk::EventType::DOUBLE_BUTTON_PRESS x, y = @window.winpos_to_surfacepos( event.x.to_i, event.y.to_i, @scale) @__sakura.notify_event( 'OnEBMouseDoubleClick', @name, x, y, @id) end return true end def button_release(widget, event) @x_root = nil @y_root = nil x, y = @window.winpos_to_surfacepos( event.x.to_i, event.y.to_i, @scale) if event.event_type == Gdk::EventType::BUTTON_RELEASE case event.button when 1 @__sakura.notify_event( 'OnEBMouseClick', @name, x, y, @id, 0) when 3 @__sakura.notify_event( 'OnEBMouseClick', @name, x, y, @id, 1) end end destroy if @clickerase return true end def motion_notify(widget, event) unless @x_root.nil? or @y_root.nil? x_delta = (event.x_root - @x_root).to_i y_delta = (event.y_root - @y_root).to_i unless (event.state & Gdk::ModifierType::BUTTON1_MASK).zero? @x += x_delta if @dragmove_horizontal @y += y_delta if @dragmove_vertical set_position() @x_root = event.x_root @y_root = event.y_root end end if @move_notify_time.nil? or \ (Time.now - @move_notify_time) > (500 * 0.001) x, y = @window.winpos_to_surfacepos( event.x.to_i, event.y.to_i, @scale) @__sakura.notify_event( 'OnEBMouseMove', @name, x, y, @id) @move_notify_time = Time.now end return true end def leave_notify(widget, event) @move_notify_time = nil end def delete(window, event) return true end def destroy @visible = false @window.destroy() unless @window.nil? @window = nil GLib::Source.remove(@timeout_id) unless @timeout_id.nil? @timeout_id = nil end end end ninix-aya-5.0.9/lib/ninix/dll/hanayu.rb0000644000175000017500000004503513416507430016104 0ustar shyshy# -*- coding: utf-8 -*- # # hanayu.rb - a "花柚" compatible Saori module for ninix # Copyright (C) 2002-2019 by Shyouzou Sugitani # Copyright (C) 2002, 2003 by MATSUMURA Namihiko # # This program is free software; you can redistribute it and/or modify it # under the terms of the GNU General Public License (version 2) as # published by the Free Software Foundation. It is distributed in the # hope that it will be useful, but WITHOUT ANY WARRANTY; without even the # implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR # PURPOSE. See the GNU General Public License for more details. # # TODO: # - usetime, line, radar 以外の形式のグラフへの対応. require "gtk3" require_relative "../pix" require_relative "../dll" require_relative "../logging" module Hanayu class Saori < DLL::SAORI DBNAME = 'HANAYU.db' def initialize super() @graphs = {} @data = {} end def setup @dbpath = File.join(@dir, DBNAME) @graphs = {} @data = read_hanayu_txt(@dir) unless @data.nil? or @data.empty? read_db() return 1 else return 0 end end def read_hanayu_txt(dir) graphs = {} begin open(File.join(dir, 'hanayu.txt'), 'r', :encoding => 'CP932') do |f| data = {} name = '' tmp_name = '' for line in f line = line.encode('UTF-8', :invalid => :replace, :undef => :replace).strip() next if line.empty? next if line.start_with?('//') if line.start_with?('[') # bln.txt like format graphs[name] = data data = {} end_ = line.find(']') if end_ < 0 end_ = line.length name = line[1..end_-1] end elsif line == '{' # surfaces.txt like format graphs[name] = data data = {} name = tmp_name ## FIXME elsif line == '}' # surfaces.txt like format graphs[name] = data data = {} name = '' elsif line.include?(',') key, value = line.split(',', 2) key.strip! value.strip! data[key] = value elsif not name tmp_name = line end end graphs[name] = data unless data.empty? return graphs end rescue return nil end end def finalize @graphs.each_key {|name| @graphs[name].destroy() } @graphs = {} @data = {} write_db() return 1 end def time_to_key(base, offset) year = base.year month = base.month day = base.day target_time = (Time.new(year, month, day, 0, 0, 0) + offset * 24 *60 * 60) year = target_time.year month = target_time.month day = target_time.day key = (year * 10000 + month * 100 + day).to_s return key, year, month, day end def read_db @seven_days = [] current_time = @last_update = Time.now for index_ in -6..0 key, year, month, day = time_to_key(current_time, index_) @seven_days << [key, year, month, day, 0.0] end begin open(@dbpath) do |f| ver = nil for line in f line = line.strip() next if line.empty? if ver.nil? if line == '# Format: v1.0' ver = 1 end next end next unless line.include?(',') key, value = line.split(',', 2) for index_ in 0..6 if @seven_days[index_][0] == key @seven_days[index_][4] = Float(value) end end end end rescue return end end def update_db current_time = Time.now old_seven_days = [] old_seven_days.concat(@seven_days) @seven_days = [] for index_ in -6..0 key, year, month, day = time_to_key(current_time, index_) @seven_days << [key, year, month, day, 0.0] end for i in 0..6 key = old_seven_days[i][0] for j in 0..6 if @seven_days[j][0] == key @seven_days[j][4] = old_seven_days[i][4] if j == 6 @seven_days[j][4] = (@seven_days[j][4] + \ (current_time - \ @last_update) / \ (60.0 * 60.0)) end end end end @last_update = current_time end def write_db update_db() begin open(@dbpath, 'w') do |f| f.write("# Format: v1.0\n") for index_ in 0..6 f.write(@seven_days[index_][0].to_s + ", " + @seven_days[index_][4].to_s + "\n") end end rescue # IOError, SystemCallError Logging::Logging.error('HANAYU: cannot write database (ignored)') end end def execute(argument) return RESPONSE[400] if argument.nil? or argument.empty? command = argument[0] case command when 'show' if argument.length >= 2 name = argument[1] if @graphs.include?(name) @graphs[name].destroy() end else name = '' end return RESPONSE[400] unless @data.include?(name) if @data[name].include?('graph') and \ ['line', 'bar', 'radar', 'radar2'].include?(@data[name]['graph']) graph_type = @data[name]['graph'] else graph_type = 'usetime' end case graph_type when 'usetime' update_db() new_args = [] for index_ in 0..6 date = [@seven_days[index_][2].to_s, '/', @seven_days[index_][3].to_s].join("") new_args << date hours = @seven_days[index_][4] new_args << hours end @graphs[name] = Line.new( @dir, @data[name], :args => new_args, :limit_min => 0, :limit_max => 24) when 'line' @graphs[name] = Line.new( @dir, @data[name], :args => argument[2..-1]) when 'bar' @graphs[name] = Bar.new( @dir, @data[name], :args => argument[2..-1]) when 'radar' @graphs[name] = Radar.new( @dir, @data[name], :args => argument[2..-1]) when 'radar2' @graphs[name] = Radar2.new( @dir, @data[name], :args => argument[2..-1]) end when 'hide' if argument.length >= 2 name = argument[1] else name = '' end if @graphs.include?(name) @graphs[name].destroy() else return RESPONSE[400] end else return RESPONSE[400] end return RESPONSE[204] end end class Graph WIDTH = 450 HEIGHT = 340 def initialize(dir, data, args: [], limit_min: nil, limit_max: nil) @dir = dir @data = data @args = args @min = limit_min @max = limit_max create_window() @window.show() end def create_window @window = Gtk::Window.new @window.set_title('花柚') # UTF-8 @window.set_decorated(false) @window.set_resizable(false) @window.signal_connect('delete_event') do |w ,e| next delete(w, e) end scrn = Gdk::Screen.default left, top = 0, 0 # XXX scrn_w = scrn.width - left scrn_h = scrn.height - top @x = (left + (scrn_w / 2).to_i) @y = (top + (scrn_h / 4).to_i) @window.move(@x, @y) @darea = Gtk::DrawingArea.new @darea.set_events(Gdk::EventMask::EXPOSURE_MASK| Gdk::EventMask::BUTTON_PRESS_MASK) @darea.signal_connect('draw') do |w ,e| redraw(w, e) next true end @darea.signal_connect('button_press_event') do |w ,e | next button_press(w, e) end @darea.set_size_request(WIDTH, HEIGHT) @darea.show() @window.add(@darea) @darea.realize() @layout = Pango::Layout.new(@darea.pango_context) surface = nil if @data.include?('background.filename') path = File.join( @dir, @data['background.filename'].gsub('\\', '/')) begin surface = Pix.create_surface_from_file(path, is_pnr=0) rescue surface = nil end @surface = surface end end def get_color(target) fail "assert" unless ['font', 'line', 'frame', 'bar', 'background'].include?(target) if target == 'background' r = g = b = 255 # white else r = g = b = 0 # black end name = [target, '.color'].join("") if @data.include?(name) r = @data[name][0..1].to_i(16) g = @data[name][2..3].to_i(16) b = @data[name][4..5].to_i(16) else name_r = [name, '.r'].join("") if @data.include?(name_r) r = @data[name_r].to_i end name_g = [name, '.g'].join("") if @data.include?(name_g) g = @data[name_g].to_i end name_b = [name, '.b'].join("") if @data.include?(name_b) b = @data[name_b].to_i end end return [r / 255.0, g / 255.0, b / 255.0] end def draw_title(widget, cr) return unless @data.has_key?('title') @title = @data['title'] font_size = 12 # pixel @font_desc = Pango::FontDescription.new @font_desc.set_family('Sans') @font_desc.set_size(font_size * Pango::SCALE) cr.set_source_rgb(*get_color('font')) w = widget.allocated_width h = widget.allocated_height cr.translate(w, 0) cr.rotate(Math::PI / 2.0) layout = Pango::Layout.new(widget.pango_context) layout.set_font_description(@font_desc) context = layout.context default_gravity = context.base_gravity # XXX context.base_gravity = Pango::Gravity::EAST # Vertical Text layout.set_text(@title) layout.set_wrap(:word) tw, th = layout.pixel_size cr.move_to(58, w - 20 - th) cr.show_pango_layout(layout) context.base_gravity = default_gravity # XXX end def draw_frame(widget, cr) #pass end def draw_graph(widget, cr) #pass end def redraw(widget, cr) cr.save() cr.set_source_rgb(*get_color('background')) cr.paint() unless @surface.nil? width = @surface.width height = @surface.height xoffset = ((WIDTH - width) / 2).to_i yoffset = ((HEIGHT - height) / 2).to_i cr.set_source(@surface, xoffset, yoffset) cr.set_operator(Cairo::OPERATOR_SOURCE) cr.paint() end cr.restore() cr.save() draw_title(widget, cr) cr.restore() cr.save() draw_frame(widget, cr) cr.restore() cr.save() draw_graph(widget, cr) cr.restore() end def button_press(window, event) case event.event_type when Gdk::EventType::BUTTON_PRESS @window.begin_move_drag( event.button, event.x_root.to_i, event.y_root.to_i, event.time) when Gdk::EventType::DOUBLE_BUTTON_PRESS # double click destroy() end return true end def delete(window, event) return true end def destroy @window.destroy() unless @window.nil? @window = nil @timeout_id = nil end end class Line < Graph def draw_frame(widget, cr) frame_width = 2 if @data.include?('frame.width') frame_width = @data['frame.width'].to_i end cr.set_line_width(frame_width) cr.set_source_rgb(*get_color('frame')) cr.move_to(60, 48) cr.line_to(60, 260) cr.line_to(420, 260) cr.stroke() end def draw_graph(widget, cr) cr.set_source_rgb(*get_color('font')) num = (@args.length / 2).to_i step = (368 / num).to_i for index_ in 0..num-1 @layout.set_text(@args[index_ * 2]) w, h = @layout.pixel_size pos_x = (60 + index_ * step + (step / 2).to_i - (w / 2).to_i) pos_y = 268 cr.move_to(pos_x, pos_y) cr.show_pango_layout(@layout) end unless @min.nil? limit_min = @min else limit_min = @args[1] for index_ in 2..num-1 if @args[index_ * 2] < limit_min limit_min = @args[index_ * 2] end end end unless @max.nil? limit_max = @max else limit_max = @args[1] for index_ in 2..num-1 if @args[index_ * 2] > limit_max limit_max = @args[index_ * 2] end end end line_width = 2 if @data.include?('line.width') line_width = @data['line.width'].to_i end cr.set_line_width(line_width) cr.set_source_rgb(*get_color('line')) for index_ in 1..num-1 src_x = (60 + (index_ - 1) * step + (step / 2).to_i) src_y = (220 - ( 168 * @args[(index_ - 1) * 2 + 1] / (limit_max - limit_min)).to_i) dst_x = (60 + index_ * step + (step / 2).to_i) dst_y = (220 - (168 * @args[index_ * 2 + 1] / (limit_max - limit_min)).to_i) cr.move_to(src_x, src_y) cr.line_to(dst_x, dst_y) cr.stroke() end for index_ in 0..num-1 surface = nil if @args[index_ * 2 + 1] == limit_min and \ @data.include?('mark0.filename') path = File.join( @dir, @data['mark0.filename'].gsub('\\', '/')) if File.exist?(path) begin surface = Pix.create_surface_from_file(path) rescue surface = nil end end elsif @args[index_ * 2 + 1] == limit_max and \ @data.include?('mark2.filename') path = File.join( @dir, @data['mark2.filename'].gsub('\\', '/')) if File.exist?(path) begin surface = Pix.create_surface_from_file(path) rescue surface = nil end end elsif @data.include?('mark1.filename') path = File.join( @dir, @data['mark1.filename'].gsub('\\', '/')) if File.exist?(path) begin surface = Pix.create_surface_from_file(path) rescue surface = nil end end end unless surface.nil? w = surface.width h = surface.height x = (60 + index_ * step + (step / 2).to_i - (w / 2).to_i) y = (220 - ( 168 * @args[index_ * 2 + 1] / (limit_max - limit_min)).to_i - (h / 2).to_i) cr.set_source(surface, x, y) cr.paint() end end end end class Bar < Graph def draw_frame(widget, cr) frame_width = 2 if @data.include?('frame.width') frame_width = @data['frame.width'].to_i end cr.set_line_width(frame_width) cr.set_source_rgb(*get_color('frame')) cr.move_to(60, 48) cr.line_to(60, 260) cr.line_to(420, 260) cr.stroke() end def draw_graph(widget, cr) ## FIXME cr.set_source_rgb(*get_color('bar')) bar_with = 20 ## FIXME if @data.include?('bar.width') bar_width = @data['bar.width'].to_i end ### NOT YET ### end end class Radar < Graph WIDTH = 288 HEIGHT = 288 def initialize(dir, data, args: []) super(dir, data, args) end def draw_frame(widget, cr) frame_width = 2 if @data.include?('frame.width') frame_width = @data['frame.width'].to_i end cr.set_line_width(frame_width) cr.set_source_rgb(*get_color('frame')) num = (@args.length / 2).to_i for index_ in 0..num-1 x = (146 + (Math.cos(Math::PI * (0.5 - 2.0 * index_ / num)) * 114).to_i) y = (146 - (Math.sin(Math::PI * (0.5 - 2.0 * index_ / num)) * 114).to_i) cr.move_to(146, 146,) cr.line_to(x, y) cr.stroke() end end def draw_graph(widget, cr) num = (@args.length / 2).to_i for index_ in 0..num-1 begin value = @args[index_ * 2 + 1] @args[index_ * 2 + 1] = Float(value) rescue @args[index_ * 2 + 1] = 0.0 end if @args[index_ * 2 + 1] < 0 @args[index_ * 2 + 1] = 0.0 end end limit_min = @args[1] for index_ in 0..num-1 if @args[index_ * 2 + 1] < limit_min limit_min = @args[index_ * 2 + 1] end end limit_max = @args[1] for index_ in 0..num-1 if @args[index_ * 2 + 1] > limit_max limit_max = @args[index_ * 2 + 1] end end line_width = 2 if @data.include?('line.width') line_width = @data['line.width'].to_i end cr.set_line_width(line_width) cr.set_source_rgb(*get_color('line')) if limit_max > 0 value = (@args[(num - 1) * 2 + 1] / limit_max) else value = 1.0 end src_x = (146 + (Math.cos( Math::PI * (0.5 - 2.0 * (num - 1) / num)) * value * 100).to_i) src_y = (146 - (Math.sin( Math::PI * (0.5 - 2.0 * (num - 1) / num)) * value * 100).to_i) cr.move_to(src_x, src_y) for index_ in 0..num-1 if limit_max > 0 value = (@args[index_ * 2 + 1] / limit_max) else value = 1.0 end dst_x = (146 + ( Math.cos(Math::PI * (0.5 - 2.0 * index_ / num)) * value * 100).to_i) dst_y = (146 - ( Math.sin(Math::PI * (0.5 - 2.0 * index_ / num)) * value * 100).to_i) cr.line_to(dst_x, dst_y) end cr.stroke() font_size = 9 # pixel @font_desc.set_size(font_size * Pango::SCALE) @layout.set_font_description(@font_desc) cr.set_source_rgb(*get_color('font')) for index_ in 0..num-1 ##if limit_max > 0 ## value = (@args[index_ * 2 + 1] / limit_max) ##else ## value = 1.0 ##end value = 1.2 # XXX x = (146 + (Math.cos( Math::PI * (0.5 - 2.0 * index_ / num)) * value * 100).to_i) y = (146 - (Math.sin( Math::PI * (0.5 - 2.0 * index_ / num)) * value * 100).to_i) @layout.set_text(@args[index_ * 2]) w, h = @layout.pixel_size x -= (w / 2).to_i y -= (h / 2).to_i cr.move_to(x, y) cr.show_pango_layout(@layout) end end end class Radar2 < Graph WIDTH = 288 HEIGHT = 288 def initialize(dir, data, args: []) super(dir, data, args) end end end ninix-aya-5.0.9/lib/ninix/dll/niseshiori.rb0000644000175000017500000011351213416507430016767 0ustar shyshy# -*- coding: utf-8 -*- # # niseshiori.rb - a "偽栞" compatible Shiori module for ninix # Copyright (C) 2001, 2002 by Tamito KAJIYAMA # Copyright (C) 2002, 2003 by MATSUMURA Namihiko # Copyright (C) 2002-2019 by Shyouzou Sugitani # Copyright (C) 2003 by Shun-ichi TAHARA # # This program is free software; you can redistribute it and/or modify it # under the terms of the GNU General Public License (version 2) as # published by the Free Software Foundation. It is distributed in the # hope that it will be useful, but WITHOUT ANY WARRANTY; without even the # implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR # PURPOSE. See the GNU General Public License for more details. # require_relative "../logging" module Niseshiori REVISION = '$Revision: 1.28 $' ## FIXME # tree node types ADD_EXPR = 1 MUL_EXPR = 2 UNARY_EXPR = 3 PRIMARY_EXPR = 4 def self.list_dict(top_dir) buf = [] begin filelist = [] Dir.foreach(top_dir, :encoding => 'UTF-8') do |file| next if file == '..' or file == '.' filelist << file end rescue #except OSError: filelist = [] end re_dict_filename = Regexp.new('\Aai.*\.(dtx|txt)\z') for filename in filelist unless re_dict_filename.match(filename).nil? buf << File.join(top_dir, filename) end end return buf end class NiseShiori def initialize @dict = {} @type_chains = {} @word_chains = {} @keywords = {} @responses = {} @greetings = {} @events = {} @resources = {} @variables = {} @dbpath = nil @expr_parser = ExprParser.new() @username = '' @ai_talk_interval = 180 @ai_talk_count = 0 @surf0 = 0 @surf1 = 10 @event = nil @reference = nil @jump_entry = nil @motion_area = nil @motion_count = 0 @mikire = 0 @kasanari = 0 @otherghost = [] @to = '' @sender = '' end def load(top_dir) # read dictionaries dictlist = Niseshiori.list_dict(top_dir) return 1 if dictlist.empty? for path in dictlist read_dict(path) end # read variables @dbpath = File.join(top_dir, 'niseshiori.db') load_database(@dbpath) return 0 end def load_database(path) begin open(path, encoding='utf-8') do |f| dic = {} for line in f if line.start_with?('# ns_st: ') begin @ai_talk_interval = Integer(line[9..-1]) rescue #except ValueError: #pass end next elsif line.start_with?('# ns_tn: ') @username = line[9..-1].strip() next end begin name, value = line.split('=', 2) name.strip! value.strip! rescue #except ValueError: Logging::Logging.error( 'niseshiori.py: malformed database (ignored)') return end dic[name] = value end end rescue #except IOError: return end @variables = dic end def save_database return if @dbpath.nil? begin open(@dbpath, 'w') do |f| f.write('# ns_st: ' + @ai_talk_interval.to_i.to_s + "\n") f.write('# ns_tn: ' + @username.to_s + "\n") for name in @variables.keys value = @variables[name] unless name.start_with?('_') f.write(name.to_s + '=' + value.to_s + "\n") end end end rescue #except IOError: Logging::Logging.error('niseshiori.py: cannot write database (ignored)') return end end def finalize #pass end Re_type = Regexp.new('\A\\\(m[szlchtep?]|[dk])') Re_user = Regexp.new('\A\\\u[a-z]') Re_category = Regexp.new('\A\\\(m[szlchtep]|[dk])?\[([^\]]+)\]') def read_dict(path) # read dict file and decrypt if necessary buf = [] open(path, 'rb') do |f| basename = File.basename(path, ".*") ext = File.extname(path) ext = ext.downcase if ext == '.dtx' buf = decrypt(f.read()) else buf = f.readlines() end end # omit empty lines and comments and merge continuous lines definitions = [] decode = lambda {|line| line.force_encoding('CP932').encode('UTF-8', :invalid => :replace, :undef => :replace) } in_comment = false i, j = 0, buf.length while i < j line = buf[i].strip() i += 1 next if line.empty? if i == 1 and line.start_with?('#Charset:') charset = line[9..-1].force_encoding('ascii').strip() if ['UTF-8', 'EUC-JP', 'EUC-KR'].include?(charset) # XXX decode = lambda {|line| line.force_encoding(charset).encode('UTF-8', :invalid => :replace, :undef => :replace) } end next elsif line.start_with?('/*') in_comment = true next elsif line.start_with?('*/') in_comment = false next elsif in_comment or line.start_with?('#') or line.start_with?('//') next end lines = [line] while i < j and not buf[i].nil? and \ (buf[i].start_with?(' ') or buf[i].start_with?("\t")) lines << buf[i].strip() i += 1 end definitions << lines.join('') end # parse each line for line in definitions line = decode.call(line) # special case: words in a category match = Re_category.match(line) unless match.nil? line = line[match.end(0)..-1].strip() if line.empty? or not line.start_with?(',') syntax_error(path, line) next end words = split(line).map {|s| s.strip unless s.strip.empty? } words.delete(nil) cattype = match.to_a[1] catlist = match.to_a[2..-1] for cat in catlist.map {|s| s.strip } if cattype.nil? keylist = [[nil, cat]] else keylist = [[nil, cat], [cattype, cat]] end for key in keylist value = (@dict.include?(key) ? @dict[key] : []) value.concat(words) @dict[key] = value end end unless cattype.nil? key = ['\\', cattype].join('') value = (@dict.include?(key) ? @dict[key] : []) value.concat(words) @dict[key] = value end next end # other cases begin command, argv = split(line, :maxcount => 1) command.strip! argv.strip! rescue #except ValueError: syntax_error(path, line) next end if command == '\ch' argv = split(argv).map {|s| s.strip } case argv.length when 5 t1, w1, t2, w2, c = argv when 4 t1, w1, t2, w2 = argv c = nil when 3 t1, w1, c = argv t2 = w2 = nil else syntax_error(path, line) next end if Re_type.match(t1).nil? and Re_user.match(t1).nil? syntax_error(path, line) next end unless c.nil? ch_list = (@type_chains.include?(t1) ? @type_chains[t1] : []) ch_list << [c, w1] @type_chains[t1] = ch_list end next if t2.nil? if Re_type.match(t2).nil? and Re_user.match(t2).nil? syntax_error(path, line) next end unless c.nil? ch_list = (@type_chains.include?(t2) ? @type_chains[t2] : []) ch_list << [c, w2] @type_chains[t2] = ch_list end m1 = ['%', t1[1..-1]].join('') m2 = ['%', t2[1..-1]].join('') key = [m1, w1] dic = (@word_chains.include?(key) ? @word_chains[key] : {}) ch_list = (dic.include?(m2) ? dic[m2] : []) unless ch_list.include?([c, w2]) ch_list << [c, w2] dic[m2] = ch_list @word_chains[key] = dic end key = [m2, w2] dic = (@word_chains.include?(key) ? @word_chains[key] : {}) ch_list = (dic.include?(m1) ? dic[m1] : []) unless ch_list.include?([c, w1]) ch_list << [c, w1] dic[m1] = ch_list @word_chains[key] = dic end ch_list = (@dict.include?(t1) ? @dict[t1] : []) unless ch_list.include?(w1) ch_list << w1 @dict[t1] = ch_list end ch_list = (@dict.include?(t2) ? @dict[t2] : []) unless ch_list.include?(w2) ch_list << w2 @dict[t2] = ch_list end elsif not Re_type.match(command).nil? or not Re_user.match(command).nil? words = split(argv).map {|s| s.strip unless s.strip.empty? } words.delete(nil) value = (@dict.include?(command) ? @dict[command] : []) value.concat(words) @dict[command] = value elsif ['\dms', '\e'].include?(command) value = (@dict.include?(command) ? @dict[command] : []) value << argv @dict[command] = value elsif command == '\ft' argv = split(argv, :maxcount => 2).map {|s| s.strip } if argv.length != 3 syntax_error(path, line) next end w, t, s = argv if Re_type.match(t).nil? syntax_error(path, line) next end @keywords[[w, t]] = s elsif command == '\re' argv = split(argv, :maxcount => 1).map {|s| s.strip } if argv.length == 2 cond = parse_condition(argv[0]) re_list = (@responses.include?(cond) ? @responses[cond] : []) re_list << argv[1] @responses[cond] = re_list end elsif command == '\hl' argv = split(argv, :maxcount => 1).map {|s| s.strip } if argv.length == 2 hl_list = (@greetings.include?(argv[0]) ? @greetings[argv[0]] : []) hl_list << argv[1] @greetings[argv[0]] = hl_list end elsif command == '\ev' argv = split(argv, :maxcount => 1).map {|s| s.strip } if argv.length == 2 cond = parse_condition(argv[0]) ev_list = (@events.include?(cond) ? @events[cond] : []) ev_list << argv[1] @events[cond] = ev_list end elsif command == '\id' argv = split(argv, :maxcount => 1).map {|s| s.strip } if argv.length == 2 if ['sakura.recommendsites', 'kero.recommendsites', 'sakura.portalsites'].include?(argv[0]) id_list = (@resources.include?(argv[0]) ? @resources[argv[0]] : '') unless id_list.empty? id_list = [id_list, "\2"].join('') end id_list = [id_list, argv[1].gsub(' ', "\1")].join('') @resources[argv[0]] = id_list else @resources[argv[0]] = argv[1] end end elsif command == '\tc' #pass else syntax_error(path, line) end end end def split(line, maxcount: nil) buf = [] count = 0 end_ = pos = 0 while maxcount.nil? or count < maxcount pos = line.index(',', pos) if pos.nil? or pos < 0 break elsif pos > 0 and line[pos - 1] == '\\' pos += 1 else unless pos.zero? buf << line[end_..pos-1] count += 1 end end end_ = pos = (pos + 1) end buf << line[end_..-1] return buf.map {|s| s.gsub('\\,', ',') } end def syntax_error(path, line) Logging::Logging.debug( 'niseshiori.py: syntax error in ' + File.basename(path, ".*")) Logging::Logging.debug(line) end Re_comp_op = Regexp.new('<[>=]?|>=?|=') COND_COMPARISON = 1 COND_STRING = 2 def parse_condition(condition) buf = [] for expr in condition.split('&', -1).map {|s| s.strip } match = Re_comp_op.match(expr) unless match.nil? buf << [COND_COMPARISON, [ expr[0..match.begin(0)-1].strip(), match.to_s, expr[match.end(0)..-1].strip()]] else buf << [COND_STRING, expr] end end return buf end def decrypt(data) buf = [] a = 0x61 i = 0 j = data.length line = [] while i < j if data[i].ord == 64 # == '@'[0] i += 1 buf << line.join('') line = [] next end y = data[i].ord i += 1 x = data[i].ord i += 1 x -= a a += 9 y -= a a += 2 a = 0x61 if a > 0xdd line << ((x & 0x03) | ((y & 0x03) << 2) | \ ((y & 0x0c) << 2) | ((x & 0x0c) << 4)).chr end return buf end def getaistringrandom result = get_event_response('OnNSRandomTalk') result = get('\e') if result.nil? or result.empty? return result end def get_event_response(event, ref0=nil, ref1=nil, ref2=nil, ref3=nil, ref4=nil, ref5=nil, ref6=nil, ref7=nil) ref = [ref0, ref1, ref2, ref3, ref4, ref5, ref6, ref7] if ['OnSecondChange', 'OnMinuteChange'].include?(event) if ref[1] == 1 @mikire += 1 script = get_event_response('OnNSMikireHappen') return script unless script.nil? elsif @mikire > 0 @mikire = 0 return get_event_response('OnNSMikireSolve') end if ref[2] == 1 @kasanari += 1 script = get_event_response('OnNSKasanariHappen') return script unless script.nil? elsif @kasanari > 0 @kasanari = 0 return get_event_response('OnNSKasanariHappen') end if event == 'OnSecondChange' and @ai_talk_interval > 0 @ai_talk_count += 1 if @ai_talk_count == @ai_talk_interval @ai_talk_count = 0 if not @otherghost.empty? and \ (0..10).to_a.sample.zero? target = [] for name, s0, s1 in @otherghost if @greetings.include?(name) target << name end end unless target.empty? @to = target.sample end unless @to.empty? @current_time = Time.now s = @greetings[@to].sample unless s.empty? s = replace_meta(s) while true match = Re_ns_tag.match(s) break if match.nil? value = eval_ns_tag(match.to_s) s = [s[0..match.begin(0)-1], value.to_s, s[match.end(0)..-1]].join('') end end return s end end return getaistringrandom() end end return nil elsif event == 'OnMouseMove' if @motion_area != [ref[3], ref[4]] @motion_area = [ref[3], ref[4]] @motion_count = 0 else @motion_count += 5 # sensitivity end elsif event == 'OnSurfaceChange' @surf0 = ref[0] @surf1 = ref[1] elsif event == 'OnUserInput' and ref[0] == 'ninix.niseshiori.username' @username = ref[1] save_database() return '\e' elsif event == 'OnCommunicate' @event = '' @reference = [ref[1]] if ref[0] == 'user' @sender = 'User' else @sender = ref[0] end candidate = [] for cond in @responses.keys if eval_condition(cond) candidate << cond end end script = nil @to = ref[0] unless candidate.empty? cond = candidate.sample script = @responses[cond].sample end if (script.nil? or script.empty?) and @responses.include?('nohit') script = @responses['nohit'].sample end if script.nil? or script.empty? @to = '' else script = replace_meta(script) while true match = Re_ns_tag.match(script) break if match.nil? value = eval_ns_tag(match.to_s) script = [script[0..match.begin(0)-1], value.to_s, script[match.end(0)..-1]].join('') end end @sender = '' return script end key = 'action' @dict[key] = [] @event = event @reference = ref @current_time = Time.now for condition in @events.keys actions = @events[condition] if eval_condition(condition) @dict[key].concat(actions) end end script = get(key, :default => nil) @ai_talk_count = 0 unless script.nil? @event = nil @reference = nil return script # unless script.nil? end def otherghostname(ghost_list) @otherghost = [] for ghost in ghost_list name, s0, s1 = ghost.split(1.chr, 3) @otherghost << [name, s0, s1] end return '' end def communicate_to to = @to @to = '' return to end def eval_condition(condition) for cond_type, expr in condition return false unless eval_conditional_expression(cond_type, expr) end return true end def eval_conditional_expression(cond_type, expr) case cond_type when COND_COMPARISON value1 = expand_meta(expr[0]) value2 = expr[2] begin op1 = Integer(value1) op2 = Integer(value2) rescue #except ValueError: op1 = value1.to_s op2 = value2.to_s end case expr[1] when '>=' return op1 >= op2 when '<=' return op1 <= op2 when '>' return op1 > op2 when '<' return op1 < op2 when '=' return op1 == op2 when '<>' return op1 != op2 end when COND_STRING return true if @event.include?(expr) for ref in @reference return true if not ref.nil? and ref.to_s.include?(expr) end return false else return false end end Re_ns_tag = Regexp.new('\\\(ns_(st(\[[0-9]+\])?|cr|hl|rf\[[^\]]+\]|ce|tc\[[^\]]+\]|tn(\[[^\]]+\])?|jp\[[^\]]+\]|rt)|set\[[^\]]+\])') def get(key, default: '') @current_time = Time.now s = expand(key, '', :default => default) unless s.nil? or s.empty? while true match = Re_ns_tag.match(s) break if match.nil? value = eval_ns_tag(match.to_s) s = [s[0..match.begin(0)-1], value.to_s, s[match.end(0)..-1]].join('') end end return s end def eval_ns_tag(tag) value = '' if tag == '\ns_cr' @ai_talk_count = 0 elsif tag == '\ns_ce' @to = '' elsif tag == '\ns_hl' if not @otherghost.empty? @to = @otherghost.sample[0] elsif tag.start_with?('\ns_st[') and tag.end_with?(']') begin num = Integer(tag[7..-2]) rescue #except ValueError: @pass else case num when 0 @ai_talk_interval = 0 when 1 @ai_talk_interval = 420 when 2 @ai_talk_interval = 180 when 3 @ai_talk_interval = 60 else @ai_talk_interval = [[num, 4].max, 999].min end save_database() @ai_talk_count = 0 end elsif tag.start_with?('\ns_jp[') and tag.end_with?(']') name = tag[7..-2] @jump_entry = name value = get_event_response('OnNSJumpEntry') value = '' if value.nil? or value.empty? @jump_entry = nil elsif tag.start_with?('\set[') and tag.end_with?(']') statement = tag[5..-2] unless statement.include?('=') Logging::Logging.debug('niseshiori.py: syntax error') Logging::Logging.debug(tag) else name, expr = statement.split('=', :maxcount => 2) name.strip! expr .strip! value = eval_expression(expr) unless value.nil? @variables[name] = value unless name.start_with?('_') save_database() end Logging::Logging.debug('set ' + name.to_s + ' = "' + value.to_s + '"') else Logging::Logging.debug('niseshiori.py: syntax error') Logging::Logging.debug(tag) end end end elsif tag == '\ns_rt' value = '\a' elsif tag == '\ns_tn' value = '\![open,inputbox,ninix.niseshiori.username,-1]' elsif tag.start_with?('\ns_tn[') and tag.end_with?(']') @username = tag[7..-2] save_database() end return value end Re_meta = Regexp.new('%((rand([1-9]|\[[0-9]+\])|selfname2?|sakuraname|keroname|username|friendname|year|month|day|hour|minute|second|week|ghostname|sender|ref[0-7]|surf[01]|word|ns_st|get\[[^\]]+\]|jpentry|plathome|platform|move|mikire|kasanari|ver)|(dms|(m[szlchtep?]|[dk])(\[[^\]]*\])?|\[[^\]]*\]|u[a-z])([2-9]?))') def replace_meta(s) pos = 0 buf = [] variables = {} variable_chains = [] while true match = Re_meta.match(s, pos) if match.nil? buf << s[pos..-1] break end unless match.begin(0).zero? buf << s[pos..match.begin(0)-1] end meta = match.to_s unless match[4].nil? # %ms, %dms, %ua, etc. unless variables.include?(meta) chained_meta = ['%', match[4]].join('') break_flag = false for chains in variable_chains if chains.include?(meta) candidates_A, candidates_B = chains[chained_meta] unless candidates_A.empty? word = candidates_A.sample candidates_A.delete(word) else word = candidates_B.sample candidates_B.delete(word) end if candidates_A.empty? and candidates_B.empty? chains.delete(chained_meta) end Logging::Logging.debug('chained: ' + meta.to_s + ' => ' + word.to_s) break_flag = true break end end unless break_flag if match[4] == 'm?' word = expand( ['\\', ['ms', 'mz', 'ml', 'mc', 'mh', 'mt', 'me', 'mp'].sample].join(''), s) else word = expand( ['\\', match[4]].join(''), s) end end chains = find_chains([chained_meta, word], s) prefix = 'chain:' for k in chains.keys candidates_A, candidates_B = chains[k] for w in candidates_A Logging::Logging.debug(prefix.to_s + ' ' + k.to_s + ', ' + w.to_s) prefix = ' ' end for w in candidates_B Logging::Logging.debug(prefix.to_s + ' ' + k.to_s + ', ' + w.to_s) prefix = ' ' end end variables[meta] = word variable_chains << chains end buf << variables[meta] else buf << expand_meta(meta).to_s end pos = match.end(0) end t = buf.join('') return t end def expand(key, context, default: '') choices = [] for keyword, word in (@type_chains.include?(key) ? @type_chains[key] : []) if context.include?(keyword) Logging::Logging.debug('chain keyword: ' + keyword.to_s) choices << word end end if choices.empty? match = Re_category.match(key) key = match.to_a[1..2] unless match.nil? choices = @dict[key] end if choices.nil? or choices.empty? if key.is_a?(Array) key = ('(' + key[0].to_s + ', ' + key[1..-1].to_s + ')') end Logging::Logging.debug(key.to_s + ' not found') return default end s = choices.sample t = replace_meta(s) if key.is_a?(Array) if key[0].nil? key = ('\\[' + key[1].to_s + ']') else key = ('\\' + key[0].to_s + '[' + key[1].to_s + ']') end end Logging::Logging.debug([key, '=>', s].join('')) Logging::Logging.debug([' ' * key.length, '=>', t].join('')) return t end def find_chains(key, context) chains = {} dic = @word_chains.include?(key) ? @word_chains[key] : {} for chained_meta in dic.keys candidates_A = [] candidates_B = [] for keyword, chained_word in dic[chained_meta] if not keyword.nil? and context.include?(keyword) candidates_A << chained_word else candidates_B << chained_word end end chains[chained_meta] = [candidates_A, candidates_B] end return chains end WEEKDAY_NAMES = ['sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat'] def expand_meta(name) if ['%selfname', '%selfname2', '%keroname', '%friendname'].include?(name) result = name elsif name == '%sakuraname' result = '%selfname' elsif name == '%username' result = (@username or '%username') elsif name.start_with?('%get[') and name.end_with?(']') value = (@variables.include?(name[5..-2]) ? @variables[name[5..-2]] : '?') begin result = Integer(value) rescue #except ValueError: result = value end elsif name.start_with?('%rand[') and name.end_with?(']') n = name[6..-2].to_i result = (1..n).to_a.sample elsif name.start_with?('%rand') and name.length == 6 n = name[5].to_i result = (10**(n - 1)..10**n - 1).to_a.sample elsif name.start_with?('%ref') and name.length == 5 and \ '01234567'.include?(name[4]) if @reference.nil? result = '' else n = name[4].to_i if @reference[n].nil? result = '' else result = @reference[n] end end elsif name == '%jpentry' if @jump_entry.nil? result = '' else result = @jump_entry end elsif name == '%year' result = @current_time.year.to_s elsif name == '%month' result = @current_time.month.to_s elsif name == '%day' result = @current_time.day.to_s elsif name == '%hour' result = @current_time.hour.to_s elsif name == '%minute' result = @current_time.min.to_s elsif name == '%second' result = @current_time.to_s elsif name == '%week' result = WEEKDAY_NAMES[@current_time.wday] elsif name == '%ns_st' result = @ai_talk_interval elsif name == '%surf0' result = @surf0.to_s elsif name == '%surf1' result = @surf1.to_s elsif ['%plathome', '%platform'].include?(name) ## FIXME result = 'ninix' elsif name == '%move' result = @motion_count.to_s elsif name == '%mikire' result = @mikire.to_s elsif name == '%kasanari' result = @kasanari.to_s elsif name == '%ver' ## FIXME if REVISION[1..10] == 'Revision: ' result = ('偽栞 for ninix (rev.' + REVISION[11..-3] + ')') else result = '偽栞 for ninix' end elsif name == '%sender' unless @sender.empty? result = @sender else result = '' end elsif name == '%ghost' if not @to.empty? result = @to elsif not @otherghost.empty? result = @otherghost.sample[0] else result = '' end else result = ['\\', name].join('') end return result end def eval_expression(expr) tree = @expr_parser.parse(expr) return tree.nil? ? nil : interp_expr(tree) end def __interp_add_expr(tree) value = interp_expr(tree[0]) 1.step(tree.length-1, 2) do |i| operand = interp_expr(tree[i + 1]) begin if tree[i] == '+' value = (Integer(value) + Integer(operand)) elsif tree[i] == '-' value = (Integer(value) - Integer(operand)) end rescue #except ValueError: value = [value, tree[i], operand].join('') end end return value end def __interp_mul_expr(tree) value = interp_expr(tree[0]) 1.step(tree.length-1, 2) do |i| operand = interp_expr(tree[i + 1]) begin case tree[i] when '*' value = (Integer(value) * Integer(operand)) when '/' value = (Integer(value) / Integer(operand)).to_i when '\\' value = (Integer(value) % Integer(operand)) end rescue #except (ValueError, ZeroDivisionError): value = [value, tree[i], operand].join('') end end return value end def __interp_unary_expr(tree) operand = interp_expr(tree[1]) begin case tree[0] when '+' return Integer(operand) when '-' return - Integer(operand) end rescue #except ValueError: return [tree[0], operand].join('') end end def __interp_primary_expr(tree) if is_number(tree[0]) return tree[0].to_i elsif tree[0].start_with?('%') return expand_meta(tree[0]) end begin return @variables[tree[0]] rescue #except KeyError: return '?' end end __expr = { ADD_EXPR => '__interp_add_expr', MUL_EXPR => '__interp_mul_expr', UNARY_EXPR => '__interp_unary_expr', PRIMARY_EXPR => '__interp_primary_expr', } def interp_expr(tree) key = tree[0] if @__expr.include?(key) return method(@__expr[key]).call(self, tree[1..-1]) else fail RuntimeError('should not reach here') end end def is_number(s) return (not s.nil? and not s.empty? and s.chars.map.all? {|c| '0123456789'.include?(c) }) end end class ExprError < StandardError # ValueError #pass end class ExprParser def initialize #pass end def show_progress(func, buf) if buf.nil? Logging::Logging.debug(func.to_s + '() -> syntax error') else Logging::Logging.debug(func.to_s + '() -> ' + buf.to_s) end end Re_token_A = Regexp.new('\A[()*/\+-]|\d+|\s+') Re_token_B = Regexp.new('[()*/\+-]|\d+|\s+') def tokenize(data) buf = [] end_ = 0 while true match = NiseShiori::Re_meta.match(data, end_) unless match.nil? buf << match.to_s end_ = match.end(0) next end match = Re_token_A.match(data, end_) unless match.nil? unless match.to_s.strip().empty? buf << match.to_s end end_ = match.end(0) next end match = Re_token_B.match(data, end_) unless match.nil? buf << data[end_..match.begin(0)-1] unless match.to_s.strip().empty? buf << match.to_s end end_ = match.end(0) else if end_ < data.length buf << data[end_..-1] end break end end return buf end def parse(data) @tokens = tokenize(data) begin return get_expr() rescue #except ExprError: return nil # syntax error end end # internal def done @tokens.empty? end def pop begin return @tokens.shift rescue #except IndexError: raise ExprError end end def look_ahead(index: 0) begin return @tokens[index] rescue #except IndexError: raise ExprError end end def match(s) fail ExprError if pop() != s end def get_expr buf = get_add_expr() fail ExprError unless done() show_progress('get_expr', buf) return buf end def get_add_expr buf = [ADD_EXPR] while true buf << get_mul_expr() break if done() or not ['+', '-'].include?(look_ahead()) buf << pop() # operator end buf = buf[1] if buf.length == 2 show_progress('get_add_expr', buf) return buf end def get_mul_expr buf = [MUL_EXPR] while true buf << get_unary_expr() break if done() or not ['*', '/', '\\'].include?(look_ahead()) buf << pop() # operator end buf = buf[1] if buf.length == 2 show_progress('get_mul_expr', buf) return buf end def get_unary_expr if ['+', '-'].include?(look_ahead()) buf = [UNARY_EXPR, pop(), get_unary_expr()] else buf = get_primary_expr() end show_progress('get_unary_expr', buf) return buf end def get_primary_expr if look_ahead() == '(' match('(') buf = get_add_expr() match(')') else buf = [PRIMARY_EXPR, pop()] end show_progress('get_primary_expr', buf) return buf end end # <<< EXPRESSION SYNTAX >>> # expr := add-expr # add-expr := mul-expr (add-op mul-expr)* # add-op := '+' | '-' # mul-expr := unary-expr (mul-op unary-expr)* # mul-op := '*' | '/' | '\' # unary-expr := unary-op unary-expr | primary-expr # unary-op := '+' | '-' # primary-expr := identifier | constant | '(' add-expr ')' class Shiori < NiseShiori attr_reader :dict, :type_chains, :word_chains, :keywords, :responses, :greetings, :events def initialize(dll_name) super() @dll_name = dll_name end def load(dir: nil) super(dir) return 1 end def unload finalize end def find(top_dir, dll_name) result = 0 result = 100 unless Niseshiori.list_dict(top_dir).empty? return result end def show_description Logging::Logging.info( "Shiori: NiseShiori compatible module for ninix\n" \ " Copyright (C) 2001, 2002 by Tamito KAJIYAMA\n" \ " Copyright (C) 2002, 2003 by MATSUMURA Namihiko\n" \ " Copyright (C) 2002-2019 by Shyouzou Sugitani\n" \ " Copyright (C) 2003 by Shun-ichi TAHARA") end def request(req_string) header = req_string.split(/\r?\n/, 0) req_header = {} line = header.shift unless line.empty? line = line.strip() req_list = line.split(nil, -1) if req_list.length >= 2 command = req_list[0].strip() protocol = req_list[1].strip() end for line in header line = line.encode('utf-8', :invalid => :replace, :undef => :replace).strip() next if line.empty? next unless line.include?(':') key, value = line.split(':', 2) key.strip! value.strip! begin value = Integer(value) rescue #pass end req_header[key] = value end end result = '' to = nil if req_header.include?('ID') if req_header['ID'] == 'dms' result = get('\dms') elsif req_header['ID'] == 'OnAITalk' result = getaistringrandom() elsif ['\\ms', '\\mz', '\\ml', '\\mc', '\\mh', '\\mt', '\\me', '\\mp'].include?(req_header['ID']) result = get(req_header['ID']) elsif req_header['ID'] == '\\m?' result = get(['\\ms', '\\mz', '\\ml', '\\mc', '\\mh', '\\mt', '\\me', '\\mp'].sample) elsif req_header['ID'] == 'otherghostname' otherghost = [] for n in 0..127 if req_header.include?(['Reference', n.to_s].join('')) otherghost << req_header[['Reference', n.to_s].join('')] end end result = otherghostname(otherghost) elsif req_header['ID'] == 'OnTeach' if req_header.include?('Reference0') #teach(req_header['Reference0']) #pass ## FIXME end else result = @resources[req_header['ID']] if result.nil? ref = [] for n in 0..7 if req_header.include?(['Reference', n.to_s].join('')) ref << req_header[['Reference', n.to_s].join('')] else ref << nil end end ref0, ref1, ref2, ref3, ref4, ref5, ref6, ref7 = ref result = get_event_response( req_header['ID'], ref0, ref1, ref2, ref3, ref4, ref5, ref6, ref7) end end result = '' if result.nil? to = communicate_to() end result = ["SHIORI/3.0 200 OK\r\n", "Sender: Niseshiori\r\n", "Charset: UTF-8\r\n", "Value: ", result, "\r\n"].join("") unless to.nil? result = [result, "Reference0: ", to, "\r\n"].join("") end result = [result, "\r\n"].join("") return result.encode('utf-8', :invalid => :replace, :undef => :replace) end end end ninix-aya-5.0.9/lib/ninix/dll/aya5.rb0000644000175000017500000036130613416507430015460 0ustar shyshy# -*- coding: utf-8 -*- # # aya5.rb - an aya.dll(Ver.5) compatible Shiori module for ninix # Copyright (C) 2002-2019 by Shyouzou Sugitani # Copyright (C) 2002, 2003 by MATSUMURA Namihiko # # This program is free software; you can redistribute it and/or modify it # under the terms of the GNU General Public License (version 2) as # published by the Free Software Foundation. It is distributed in the # hope that it will be useful, but WITHOUT ANY WARRANTY; without even the # implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR # PURPOSE. See the GNU General Public License for more details. # # # TODO: # - 文字列内埋め込み要素の展開の動作が非互換. # - システム関数: # - たくさん. require_relative "../home" require_relative "../logging" module Aya5 class AyaError < StandardError # XXX #pass end def self.encrypt_char(char) c = char[0].ord j = 0 while j < 3 msb = (c & 0x80) c <<= 1 c &= 0xff unless msb.zero? c |= 0x01 else c &= 0xfe end j += 1 end c ^= 0xd2 return c.chr end def self.decrypt_char(char) c = char[0].ord c ^= 0xd2 j = 0 while j < 3 lsb = (c & 0x01) c >>= 1 unless lsb.zero? c |= 0x80 else c &= 0x7f end j += 1 end return c.chr end def self.decrypt_readline(f) line = '' while true c = f.read(1) break if c == '' line = [line, Aya5.decrypt_char(c)].join('') if line.end_with?(10.chr) or \ line.end_with?(0xda.chr) break end end return line end def self.find_not_quoted(line, token) position = 0 while true pos_new = line.index(token, position) if pos_new.nil? pos_new = -1 break elsif pos_new.zero? break end position = line.index('"', position) if not position.nil? and 0 <= position and position < pos_new position += 1 while position < line.length - 1 if line[position] == '"' position += 1 break else position += 1 next end end else break end end return pos_new end def self.find_comment(line) if line.start_with?("//") return 0, line.length end start = line.length # not line.length - 1 end_ = -1 for token in ["//", "/*"] pos_new = Aya5.find_not_quoted(line, token) if 0 <= pos_new and pos_new < start start = pos_new if token == "/*" end_ = Aya5.find_not_quoted(line, "*/") if end_ >= 0 end_ += 2 end else end_ = line.length end end end if start == line.length start = -1 end return start, end_ end def self.get_aya_version(filelist) return 0 if filelist.empty? dic_files = filelist for filename in dic_files if filename.downcase.end_with?('_shiori3.dic') # XXX open(filename, 'rb', :encoding => 'CP932') do |f| for line in f begin line = line.encode("UTF-8", :invalid => :replace, :undef => :replace) v4 = line.index('for 文 version 4') v5 = line.index('for AYA5') if not v4.nil? and v4 > 0 return 4 elsif not v5.nil? and v5 > 0 return 5 end rescue return 5 end end end end end return 3 end def self.find_dict(aya_dir, f) comment = 0 dic_files = [] for line in f line = line.encode("UTF-8", :invalid => :replace, :undef => :replace) unless comment.zero? end_ = Aya5.find_not_quoted(line, '*/') next if end_ < 0 line = line[end_ + 2..-1] comment = 0 end while true start, end_ = Aya5.find_comment(line) break if start < 0 line = "" if start.zero? if end_ < 0 comment = 1 line = line[0..start-1] break end line = [line[0..start-1], line[end_..-1]].join(' ') end line = line.strip() next if line.empty? next unless line.include?(',') key, value = line.split(',', 2) key.strip! value.strip! if key == 'dic' filename = Home.get_normalized_path(value) path = File.join(aya_dir, filename) dic_files << path end end return dic_files end def self.check_version(top_dir, dll_name) filename = nil if File.file?(File.join(top_dir, 'aya.txt')) filename = File.join(top_dir, 'aya.txt') elsif File.file?(File.join(top_dir, 'yaya.txt')) return 6 # XXX: YAYA elsif not dll_name.nil? and \ File.file?(File.join(top_dir, [dll_name[0..-4], 'txt'].join(''))) filename = File.join(top_dir, [dll_name[0..-4], 'txt'].join('')) end version = 0 unless filename.nil? open(filename, :encoding => 'CP932') do |f| version = Aya5.get_aya_version(Aya5.find_dict(top_dir, f)) end end return version end class Shiori attr_reader :charset, :dbpath, :dic, :aya_dir, :saori_library def initialize(dll_name) @dll_name = dll_name unless dll_name.nil? @__AYA_TXT = [dll_name[0..-4], 'txt'].join('') @__DBNAME = [dll_name[0..-5], '_variable.cfg'].join('') else @__AYA_TXT = 'aya5.txt' @__DBNAME = 'aya5_variable.cfg' end @saori = nil @dic_files = [] end def use_saori(saori) @saori = saori end def find(top_dir, dll_name) result = 0 version = Aya5.check_version(top_dir, dll_name) if version == 5 result = 200 elsif version == 6 @__AYA_TXT = 'yaya.txt' @__DBNAME = 'yaya_variable.cfg' result = 100 end return result end def show_description Logging::Logging.info( "Shiori: AYA5 compatible module for ninix\n" \ " Copyright (C) 2002-2019 by Shyouzou Sugitani\n" \ " Copyright (C) 2002, 2003 by MATSUMURA Namihiko") end def reset @boot_time = Time.new @aitalk = 0 @dic_files = [] @dic = AyaDictionary.new(self) @global_namespace = AyaGlobalNamespace.new(self) @system_functions = AyaSystemFunctions.new(self) @logfile = nil @filelist = {} end def load(dir: nil) @aya_dir = dir @dbpath = File.join(@aya_dir, @__DBNAME) @saori_library = AyaSaoriLibrary.new(@saori, @aya_dir) reset() begin path = File.join(@aya_dir, @__AYA_TXT) open(path, :encoding => 'CP932') do |aya_txt| load_aya_txt(aya_txt) end rescue #except IOError: Logging::Logging.debug('cannot read aya.txt') return 0 rescue #except AyaError as error: Logging::Logging.debug(error) return 0 end @global_namespace.load_database(self) # default setting unless @global_namespace.exists('log') @global_namespace.put('log', '') end for path in @dic_files basename = File.basename(path, '.*') ext = File.extname(path) ext = ext.downcase if ext == '.ayc' encrypted = true else encrypted = false end begin open(path, 'rb') do |dicfile| @dic.load(dicfile, encrypted) end rescue Logging::Logging.debug('cannot read ' + path.to_s) next end end func = @dic.get_function('load') unless func.nil? func.call(:argv => [@aya_dir.gsub('/', "\\")]) end return 1 end def load_aya_txt(f) comment = 0 @charset = 'CP932' # default for line in f line = line.force_encoding(@charset).encode("UTF-8", :invalid => :replace, :undef => :replace) unless comment.zero? end_ = Aya5.find_not_quoted(line, '*/') next if end_ < 0 line = line[end_ + 2..-1] comment = 0 end while true start, end_ = Aya5.find_comment(line) break if start < 0 line = "" if start.zero? if end_ < 0 comment = 1 line = line[0..start-1] break end line = [line[0..start-1], line[end_..-1]].join(' ') end line = line.strip() next if line.empty? next unless line.include?(',') key, value = line.split(',', 2) key.strip! value.strip! evaluate_config(key, value) end end def evaluate_config(key, value) if key == 'charset' if ['Shift_JIS', 'ShiftJIS', 'SJIS'].include?(value) @charset = 'CP932' elsif value == 'UTF-8' @charset = 'UTF-8' else # default and error @charset = 'CP932' end elsif key == 'dic' filename = Home.get_normalized_path(value) path = File.join(@aya_dir, filename) @dic_files << path elsif key == 'msglang' #pass ## FIXME elsif key == 'log' fail "assert" unless value.is_a?(String) filename = value path = File.join(@aya_dir, filename) begin f = open(path, 'w') rescue Logging::Logging.debug('cannnot open ' + path) else @logfile.close() unless @logfile.nil? @logfile = f @global_namespace.put('log', value.to_s) end elsif key == 'iolog' #pass ## FIXME elsif key == 'fncdepth' #pass ## FIXME end end def get_dictionary @dic end def get_ghost_dir @aya_dir end def get_global_namespace @global_namespace end def get_system_functions @system_functions end def get_boot_time @boot_time end def unload func = @dic.get_function('unload') func.call(:argv => []) unless func.nil? @global_namespace.save_database() @saori_library.unload() @logfile.close() unless @logfile.nil? for key in @filelist.keys() @filelist[key].close() end end # SHIORI API def request(req_string) result = '' func = @dic.get_function('request') unless func.nil? req = req_string.force_encoding(@charset).encode("UTF-8", :invalid => :replace, :undef => :replace) result = func.call(:argv => [req]) end if result.nil? result = '' end return result.encode(@charset, :invalid => :replace, :undef => :replace) end end class AyaDictionary attr_reader :aya def initialize(aya) @aya = aya @functions = {} @global_macro = {} end def get_function(name) if @functions.has_key?(name) return @functions[name] else return nil end end def load(f, encrypted) all_lines = [] local_macro = {} logical_line = '' comment = 0 while true if encrypted line = Aya5.decrypt_readline(f) else line = f.gets end break if line.nil? # EOF line = line.force_encoding(@aya.charset).encode("UTF-8", :invalid => :replace, :undef => :replace) unless comment.zero? end_ = Aya5.find_not_quoted(line, '*/') next if end_ < 0 line = line[end_ + 2..-1] comment = 0 end line = line.strip() next if line.empty? if line.end_with?('/') and \ not line.end_with?('*/') and not line.end_with?('//') logical_line = [logical_line, line[0..-2]].join('') else logical_line = [logical_line, line].join('') while true start, end_ = Aya5.find_comment(logical_line) break if start < 0 logical_line = "" if start.zero? if end_ < 0 comment = 1 logical_line = logical_line[0..start-1] break end logical_line = [logical_line[0..start-1], ' ', logical_line[end_..-1]].join('') end logical_line = logical_line.strip() next if logical_line.empty? buf = logical_line # preprosess if buf.start_with?('#') buf = buf[1..-1].strip() for (tag, target) in [['define', local_macro], ['globaldefine', @global_macro]] if buf.start_with?(tag) buf = buf[tag.length..-1].strip() i = 0 while i < buf.length if buf[i] == " " or buf[i] == "\t" or \ buf[i] == " " key = buf[0..i-1].strip() target[key] = buf[i..-1].strip() break end i += 1 end break end end logical_line = '' # reset next end for macro in [local_macro, @global_macro] logical_line = preprocess(macro, logical_line) end # multi statement list_lines = split_line(logical_line.strip()) unless list_lines.empty? all_lines.concat(list_lines) end logical_line = '' # reset end end evaluate_lines(all_lines, File.split(f.path)[1]) end def split_line(line) lines = [] while true break if line.nil? or line.empty? pos = line.length # not line.length - 1 token = '' for x in ['{', '}', ';'] pos_new = Aya5.find_not_quoted(line, x) if 0 <= pos_new and pos_new < pos pos = pos_new token = x end end unless pos.zero? new = line[0..pos-1].strip() else # '{' or '}' new = "" end line = line[pos + token.length..-1].strip() unless new.empty? lines << new end unless ['', ';'].include?(token) lines << token end end return lines end def preprocess(macro, line) for key in macro.keys value = macro[key] line = line.gsub(key, value) end return line end SPECIAL_CHARS = [']', '(', ')', '[', '+', '-', '*', '/', '=', ':', ';', '!', '{', '}', '%', '&', '#', '"', '<', '>', ',', '?'] def evaluate_lines(lines, file_name) prev = nil name = nil function = [] option = nil block_nest = 0 for i in 0..lines.length-1 line = lines[i] if line == '{' unless name.nil? if block_nest > 0 function << line end block_nest += 1 else if prev.nil? Logging::Logging.debug( 'syntax error in ' + file_name.to_s + ': unbalanced "{" at ' \ 'the top of file') else Logging::Logging.debug( 'syntax error in ' + file_name.to_s + ': unbalanced "{" at ' \ 'the bottom of function "' + prev.to_s + '"') end end elsif line == '}' unless name.nil? block_nest -= 1 if block_nest > 0 function << line elsif block_nest.zero? @functions[name] = AyaFunction.new(self, name, function, option) # reset prev = name name = nil function = [] option = nil end else if prev.nil? Logging::Logging.debug( 'syntax error in ' + file_name.to_s + ': unbalanced "}" at ' \ 'the top of file') else Logging::Logging.debug( 'syntax error in ' + file_name.to_s + ': unbalanced "}" at ' \ 'the bottom of function "' + prev.to_s + '"') end block_nest = 0 end elsif name.nil? if line.include?(':') name, option = line.split(':', 2) name.strip! option.strip! else name = line end for char in SPECIAL_CHARS if name.include?(char) Logging::Logging.debug( 'illegal function name "' + name.to_s + '" in ' + file_name.to_s) end end function = [] else if not name.nil? and block_nest > 0 function << line else Logging::Logging.debug('syntax error in ' + file_name + ': ' + line) end end end end end class AyaFunction TYPE_INT = 10 TYPE_FLOAT = 11 TYPE_DECISION = 12 TYPE_RETURN = 13 TYPE_BLOCK = 14 TYPE_SUBSTITUTION = 15 TYPE_INC = 16 TYPE_DEC = 17 TYPE_IF = 18 TYPE_WHILE = 19 TYPE_FOR = 20 TYPE_BREAK = 21 TYPE_CONTINUE = 22 TYPE_SWITCH = 23 TYPE_CASE = 24 TYPE_STRING_LITERAL = 25 TYPE_STRING = 26 TYPE_OPERATOR = 27 TYPE_STATEMENT = 28 TYPE_CONDITION = 29 TYPE_SYSTEM_FUNCTION = 30 TYPE_FUNCTION = 31 TYPE_ARRAY_POINTER = 32 TYPE_ARRAY = 33 TYPE_VARIABLE_POINTER = 34 TYPE_VARIABLE = 35 TYPE_TOKEN = 36 TYPE_NEW_ARRAY = 37 TYPE_FOREACH = 38 TYPE_PARALLEL = 39 TYPE_FORMULA = 40 CODE_NONE = 50 CODE_RETURN = 51 CODE_BREAK = 52 CODE_CONTINUE = 53 Re_f = Regexp.new('\A^[-+]?\d+(\.\d*)\z') Re_d = Regexp.new('\A[-+]?\d+\z') Re_d_ = Regexp.new('[-+]?\d+\z') Re_b = Regexp.new('\A[-+]?0[bB][01]+\z') Re_x = Regexp.new('\A[-+]?0[xX][\dA-Fa-f]+\z') Re_if = Regexp.new('\Aif\s') Re_others = Regexp.new('\Aothers\s') Re_elseif = Regexp.new('\Aelseif\s') Re_while = Regexp.new('\Awhile\s') Re_for = Regexp.new('\Afor\s') Re_foreach = Regexp.new('\Aforeach\s') Re_switch = Regexp.new('\Aswitch\s') Re_case = Regexp.new('\Acase\s') Re_when = Regexp.new('\Awhen\s') Re_parallel = Regexp.new('\Aparallel\s') SPECIAL_CHARS = [']', '(', ')', '[', '+', '-', '*', '/', '=', ':', ';', '!', '{', '}', '%', '&', '#', '"', '<', '>', ',', '?'] def initialize(dic, name, lines, option) @dic = dic @name = name @status = CODE_NONE @lines = parse(lines) if option == 'nonoverlap' @nonoverlap = [[], [], []] else @nonoverlap = nil end if option == 'sequential' @sequential = [[], []] else @sequential = nil end ## FIXME: void, array end def parse(lines) result = [] i = 0 while i < lines.length line = lines[i] if line == '--' result << [TYPE_DECISION, []] elsif line == 'return' result << [TYPE_RETURN, []] elsif line == 'break' result << [TYPE_BREAK, []] elsif line == 'continue' result << [TYPE_CONTINUE, []] elsif line == '{' inner_func = [] i, inner_func = get_block(lines, i) result << [TYPE_BLOCK, parse(inner_func)] elsif not Re_if.match(line).nil? inner_blocks = [] while true current_line = lines[i] if not Re_if.match(current_line).nil? condition = parse_(current_line[2..-1].strip()) elsif not Re_elseif.match(current_line).nil? condition = parse_(current_line[6..-1].strip()) else condition = [TYPE_CONDITION, nil] end inner_block = [] i, inner_block = get_block(lines, i + 1) if condition.nil? inner_blocks = [] break end entry = [] entry << condition entry << parse(inner_block) inner_blocks << entry if i + 1 >= lines.length break end next_line = lines[i + 1] if Re_elseif.match(next_line).nil? and \ next_line != 'else' break end i = (i + 1) end unless inner_blocks.empty? result << [TYPE_IF, inner_blocks] end elsif not Re_while.match(line).nil? condition = parse_(line[5..-1].strip()) inner_block = [] i, inner_block = get_block(lines, i + 1) result << [TYPE_WHILE, [condition, parse(inner_block)]] elsif not Re_for.match(line).nil? init = parse([line[3..-1].strip()]) ## FIXME(?) condition = parse_(lines[i + 1]) reset = parse([lines[i + 2]]) ## FIXME(?) inner_block = [] i, inner_block = get_block(lines, i + 3) unless condition.nil? result << [TYPE_FOR, [[init, condition, reset], parse(inner_block)]] end elsif not Re_foreach.match(line).nil? name = line[7..-1].strip() var = lines[i + 1] i, inner_block = get_block(lines, i + 2) result << [TYPE_FOREACH, [[name, var], parse(inner_block)]] elsif not Re_switch.match(line).nil? index = parse_(line[6..-1].strip()) inner_block = [] i, inner_block = get_block(lines, i + 1) result << [TYPE_SWITCH, [index, parse(inner_block)]] elsif not Re_case.match(line).nil? left = parse_(line[4..-1].strip()) i, block = get_block(lines, i + 1) inner_blocks = [] j = 0 while true current_line = block[j] unless Re_when.match(current_line).nil? right = current_line[4..-1].strip() else # 'others' right = nil end inner_block = [] j, inner_block = get_block(block, j + 1) unless right.nil? argument = AyaArgument.new(right) while argument.has_more_tokens() entry = [] right = argument.next_token() tokens = AyaStatement.new(right).tokens if ['-', '+'].include?(tokens[0]) value_min = parse_statement([tokens.shift, tokens.shift]) ## FIXME: parse_ else value_min = parse_statement([tokens.shift]) ## FIXME: parse_ end value_max = value_min unless tokens.empty? if tokens[0] != '-' Logging::Logging.debug( 'syntax error in function ' \ '"' + @name.to_s + '": when ' + right.to_s) next else tokens.shift end if tokens.length > 2 or \ (tokens.length == 2 and \ not ['-', '+'].include?(tokens[0])) Logging::Logging.debug( 'syntax error in function ' \ '"' + @name + '": when ' + right.to_s) next else value_max = parse_statement(tokens) ## FIXME: parse_ end end entry << [value_min, value_max] entry << parse(inner_block) inner_blocks << entry end else entry = [] entry << right entry << parse(inner_block) inner_blocks << entry end break if j + 1 == block.length next_line = block[j + 1] if Re_when.match(next_line).nil? and \ next_line != 'others' break end j += 1 end result << [TYPE_CASE, [left, inner_blocks]] elsif not Re_parallel.match(line).nil? ## FIXME #pass else result << [TYPE_FORMULA, parse_(line)] ## FIXME end i += 1 end result << [TYPE_DECISION, []] return result end def find_close_(token_open, tokens, position) nest = 0 current = position if token_open == '[' token_close = ']' elsif token_open == '(' token_close = ')' end while tokens[current..-1].count(token_close) > 0 pos_new = tokens[current..-1].index(token_close) pos_new += current break if pos_new.zero? nest = tokens[position..pos_new-1].count(token_open) - tokens[position..pos_new-1].count(token_close) # - 1 if nest > 0 current = (pos_new + 1) else current = pos_new break end end return current end def iter_position(tokens, ope_list, reverse: 0) position = 0 len_tokens = tokens.length result = [] while true new_pos = len_tokens tokens.reverse! unless reverse.zero? for ope in ope_list if tokens[position..-1].include?(ope) temp_pos = tokens[position..-1].index(ope) temp_pos += position new_pos = [new_pos, temp_pos].min end end tokens.reverse! unless reverse.zero? position = new_pos break if position >= len_tokens unless reverse.zero? result << (len_tokens - position) else result << position end position += 1 end return result end def find_position(tokens, ope_list, position, reverse: 0) len_tokens = tokens.length new_pos = len_tokens tokens.reverse! unless reverse.zero? for ope in ope_list if tokens[position..-1].include?(ope) temp_pos = tokens[position..-1].index(ope) temp_pos += position new_pos = [new_pos, temp_pos].min end end tokens.reverse! unless reverse.zero? position = new_pos return -1 if position >= len_tokens unless reverse.zero? return len_tokens - 1 - position else return position end end def get_left_(tokens, position) position -= 1 left = tokens.delete_at(position) if left.is_a?(String) # not processed left = new_parse([left]) end return left end def get_right_(tokens, position) right = tokens.delete_at(position) if right.is_a?(String) # not processed if ['-', '+'].include?(right) tmp_token = tokens.delete_at(position) right = new_parse([right, tmp_token]) else right = new_parse([right]) end end return right end def new_parse(tokens) ## FIXME return [] if tokens.length.zero? ## FIXME position = find_position(tokens, ['(', '['], 0) while position >= 0 pos_end = find_close_(tokens[position], tokens, position + 1) temp = [] token_open = tokens.delete_at(position) # open for i in 0..pos_end - position - 2 temp << tokens.delete_at(position) end tokens.delete_at(position) # close if token_open == '[' # array name = tokens.delete_at(position - 1) tokens.insert(position - 1, [TYPE_ARRAY, [name, [TYPE_FORMULA, new_parse(temp)]]]) else ope_list = ['!', '++', '--', '*', '/', '%', '+', '-', '&', '==', '!=', '>=', '<=', '>', '<', '_in_', '!_in_', '&&', '||', '=', ':=', '+=', '-=', '*=', '/=', '%=', '+:=', '-:=', '*:=', '/:=', '%:=', ',=', ','] ## FIXME if position.zero? or \ ope_list.include?(tokens[position - 1]) # FORMULA tokens.insert(position, [TYPE_FORMULA, new_parse(temp)]) else # should be function name = tokens.delete_at(position - 1) if @dic.aya.get_system_functions().exists(name) tokens.insert(position - 1, [TYPE_SYSTEM_FUNCTION, [name, [new_parse(temp)]]]) ## CHECK: arguments else tokens.insert(position - 1, [TYPE_FUNCTION, [name, [new_parse(temp)]]]) ## CHECK: arguments end end end position = find_position(tokens, ['(', '['], 0) end for position in iter_position(tokens, ['!']) tokens.delete_at(position) right = get_right_(tokens, position) tokens.insert(position, [TYPE_CONDITION, [nil, [TYPE_OPERATOR, '!'], right]]) end for position in iter_position(tokens, ['++', '--']) if tokens[position] == '++' type_ = TYPE_INC else type_ = TYPE_DEC end tokens.delete_at(position) var = tokens.delete_at(position - 1) if var.is_a?(String) # not processed right = new_parse([var]) ## FIXME end tokens.insert(position - 1, [type_, var]) end position = find_position(tokens, ['*', '/', '%'], 0, :reverse => 1) while position >= 0 ope = [TYPE_OPERATOR, tokens.delete_at(position)] right = get_right_(tokens, position) left = get_left_(tokens, position) tokens.insert(position - 1, [TYPE_STATEMENT, left, ope, right]) position = find_position(tokens, ['*', '/', '%'], 0, :reverse => 1) end position = 0 position = find_position(tokens, ['+', '-'], position) while position >= 0 ope = [TYPE_OPERATOR, tokens.delete_at(position)] right = get_right_(tokens, position) ope_list = ['!_in_', '_in_', '+:=', '-:=', '*:=', '/:=', '%:=', ':=', '+=', '-=', '*=', '/=', '%=', '<=', '>=', '==', '!=', '&&', '||', ',=', '++', '--', ',', '=', '!', '+', '-', '/', '*', '%', '&'] if position.zero? or ope_list.include?(tokens[position - 1]) left = [TYPE_INT, 0] tokens.insert(position, [TYPE_STATEMENT, left, ope, right]) else left = get_left_(tokens, position) tokens.insert(position - 1, [TYPE_STATEMENT, left, ope, right]) end position = find_position(tokens, ['+', '-'], position) end for position in iter_position(tokens, ['&']) type_ = tokens[position + 1][0] var_ = tokens[position + 1][1] tokens.delete_at(position) tokens.delete_at(position) # +1 if type_ == TYPE_ARRAY tokens.insert(position, [TYPE_ARRAY_POINTER, var_]) elsif type_ == TYPE_VARIABLE tokens.insert(position, [TYPE_VARIABLE_POINTER, var_]) elsif type_ == TYPE_TOKEN tokens.insert(position, [TYPE_VARIABLE_POINTER, [var_, nil]]) else Logging::Logging.debug( 'syntax error in function "' + @name.to_s + '": ' \ 'illegal argument "' + tokens.to_s + '"') end end position = find_position(tokens, ['==', '!=', '>=', '<=', '>', '<', '_in_', '!_in_'], 0, :reverse => 1) while position >= 0 ope = [TYPE_OPERATOR, tokens.delete_at(position)] right = get_right_(tokens, position) left = get_left_(tokens, position) tokens.insert(position - 1, [TYPE_CONDITION, [left, ope, right]]) position = find_position(tokens, ['==', '!=', '>=', '<=', '>', '<', '_in_', '!_in_'], 0, :reverse => 1) end position = find_position(tokens, ['&&'], 0, :reverse => 1) while position >= 0 ope = [TYPE_OPERATOR, tokens.delete_at(position)] right = get_right_(tokens, position) left = get_left_(tokens, position) tokens.insert(position - 1, [TYPE_CONDITION, [left, ope, right]]) position = find_position(tokens, ['&&'], 0, :reverse => 1) end position = find_position(tokens, ['||'], 0, :reverse => 1) while position >= 0 ope = [TYPE_OPERATOR, tokens.delete_at(position)] right = get_right_(tokens, position) left = get_left_(tokens, position) tokens.insert(position - 1, [TYPE_CONDITION, [left, ope, right]]) position = find_position(tokens, ['||'], 0, :reverse => 1) end position = find_position(tokens, ['=', ':='], 0, :reverse => 1) while position >= 0 ope = [TYPE_OPERATOR, tokens.delete_at(position)] right = get_right_(tokens, position) left = get_left_(tokens, position) tokens.insert(position - 1, [TYPE_SUBSTITUTION, [left, ope, right]]) position = find_position(tokens, ['=', ':='], 0, :reverse => 1) end position = find_position(tokens, ['+=', '-=', '*=', '/=', '%=', '+:=', '-:=', '*:=', '/:=', '%:=', ',='], 0, :reverse => 1) while position >= 0 ope = [TYPE_OPERATOR, tokens.delete_at(position)] right = get_right_(tokens, position) left = get_left_(tokens, position) tokens.insert(position - 1, [TYPE_SUBSTITUTION, [left, ope, right]]) position = find_position(tokens, ['+=', '-=', '*=', '/=', '%=', '+:=', '-:=', '*:=', '/:=', '%:=', ',='], 0, :reverse => 1) end temp = [] position = find_position(tokens, [','], 0, :reverse => 0) while position >= 0 tokens.delete_at(position) temp << get_right_(tokens, position) if position > 0 temp.insert(0, get_left_(tokens, position)) end position = find_position(tokens, [','], 0, :reverse => 0) end unless temp.empty? tokens.insert(position, [TYPE_NEW_ARRAY, temp]) end for i in 0..tokens.length-1 if tokens[i].is_a?(String) token = tokens.delete_at(i) tokens.insert(i, parse_token(token)) end end return tokens[0] ## FIXME end def parse_(line) ## FIXME statement = AyaStatement.new(line) return new_parse(statement.tokens) end def parse_statement(statement_tokens) n_tokens = statement_tokens.length statement = [] if n_tokens == 1 statement = [TYPE_STATEMENT, parse_token(statement_tokens[0])] elsif statement_tokens[0] == '+' statement = parse_statement(statement_tokens[1..-1]) elsif statement_tokens[0] == '-' tokens = ['0'] tokens.concat(statement_tokens) statement = parse_statement(tokens) else ope_index = nil for ope in ['+', '-'] if statement_tokens.include?(ope) new_index = statement_tokens.index(ope) if ope_index.nil? or new_index < ope_index ope_index = new_index end end end if ope_index.nil? statement_tokens.reverse! begin for ope in ['*', '/', '%'] if statement_tokens.include?(ope) new_index = statement_tokens.index(ope) if ope_index.nil? or new_index < ope_index ope_index = new_index end end end unless ope_index.nil? ope_index = (-1 - ope_index) end ensure statement_tokens.reverse! end end if [nil, -1, 0, n_tokens - 1].include?(ope_index) return nil else ope = [TYPE_OPERATOR, statement_tokens[ope_index]] if statement_tokens[0..ope_index-1].length == 1 if statement_tokens[0].start_with?('(') tokens = AyaStatement.new(statement_tokens[0][1..-2]).tokens left = parse_statement(tokens) else left = parse_token( statement_tokens[0..ope_index-1][0]) end else left = parse_statement(statement_tokens[0..ope_index-1]) end if statement_tokens[ope_index + 1..-1].length == 1 if statement_tokens[-1].start_with?('(') tokens = AyaStatement( statement_tokens[ope_index + 1][1..-2]).tokens right = parse_statement(tokens) else right = parse_token( statement_tokens[ope_index + 1..-1][0]) end else right = parse_statement( statement_tokens[ope_index + 1..-1]) end statement = [TYPE_STATEMENT, left, ope, right] end end return statement end def parse_condition(condition_tokens) n_tokens = condition_tokens.length condition = nil ope_index = nil condition_tokens.reverse! begin for ope in ['&&', '||'] if condition_tokens.include?(ope) new_index = condition_tokens.index(ope) if ope_index.nil? or new_index < ope_index ope_index = new_index end end end unless ope_index.nil? ope_index = (-1 - ope_index) end ensure condition_tokens.reverse! end if ope_index.nil? for ope in ['==', '!=', '>', '<', '>=', '<=', '_in_', '!_in_'] if condition_tokens.include?(ope) new_index = condition_tokens.index(ope) if ope_index.nil? or new_index < ope_index ope_index = new_index end end end if [nil, -1, 0, n_tokens - 1].include?(ope_index) Logging::Logging.debug( 'syntax error in function "' + @name.to_s + '": ' \ 'illegal condition "' + condition_tokens.join(' ') + '"') return nil end ope = [TYPE_OPERATOR, condition_tokens[ope_index]] if condition_tokens[0..ope_index-1].length == 1 left = parse_statement([condition_tokens[0..ope_index-1][0]]) else left = parse_statement(condition_tokens[0..ope_index-1]) end if condition_tokens[ope_index + 1..-1].length == 1 right = parse_statement([condition_tokens[ope_index + 1..-1][0]]) else right = parse_statement(condition_tokens[ope_index + 1..-1]) end condition = [TYPE_CONDITION, [left, ope, right]] else ope = [TYPE_OPERATOR, condition_tokens[ope_index]] left = parse_condition(condition_tokens[0..ope_index-1]) right = parse_condition(condition_tokens[ope_index + 1..-1]) unless left.nil? or right.nil? condition = [TYPE_CONDITION, [left, ope, right]] end end return condition end def parse_argument(args) ## FIXME argument = AyaArgument.new(args) arguments = [] while argument.has_more_tokens() token = argument.next_token() if token.start_with?('&') result = parse_token(token[1..-1]) if result[0] == TYPE_ARRAY arguments << [TYPE_ARRAY_POINTER, result[1]] elsif result[0] == TYPE_VARIABLE arguments << [TYPE_VARIABLE_POINTER, result[1]] elsif result[0] == TYPE_TOKEN arguments << [TYPE_VARIABLE_POINTER, [result[1], nil]] else Logging::Logging.debug( 'syntax error in function "' + @name.to_s + '": ' \ 'illegal argument "' + token.to_s + '"') end elsif token.start_with?('(') unless token.end_with?(')') Logging::Logging.debug( 'syntax error in function "' + @name.to_s + '": ' \ 'unbalanced "(" in the string(' + token.to_s + ')') return nil else statement = AyaStatement.new(token[1..-2]) arguments << parse_statement(statement.tokens) end else arguments << parse_statement([token]) end end return arguments end def parse_token(token) result = [] if not Re_f.match(token).nil? result = [TYPE_FLOAT, token] elsif not Re_d.match(token).nil? result = [TYPE_INT, token] elsif token.start_with?('"') text = token[1..-1] if text.end_with?('"') text = text[0..-2] end if text.count('"') > 0 Logging::Logging.debug( 'syntax error in function "' + @name.to_s + '": ' \ '\'"\' in string "' + text.to_s + '"') end unless text.include?('%') result = [TYPE_STRING_LITERAL, text] else result = [TYPE_STRING, text] end elsif token.start_with?("'") text = token[1..-1] if text.end_with?("'") text = text[0..-2] end if text.count("'") > 0 Logging::Logging.debug( 'syntax error in function "' + @name.to_s + '": ' \ "\"'\" in string \"' + text.to_s + '\"") end result = [TYPE_STRING_LITERAL, text] else pos_parenthesis_open = token.index('(') pos_block_open = token.index('[') if pos_parenthesis_open.nil? # XXX pos_parenthesis_open = -1 end if pos_block_open.nil? # XXX pos_block_open = -1 end if pos_parenthesis_open.zero? and Aya5.find_not_quoted(token, ',') != -1 ## FIXME: Array unless token.end_with?(')') Logging::Logging.debug( 'syntax error: unbalnced "(" in "{0}"'.format(token)) else result = [TYPE_NEW_ARRAY, parse_argument(token[1..-2])] ## FIXME end elsif pos_parenthesis_open != -1 and \ (pos_block_open == -1 or \ pos_parenthesis_open < pos_block_open) # function unless token.end_with?(')') Logging::Logging.debug( 'syntax error: unbalnced "(" in "' + token.to_s + '"') else func_name = token[0..pos_parenthesis_open-1] arguments = parse_argument( token[pos_parenthesis_open + 1..-2]) break_flag = false for char in SPECIAL_CHARS if func_name.include?(char) Logging::Logging.debug( 'illegal character "' + char + '" in ' \ 'the name of function "' + token.to_s + '"') break_flag = true break end end unless break_flag if @dic.aya.get_system_functions().exists( func_name) if func_name == 'LOGGING' result = [TYPE_SYSTEM_FUNCTION, [func_name, arguments, token[pos_parenthesis_open + 1..-2]]] else result = [TYPE_SYSTEM_FUNCTION, [func_name, arguments]] end else result = [TYPE_FUNCTION, [func_name, arguments]] end end end elsif not pos_block_open.nil? and pos_block_open != -1 # array unless token.end_with?(']') Logging::Logging.debug( 'syntax error: unbalanced "[" in "' + token.to_s + '"') else array_name = token[0..pos_block_open-1] index = parse_token(token[pos_block_open + 1..-2]) break_flag = false for char in SPECIAL_CHARS if array_name.include?(char) Logging::Logging.debug( 'illegal character "' + char.to_s + '" in ' \ 'the name of array "' + token.to_s + '"') break_flag = true break end end unless break_flag result = [TYPE_ARRAY, [array_name, index]] end end else # variable or function break_flag = false for char in SPECIAL_CHARS if token.include?(char) Logging::Logging.debug( 'syntax error in function "' + @name.to_s + '": ' \ 'illegal character "' + char + '" in the name of ' \ 'function/variable "' + token + '"') break_flag = true break end end unless break_flag result = [TYPE_TOKEN, token] end end end return result end def call(argv: nil) namespace = AyaNamespace.new(@dic.aya) _argv = [] if argv.nil? namespace.put('_argc', 0) else namespace.put('_argc', argv.length) for i in 0..argv.length-1 if argv[i].is_a?(Hash) _argv << argv[i]['value'] else _argv << argv[i] end end end namespace.put('_argv', _argv) @status = CODE_NONE result = evaluate(namespace, @lines, -1, 0) ## FIXME unless argv.nil? for i in 0..argv.length-1 if argv[i].is_a?(Hash) value = _argv[i] name = argv[i]['name'] namespace = argv[i]['namespace'] index = argv[i]['index'] namespace.put(name, value, :index => index) end end end return result end def evaluate(namespace, lines, index_to_return, is_inner_block, is_block: 1, connect: 0) ## FIXME result = [] alternatives = [] for line in lines next if line.nil? or line.empty? if [TYPE_DECISION, TYPE_RETURN, TYPE_BREAK, TYPE_CONTINUE].include?(line[0]) or \ [CODE_RETURN, CODE_BREAK, CODE_CONTINUE].include?(@status) unless alternatives.empty? unless is_inner_block.zero? if index_to_return < 0 result << alternatives.sample elsif index_to_return <= (alternatives.length - 1) result << alternatives[index_to_return] else # out of range result << '' end else result << alternatives end alternatives = [] end if line[0] == TYPE_RETURN or \ @status == CODE_RETURN @status = CODE_RETURN break elsif line[0] == TYPE_BREAK or \ @status == CODE_BREAK @status = CODE_BREAK break elsif line[0] == TYPE_CONTINUE or \ @status == CODE_CONTINUE @status = CODE_CONTINUE break end elsif line[0] == TYPE_BLOCK inner_func = line[1] local_namespace = AyaNamespace.new(@dic.aya, :parent => namespace) result_of_inner_func = evaluate(local_namespace, inner_func, -1, 1) ## FIXME unless result_of_inner_func.nil? alternatives << result_of_inner_func end elsif line[0] == TYPE_SUBSTITUTION left, ope, right = line[1] ope = ope[1] if [':=', '+:=', '-:=', '*:=', '/:=', '%:='].include?(ope) type_float = 1 else type_float = 0 end right_result = evaluate(namespace, [right], -1 , 1, :is_block => 0, :connect => 1) ## FIXME unless ['=', ':='].include?(ope) left_result = evaluate_token(namespace, left) right_result = operation(left_result, ope[0], right_result, type_float) ope = ope[1..-1] end result_of_substitution = substitute(namespace, left, ope, right_result) unless connect.zero? ## FIXME alternatives << result_of_substitution end elsif line[0] == TYPE_INC or \ line[0] == TYPE_DEC # ++/-- if line[0] == TYPE_INC ope = '++' elsif line[0] == TYPE_DEC ope = '--' else return nil # should not reach here end var_name = line[1] if var_name.start_with?('_') target_namespace = namespace else target_namespace = @dic.aya.get_global_namespace() end value = evaluate(namespace, [[TYPE_TOKEN, var_name]], -1, 1, :is_block => 0) ## FIXME index = nil if value.is_a?(Integer) or value.is_a?(Float) if ope == '++' target_namespace.put(var_name, value.to_i + 1, :index => index) elsif ope == '--' target_namespace.put(var_name, value.to_i - 1, :index => index) else return nil # should not reach here end else Logging::Logging.debug( 'illegal increment/decrement:' \ 'type of variable ' + var_name.to_s + ' is not number') end elsif line[0] == TYPE_IF inner_blocks = line[1] n_blocks = inner_blocks.length for j in 0..n_blocks-1 entry = inner_blocks[j] condition = entry[0] inner_block = entry[1] unless [0, false, nil].include?(evaluate(namespace, [condition], -1, 1, :is_block => 0)) ## FIXME local_namespace = AyaNamespace.new(@dic.aya, :parent => namespace) result_of_inner_block = evaluate(local_namespace, inner_block, -1, 1) ## FIXME unless result_of_inner_block.nil? alternatives << result_of_inner_block end break end end elsif line[0] == TYPE_WHILE condition = line[1][0] inner_block = line[1][1] fail "assert" unless condition[0] == TYPE_CONDITION or condition[0] == TYPE_INT while evaluate_condition(namespace, condition) local_namespace = AyaNamespace.new(@dic.aya, :parent => namespace) result_of_inner_block = evaluate(local_namespace, inner_block, -1, 1) ## FIXME unless result_of_inner_block.nil? alternatives << result_of_inner_block end break if @status == CODE_RETURN if @status == CODE_BREAK @status = CODE_NONE break end if @status == CODE_CONTINUE @status = CODE_NONE end end elsif line[0] == TYPE_FOR init = line[1][0][0] condition = line[1][0][1] reset = line[1][0][2] inner_block = line[1][1] evaluate(namespace, init, -1, 1, :is_block => 0) ## FIXME fail "assert" unless condition[0] == TYPE_CONDITION or condition[0] == TYPE_INT while evaluate_condition(namespace, condition) local_namespace = AyaNamespace.new(@dic.aya, :parent => namespace) result_of_inner_block = evaluate(local_namespace, inner_block, -1, 1) ## FIXME unless result_of_inner_block.nil? alternatives << result_of_inner_block end break if @status == CODE_RETURN if @status == CODE_BREAK @status = CODE_NONE break end if @status == CODE_CONTINUE @status = CODE_NONE end evaluate(namespace, reset, -1, 1, :is_block => 0) ## FIXME end elsif line[0] == TYPE_SWITCH index = evaluate_token(namespace, line[1][0]) inner_block = line[1][1] begin index = Integer(index) rescue index = 0 end local_namespace = AyaNamespace.new(@dic.aya, :parent => namespace) result_of_inner_block = evaluate(local_namespace, inner_block, index, 1) ## FIXME unless result_of_inner_block.nil? alternatives << result_of_inner_block end elsif line[0] == TYPE_CASE left = evaluate_token(namespace, line[1][0]) inner_blocks = line[1][1] n_blocks = inner_blocks.length default_result = nil break_flag = false for j in 0..n_blocks-1 entry = inner_blocks[j] inner_block = entry[1] local_namespace = AyaNamespace.new(@dic.aya, :parent => namespace) unless entry[0].nil? value_min, value_max = entry[0] value_min = evaluate_statement(namespace, value_min, 1) value_max = evaluate_statement(namespace, value_max, 1) if value_min <= left and left <= value_max result_of_inner_block = evaluate( local_namespace, inner_block, -1, 1) ## FIXME unless result_of_inner_block.nil? alternatives << result_of_inner_block break_flag = true break end end else default_result = evaluate(local_namespace, inner_block, -1, 1) ## FIXME end end unless break_flag unless default_result.nil? alternatives << default_result end end elsif line[0] == TYPE_STATEMENT result_of_func = evaluate_statement(namespace, line, 0) unless result_of_func.nil? alternatives << result_of_func end elsif line[0] == TYPE_CONDITION condition = line if condition.nil? or \ evaluate_condition(namespace, condition) alternatives << true else alternatives << false end elsif line[0] == TYPE_FORMULA result_of_formula = evaluate(namespace, [line[1]], -1, 1, :is_block => 0, :connect => connect) ## FIXME unless result_of_formula.nil? alternatives << result_of_formula end elsif line[0] == TYPE_NEW_ARRAY temp = [] for item in line[1] member_of_array = evaluate(namespace, [item], -1, 1, :is_block => 0, :connect => 1) ## FIXME temp << member_of_array end alternatives << temp elsif line[0] == TYPE_ARRAY system_functions = @dic.aya.get_system_functions() if line[1][0].is_a?(Array) or \ system_functions.exists(line[1][0]) if line[1][0].is_a?(Array) ## FIXME array = evaluate(namespace, [line[1][0]], -1, 1, :is_block => 0) ## FIXME else array = evaluate(namespace, [[TYPE_SYSTEM_FUNCTION, [line[1][0], []]]], -1, 1, :is_block => 0) end if array.is_a?(String) temp = evaluate(namespace, [line[1][1]], -1, 1, :is_block => 0) ## FIXME if temp.is_a?(Array) fail "assert" unless temp.length == 2 index, delimiter = temp else index = temp delimiter = ',' end fail "assert" unless index.is_a?(Integer) fail "assert" unless delimiter.is_a?(String) result_of_array = array.split(delimiter)[index] alternatives << result_of_array elsif array.is_a?(Array) index = evaluate(namespace, [line[1][1]], -1, 1, :is_block => 0) ## FIXME fail "assert" unless index.is_a?(Integer) result_of_array = array[index] alternatives << result_of_array else Logging::Logging.debug( 'Oops: ' + array.to_s + ' ' + line[1][1].to_s) end else var_name = line[1][0] if var_name.start_with?('_') target_namespace = namespace else target_namespace = @dic.aya.get_global_namespace() end temp = evaluate(namespace, [line[1][1]], -1, 1, :is_block => 0) ## FIXME if temp.is_a?(Array) and temp.length > 1 fail "assert" unless temp.length == 2 index, delimiter = temp fail "assert" unless delimiter.is_a?(String) else index = temp delimiter = nil end ##fail "assert" unless index.is_a?(Integer) if index.is_a?(Integer) and \ target_namespace.exists(var_name) unless delimiter.nil? array = target_namespace.get(var_name) temp = array.to_s.split(delimiter) if temp.length > index result_of_array = array.to_s.split(delimiter)[index] else result_of_array = '' end else result_of_array = target_namespace.get(var_name, :index => index) end alternatives << result_of_array else result_of_array = '' alternatives << result_of_array end end elsif [TYPE_INT, TYPE_TOKEN, TYPE_SYSTEM_FUNCTION, TYPE_STRING_LITERAL, TYPE_FUNCTION, TYPE_STRING, TYPE_ARRAY_POINTER, TYPE_VARIABLE_POINTER].include?(line[0]) result_of_eval = evaluate_token(namespace, line) unless result_of_eval.nil? alternatives << result_of_eval end elsif line[0] == TYPE_FOREACH var_name, temp = line[1][0] if var_name.start_with?('_') target_namespace = namespace else target_namespace = @dic.aya.get_global_namespace() end array = target_namespace.get_array(var_name) for item in array if temp.start_with?('_') target_namespace = namespace else target_namespace = @dic.aya.get_global_namespace() end target_namespace.put(temp, item) result_of_block = evaluate(namespace, line[1][1], -1, 1) ## FIXME break if @status == CODE_RETURN if @status == CODE_BREAK @status = CODE_NONE break end if @status == CODE_CONTINUE @status = CODE_NONE end end else ## FIXME result_of_eval = evaluate_token(namespace, line) unless result_of_eval.nil? alternatives << result_of_eval end end end if is_inner_block.zero? unless @sequential.nil? list_ = [] for alt in result list_ << alt.length end if @sequential[0] != list_ @sequential[0] = list_ @sequential[1] = ([0] * result.length) else for index in 0..result.length-1 current = @sequential[1][index] if current < result[index].length - 1 @sequential[1][index] = (current + 1) break else @sequential[1][index] = 0 end end end end unless @nonoverlap.nil? list_ = [] for alt in result list_ << alt.length end if @nonoverlap[0] != list_ @nonoverlap[0] = list_ @nonoverlap[2] = [] end if @nonoverlap[2].empty? @nonoverlap[2] << ([0] * result.length) while true new = [] new.concat(@nonoverlap[2][-1]) break_flag = false for index in 0..result.length-1 if new[index] < result[index].length - 1 new[index] += 1 @nonoverlap[2] << new break_flag = true break else new[index] = 0 end end break unless break_flag end end next_ = Random.rand(0..@nonoverlap[2].length-1) @nonoverlap[1] = @nonoverlap[2][next_] @nonoverlap[2].delete(next_) end for index in 0..result.length-1 if not @sequential.nil? result[index] = result[index][@sequential[1][index]] elsif not @nonoverlap.nil? result[index] = result[index][@nonoverlap[1][index]] else result[index] = result[index].sample end end end if result.nil? or result.empty? if is_block.zero? and not alternatives.empty? ## FIXME return alternatives[-1] ## FIXME end return nil elsif result.length == 1 return result[0] else return result.map {|s| s.to_s}.join('') end end def substitute(namespace, left, ope, right) if left[0] != TYPE_ARRAY var_name = left[1] else var_name = left[1][0] end if var_name.start_with?('_') target_namespace = namespace else target_namespace = @dic.aya.get_global_namespace() end if left[0] != TYPE_ARRAY target_namespace.put(var_name, right) else index = evaluate(namespace, [left[1][1]], -1, 1, :is_block => 0) begin index = Integer(index) rescue Logging::Logging.debug('Could not convert ' + index.to_s + ' to an integer') else if ope == '=' elem = right elsif ope == ':=' if right.is_a?(Integer) elem = right.to_f else elem = right end else return nil # should not reach here end target_namespace.put(var_name, elem, :index => index) end end return right end def evaluate_token(namespace, token) result = '' # default case token[0] when TYPE_TOKEN if not Re_b.match(token[1]).nil? pos = Re_d_.match(token[1]).begin(0) result = token[1][pos..-1].to_i(2) elsif not Re_x.match(token[1]).nil? result = token[1].to_i(16) else func = @dic.get_function(token[1]) system_functions = @dic.aya.get_system_functions() if not func.nil? result = func.call() elsif system_functions.exists(token[1]) result = system_functions.call(namespace, token[1], []) else if token[1].start_with?('_') target_namespace = namespace else target_namespace = @dic.aya.get_global_namespace() end if target_namespace.exists(token[1]) result = target_namespace.get(token[1]) end end end when TYPE_STRING_LITERAL result = token[1] when TYPE_STRING result = evaluate_string(namespace, token[1]) when TYPE_INT result = token[1].to_i when TYPE_FLOAT result = token[1].to_f when TYPE_SYSTEM_FUNCTION system_functions = @dic.aya.get_system_functions() func_name = token[1][0] ##fail ["assert: ", 'function ', func_name, ' not found.'].join('') unless system_functions.exists(func_name) arguments = evaluate(namespace, token[1][1], -1, 1, :is_block => 0, :connect => 1) ## FIXME unless arguments.is_a?(Array) ## FIXME arguments = [arguments] end if func_name == 'LOGGING' arguments.insert(0, token[1][2]) arguments.insert(0, @name) arguments.insert(0, @dic.aya.logfile) result = system_functions.call(namespace, func_name, arguments) else result = system_functions.call(namespace, func_name, arguments) end when TYPE_FUNCTION func_name = token[1][0] func = @dic.get_function(func_name) ##fail ["assert: ", 'function ', func_name, ' not found.'].join('') unless func != nil arguments = evaluate_argument(namespace, func_name, token[1][1], false) result = func.call(:argv => arguments) when TYPE_ARRAY result = evaluate(namespace, [token], -1, 1, :is_block => 0) ## FIXME when TYPE_NEW_ARRAY result = evaluate(namespace, [token], -1, 1, :is_block => 0) ## FIXME when TYPE_VARIABLE var_name = token[1][0] if var_name.start_with?('_') target_namespace = namespace else target_namespace = @dic.aya.get_global_namespace() end if target_namespace.exists(var_name) result = target_namespace.get(var_name) end when TYPE_ARRAY_POINTER var_name = token[1][0] if var_name.start_with?('_') target_namespace = namespace else target_namespace = @dic.aya.get_global_namespace() end index = evaluate(namespace, [token[1][1]], -1, 1, :is_block => 0) begin index = Integer(index) rescue Logging::Logging.debug( 'index of array has to be integer: ' + var_name.to_s + '[' + token[1][1].to_s + ']') else value = target_namespace.get(var_name, :index => index) result = {'name' => var_name, 'index' => index, 'namespace' => target_namespace, 'value' => value} end when TYPE_VARIABLE_POINTER var_name = token[1][0] if var_name.start_with?('_') target_namespace = namespace else target_namespace = @dic.aya.get_global_namespace() end value = target_namespace.get(var_name) result = {'name' => var_name, 'index' => nil, 'namespace' => target_namespace, 'value' => value} else Logging::Logging.debug('error in evaluate_token: ' + token.to_s) end return result end def evaluate_condition(namespace, condition) result = false if condition[0] == TYPE_INT unless Integer(condition[1]).zero? return true else return false end end return true if condition[1].nil? left = condition[1][0] ope = condition[1][1] right = condition[1][2] fail "assert" unless ope[0] == TYPE_OPERATOR if left.nil? # '!' left_result = true else left_result = evaluate(namespace, [left], -1, 1, :is_block => 0, :connect => 1) ## FIXME end right_result = evaluate(namespace, [right], -1, 1, :is_block => 0, :connect => 1) ## FIXME case ope[1] when '==' result = (left_result == right_result) when '!=' result = (left_result != right_result) when '_in_' if right_result.is_a?(String) and left_result.is_a?(String) if right_result.include?(left_result) result = true else result = false end else result = false end when '!_in_' if right_result.is_a?(String) and left_result.is_a?(String) unless right_result.include?(left_result) result = true else result = false end else result = false end when '<' if right_result.is_a?(String) != left_result.is_a?(String) begin left_result = Float(left_result) right_result = Float(right_result) rescue return false # XXX end end result = (left_result < right_result) when '>' if right_result.is_a?(String) != left_result.is_a?(String) begin left_result = Float(left_result) right_result = Float(right_result) rescue return false # XXX end end result = (left_result > right_result) when '<=' if right_result.is_a?(String) != left_result.is_a?(String) begin left_result = Float(left_result) right_result = Float(right_result) rescue return false # XXX end end result = (left_result <= right_result) when '>=' if right_result.is_a?(String) != left_result.is_a?(String) begin left_result = Float(left_result) right_result = Float(right_result) rescue return false # XXX end end result = (left_result >= right_result) when '||' result = (not [0, false, nil].include?(left_result) or not [0, false, nil].include?(right_result)) when '&&' result = (not [0, false ,nil].include?(left_result) and not [0, false, nil].include?(right_result)) when '!' result = ([0, false, nil].include?(right_result)) else #pass end return result end def evaluate_statement(namespace, statement, type_float) return '' if statement.nil? or statement.length <= 1 num = statement[1..-1].length type_ = statement[0] token = statement[1] if type_ == TYPE_STATEMENT left = evaluate_statement(namespace, token, type_float) else left = evaluate_token(namespace, statement) end if num == 3 ope = statement[2][1] type_ = statement[3][0] case type_ when TYPE_INT token = statement[3][1] unless type_float.zero? right = token.to_f else right = token.to_i end when TYPE_FLOAT token = statement[3][1] unless type_float.zero? right = token.to_f else right = token.to_f.to_i end when TYPE_STATEMENT right = evaluate_statement(namespace, statement[3], type_float) else right = evaluate(namespace, [statement[3]], -1, 1, :is_block => 0) ## FIXME end result = operation(left, ope, right, type_float) else result = left end return result end def operation(left, ope, right, type_float) unless left.is_a?(Array) begin if not type_float.zero? left = Float(left) right = Float(right) elsif ope != '+' or \ (not left.is_a?(String) and not right.is_a?(String)) left = Integer(left) right = Integer(right) else left = left.to_s right = right.to_s end rescue left = left.to_s right = right.to_s end end begin case ope when '+' return left + right when '-' return left - right when '*' return left * right when '/' if right.zero? return 0 else if left.is_a?(Integer) and right.is_a?(Integer) return (left / right).to_i else return left / right end end when '%' return left % right when ',' fail "assert" unless left.is_a?(Array) result = [] result.concat(left) if right.is_a?(Array) result.concat(right) else result << right end return result end rescue Logging::Logging.debug( 'illegal operation: ' + [left.to_s, ope.to_s, right.to_s].join(' ')) return '' end end def get_block(parent, startpoint) result = [] n_lines = parent.length inner_nest_level = 0 for i in startpoint..n_lines-1 inner_content = parent[i] if inner_content == '{' if inner_nest_level > 0 result << inner_content end inner_nest_level += 1 elsif inner_content == '}' inner_nest_level -= 1 if inner_nest_level > 0 result << inner_content end else result << inner_content end if inner_nest_level.zero? return i, result end end return startpoint, result end def evaluate_string(namespace, line) history = [] # %[n] buf = '' startpoint = 0 system_functions = @dic.aya.get_system_functions() while startpoint < line.length pos = line.index('%', startpoint) if pos.nil? or pos < 0 buf = [buf, line[startpoint..-1]].join('') startpoint = line.length next else unless pos.zero? buf = [buf, line[startpoint..pos-1]].join('') end startpoint = pos end break if pos == (line.length - 1) # XXX if line[pos + 1] == '(' start_ = (pos + 2) nest = 0 current_ = start_ while line[current_..-1].count(')') > 0 close_ = line.index(')', current_) break if close_.zero? nest = line[start_..close_-1].count('(') - line[start_..close_-1].count(')') if nest > 0 current_ = (close_ + 1) else current_ = close_ break end end lines_ = parse([line[start_..current_-1]]) result_ = evaluate(namespace, lines_, -1, 1, :is_block => 0, :connect => 1) buf = [buf, result_.to_s].join('') startpoint = (current_ + 1) next end endpoint = line.length for char in SPECIAL_CHARS pos = line[0..endpoint-1].index(char, startpoint + 2) if not pos.nil? and 0 < pos and pos < endpoint endpoint = pos end end if line[startpoint + 1] == '[' # history if line[endpoint] != ']' Logging::Logging.debug( 'unbalanced "%[" or illegal index in ' \ 'the string(' + line + ')') buf = '' break end index_token = parse_token(line[startpoint + 2..endpoint-1]) index = evaluate_token(namespace, index_token) begin index = Integer(index) rescue Logging::Logging.debug( 'illegal history index in the string(' + line + ')') else if 0 <= index and index < history.length buf = [buf, format(history[index])].join('') end end startpoint = (endpoint + 1) next end replaced = false while endpoint > (startpoint + 1) token = line[startpoint + 1..endpoint-1] func = @dic.get_function(token) is_system_func = system_functions.exists(token) if not func.nil? or is_system_func if endpoint < line.length and \ line[endpoint] == '(' end_of_parenthesis = line.index(')', endpoint + 1) if end_of_parenthesis.nil? or end_of_parenthesis < 0 Logging::Logging.debug( 'unbalanced "(" in the string(' + line + ')') startpoint = line.length buf = '' break end func_name = token arguments = parse_argument( line[endpoint + 1..end_of_parenthesis-1]) arguments = evaluate_argument( namespace, func_name, arguments, is_system_func) if is_system_func if func_name == 'LOGGING' arguments.insert( 0, line[endpoint + 1..end_of_parenthesis-1]) arguments.insert(0, @name) arguments.insert(0, @dic.aya.logfile) result_of_func = system_functions.call( namespace, func_name, arguments) else result_of_func = system_functions.call( namespace, func_name, arguments) end else result_of_func = func.call(:argv => arguments) end if result_of_func.nil? result_of_func = '' end history << result_of_func buf = [buf, format(result_of_func)].join('') startpoint = (end_of_parenthesis + 1) replaced = true break elsif not func.nil? result_of_func = func.call() history << result_of_func buf = [buf, format(result_of_func)].join('') startpoint = endpoint replaced = true break else result_of_func = system_functions.call( namespace, token, []) if result_of_func.nil? result_of_func = '' end history << result_of_func buf = [buf, format(result_of_func)].join('') startpoint = endpoint replaced = true break end else if token.start_with?('_') target_namespace = namespace else target_namespace = @dic.aya.get_global_namespace() end if target_namespace.exists(token) have_index = false index = nil if endpoint < line.length and line[endpoint] == '[' end_of_block = line.index(']', endpoint + 1) if end_of_block.nil? or end_of_block < 0 Logging::Logging.debug( 'unbalanced "[" or ' \ 'illegal index in the string(' + line + ')') startpoint = line.length buf = '' break end have_index = true index_token = parse_token( line[endpoint + 1..end_of_block-1]) index = evaluate_token(namespace, index_token) begin index = Integer(index) rescue have_index = false index = nil end end value = target_namespace.get(token, :index => index) unless value.nil? content_of_var = value history << content_of_var buf = [buf, format(content_of_var)].join('') if have_index startpoint = (end_of_block + 1) else startpoint = endpoint end replaced = true break end end end endpoint -= 1 end unless replaced buf = [buf, line[startpoint]].join('') startpoint += 1 end end return buf end def format(input_num) if input_num.is_a?(Float) result = round(input_num, 6).to_s else result = input_num.to_s end return result end def evaluate_argument(namespace, name, argument, is_system_func) arguments = [] for i in 0..argument.length-1 if is_system_func and \ @dic.aya.get_system_functions().not_to_evaluate(name, i) arguments << argument[i][1][1] else value = evaluate_statement(namespace, argument[i], 1) if value.is_a?(Array) arguments.concat(value) else arguments << value end end end return arguments end def is_substitution(line) statement = AyaStatement.new(line) if statement.countTokens() >= 3 statement.next_token() # left ope = statement.next_token() ope_list = ['=', ':=', '+=', '-=', '*=', '/=', '%=', '+:=', '-:=', '*:=', '/:=', '%:=', ',='] return true if ope_list.include?(ope) end return false end def is_inc_or_dec(line) return false if line.length <= 2 if line.end_with?('++') or line.end_with?('--') return true else return false end end end class AyaSystemFunctions def initialize(aya) @aya = aya @current_charset = nil @fcharset = {} @charsetlib = nil @saori_statuscode = '' @saori_header = [] @saori_value = {} @saori_protocol = '' @errno = 0 @re_result = [] @functions = { 'ACOS' => ['ACOS', [nil], [1], nil], 'ANY' => [], 'ARRAYSIZE' => ['ARRAYSIZE', [0], [1], nil], 'ASEARCH' => [], 'ASEARCHEX' => [], 'ASIN' => ['ASIN', [nil], [1], nil], 'ATAN' => ['ATAN', [nil], [1], nil], 'BINSTRTOI' => ['BINSTRTOI', [nil], [1], nil], 'CEIL' => ['CEIL', [nil], [1], nil], 'CHARSETLIB' => ['CHARSETLIB', [nil], [1], nil], 'CHR' => ['CHR', [nil], [1], nil], 'CHRCODE' => ['CHRCODE', [nil], [1], nil], 'COS' => ['COS', [nil], [1], nil], 'CUTSPACE' => ['CUTSPACE', [nil], [1], nil], 'CVINT' => ['CVINT', [0], [1], nil], 'CVREAL' => ['CVREAL', [0], [1], nil], 'CVSTR' => ['CVSTR', [0], [1], nil], 'ERASE' => ['ERASE', [nil], [3], nil], 'ERASEVAR' => ['ERASEVAR', [nil], [1], nil], 'EVAL' => ['EVAL', [nil], [1], nil], 'FATTRIB' => [], 'FCHARSET' => ['FCHARSET', [nil], [1], nil], 'FCLOSE' => ['FCLOSE', [nil], [1], nil], 'FCOPY' => ['FCOPY', [nil], [2], 259], 'FDEL' => ['FDEL', [nil], [1], 269], 'FENUM' => ['FENUM', [nil], [1, 2], 290], 'FLOOR' => ['FLOOR', [nil], [1], nil], 'FMOVE' => ['FMOVE', [nil], [2], 264], 'FOPEN' => ['FOPEN', [nil], [2], 256], 'FREAD' => ['FREAD', [nil], [1], nil], 'FRENAME' => ['FRENAME', [nil], [2], 273], 'FSIZE' => ['FSIZE', [nil], [1], 278], 'FWRITE' => ['FWRITE', [nil], [2], nil], 'FWRITE2' => ['FWRITE2', [nil], [2], nil], 'GETDELIM' => ['GETDELIM', [0], [1], nil], 'GETLASTERROR' => ['GETLASTERROR', [nil], [nil], nil], 'GETMEMINFO' => [], 'GETSETTING' => ['GETSETTING', [nil], [1], nil], 'GETSTRBYTES' => ['GETSTRBYTES', [nil], [1, 2], nil], 'GETTICKCOUNT' => ['GETTICKCOUNT', [nil], [0], nil], 'GETTIME' => ['GETTIME', [nil], [0], nil], 'GETTYPE' => ['GETTYPE', [nil], [1], nil], 'HEXSTRTOI' => ['HEXSTRTOI', [nil], [1], nil], 'IARRAY' => ['IARRAY', [nil], [nil], nil], 'INSERT' => ['INSERT', [nil], [3], nil], 'ISFUNC' => ['ISFUNC', [nil], [1], nil], 'ISINTSTR' => ['ISINTSTR', [nil], [1], nil], 'ISREALSTR' => ['ISREALSTR', [nil], [1], nil], 'ISVAR' => ['ISVAR', [nil], [1], nil], 'LETTONAME' => ['LETTONAME', [nil], [2], nil], 'LOADLIB' => ['LOADLIB', [nil], [1], 16], 'LOG' => ['LOG', [nil], [1], nil], 'LOG10' => ['LOG10', [nil], [1], nil], 'LOGGING' => [], 'LSO' => [], 'MKDIR' => ['MKDIR', [nil], [1], 282], 'POW' => ['POW', [nil], [2], nil], 'RAND' => ['RAND', [nil], [0, 1], nil], 'RE_GETLEN' => [], 'RE_GETPOS' => [], 'RE_GETSTR' => ['RE_GETSTR', [nil], [0], nil], 'RE_GREP' => [], 'RE_MATCH' => [], 'RE_REPLACE' => [], 'RE_SEARCH' => [], 'RE_SPLIT' => ['RE_SPLIT', [nil], [2], nil], 'REPLACE' => ['REPLACE', [nil], [3], nil], 'REQUESTLIB' => ['REQUESTLIB', [nil], [2], nil], 'RMDIR' => ['RMDIR', [nil], [1], 286], 'ROUND' => ['ROUND', [nil], [1], nil], 'SAVEVAR' => ['SAVEVAR', [nil], [nil], nil], 'SETDELIM' => ['SETDELIM', [0], [2], nil], 'SETLASTERROR' => ['SETLASTERROR', [nil], [1], nil], 'SIN' => ['SIN', [nil], [1], nil], 'SPLIT' => ['SPLIT', [nil], [2, 3], nil], 'SPLITPATH' => ['SPLITPATH', [nil], [1], nil ], 'SQRT' => ['SQRT', [nil], [1], nil], 'STRFORM' => [], 'STRLEN' => ['STRLEN', [1], [1], nil], 'STRSTR' => ['STRSTR', [3], [3], nil], 'SUBSTR' => ['SUBSTR', [nil], [3], nil], 'TAN' => ['TAN', [nil], [1], nil], 'TOBINSTR' => ['TOBINSTR', [nil], [1], nil], 'TOHEXSTR' => ['TOHEXSTR', [nil], [1], nil], 'TOINT' => ['TOINT', [nil], [1], nil], 'TOLOWER' => ['TOLOWER', [nil], [1], nil], 'TOREAL' => ['TOREAL', [nil], [1], nil], 'TOSTR' => ['TOSTR', [nil], [1], nil], 'TOUPPER' => ['TOUPPER', [nil], [1], nil], 'UNLOADLIB' => ['UNLOADLIB', [nil], [1], nil], #'LOGGING' => ['LOGGING', [nil], [4], nil] } end def exists(name) @functions.include?(name) end def call(namespace, name, argv) @errno = 0 if not @functions.include?(name) return '' elsif not @functions[name] # not implemented yet Logging::Logging.warning( 'aya5.py: SYSTEM FUNCTION "' + name.to_s + '" is not implemented yet.') return '' elsif check_num_args(name, argv) return method(@functions[name][0]).call(namespace, argv) else return '' end end def not_to_evaluate(name, index) return false unless @functions.include?(name) # XXX if @functions[name][1].include?(index) return true else return false end end def check_num_args(name, argv) list_num = @functions[name][2] if list_num == [nil] return true else return true if list_num.include?(argv.length) list_num.sort! if argv.length < list_num[0] errno = @functions[name][3] @errno = errno unless errno.nil? Logging::Logging.debug( [name.to_s, ': called with too few argument(s)'].join('')) return false end return true end end def ACOS(namespace, argv) begin result = math.acos(Float(argv[0])) rescue return -1 end return select_math_type(result) end def ANY(namespace, argv) #pass end def ARRAYSIZE(namespace, argv) argv.length ## FIXME end def ASEARCH(namespace, argv) #pass end def ASEARCHEX(namespace, argv) #pass end def ASIN(namespace, argv) begin result = math.asin(Float(argv[0])) rescue return -1 end return select_math_type(result) end def ATAN(namespace, argv) begin result = math.atan(Float(argv[0])) rescue return -1 end return select_math_type(result) end def BINSTRTOI(namespace, argv) begin return argv[0].to_s.to_i(2) rescue return 0 end end def CEIL(namespace, argv) begin return math.ceil(argv[0].to_f).to_i rescue return -1 end end def CHARSETLIB(namespace, argv) begin value = Integer(argv[0]) rescue return end case value when 0 @charsetlib = 'CP932' when 1 @charsetlib = 'UTF-8' when 127 @charsetlib = @aya.charset end end def CHR(namespace, argv) begin return Integer(argv[0]).chr rescue return '' end end def CHRCODE(namespace, argv) line = argv[0].to_s unless line.empty? return line[0].encode('utf-16', 'ignore') # UCS-2 else return '' end end def COS(namespace, argv) begin result = math.cos(argv[0].to_f) rescue return -1 end return select_math_type(result) end def CUTSPACE(namespace, argv) return argv[0].to_s.strip() end def CVINT(namespace, argv) var = argv[0].to_s target_namespace = select_namespace(namespace, var) token = target_namespace.get(var) begin result = Integer(token) rescue result = 0 end target_namespace.put(var, result) return nil end def CVREAL(namespace, argv) var = argv[0].to_s target_namespace = select_namespace(namespace, var) token = target_namespace.get(var) begin result = Float(token) rescue result = 0.0 end target_namespace.put(var, result) return nil end def CVSTR(namespace, argv) name = argv[0].to_s target_namespace = select_namespace(namespace, name) value = target_namespace.get(name).to_s target_namespace.put(name, value) return nil end def ERASE(namespace, argv) line = argv[0].to_s begin start = argv[1].to_i bytes = argv[2].to_i rescue return '' end return [line[0..start-1], line[start + bytes..-1]].join('').encode("UTF-8", :invalid => :replace, :undef => :replace) # XXX end def ERASEVAR(namespace, argv) var = argv[0].to_s target_namespace = select_namespace(namespace, var) target_namespace.remove(var) return nil # XXX end def EVAL(namespace, argv) script = argv[0] func = AyaFunction.new(@aya.dic, '', [script], nil) result = func.call() return result end def FATTRIB(namespace, argv) #pass end def FCHARSET(namespace, argv) begin value = Integer(argv[0]) rescue return end case value when 0 @current_charset = 'CP932' when 1 @current_charset = 'UTF-8' when 127 @current_charset = @aya.charset end end def FCLOSE(namespace, argv) filename = Home.get_normalized_path(argv[0].to_s) path = File.join(@aya.aya_dir, filename) norm_path = File.expand_path(path) if @aya.filelist.include?(norm_path) @aya.filelist[norm_path].close() @aya.filelist.delete(norm_path) @f_charset.delete(norm_path) end return nil end def FCOPY(namespace, argv) src = Home.get_normalized_path(argv[0].to_s) head, tail = File.split(src) dst = [Home.get_normalized_path(argv[1].to_s), '/', tail].join('') src_path = File.join(@aya.aya_dir, src) dst_path = File.join(@aya.aya_dir, dst) result = 0 if not File.file?(src_path) @errno = 260 elsif not File.directory?(dst_path) @errno = 261 else begin shutil.copyfile(src_path, dst_path) rescue @errno = 262 else result = 1 end end return result end def FDEL(namespace, argv) filename = Home.get_normalized_path(argv[0].to_s) path = File.join(@aya.aya_dir, filename) result = 0 unless File.file?(path) @errno = 270 else begin File.delete(path) rescue @errno = 271 else result = 1 end end return result end def FENUM(namespace, argv) if argv.length >= 2 separator = argv[1].to_s else separator = ',' end dirname = Home.get_normalized_path(argv[0].to_s) path = File.join(@aya.aya_dir, dirname) filelist = [] begin filelist = Dir.entries(path).reject{|entry| entry =~ /^\.{1,2}$/} rescue @errno = 291 end result = '' for index in 0..filelist.length-1 path = File.join(@aya.aya_dir, dirname, filelist[index]) if File.directory?(path) result = [result, "\\"].join('') end result = [result, filelist[index]].join('') if index != (filelist.length - 1) result = [result, separator].join('') end end return result end def FLOOR(namespace, argv) begin return math.floor(argv[0].to_f).to_i rescue return -1 end end def FMOVE(namespace, argv) src = Home.get_normalized_path(argv[0].to_s) head, tail = File.split(src) dst = [Home.get_normalized_path(argv[1].to_s), '/', tail].join('') src_path = File.join(@aya.aya_dir, src) dst_path = File.join(@aya.aya_dir, dst) result = 0 head, tail = File.split(dst_path) if not File.file?(src_path) @errno = 265 elsif not File.directory?(head) @errno = 266 else begin File.rename(src_path, dst_path) rescue @errno = 267 else result = 1 end end return result end def FOPEN(namespace, argv) filename = Home.get_normalized_path(argv[0].to_s) accessmode = argv[1].to_s result = 0 path = File.join(@aya.aya_dir, filename) norm_path = File.expand_path(path) if @aya.filelist.include?(norm_path) result = 2 else begin @aya.filelist[norm_path] = open(path, [accessmode[0], 'b'].join('')) # XXX rescue @errno = 257 else if @current_charset.nil? @current_charset = @aya.charset end @f_charset[norm_path] = @current_charset result = 1 end end return result end def FREAD(namespace, argv) filename = Home.get_normalized_path(argv[0].to_s) path = File.join(@aya.aya_dir, filename) norm_path = File.expand_path(path) result = -1 if @aya.filelist.include?(norm_path) f = @aya.filelist[norm_path] result = f.readline().force_encoding(@fcharset[norm_path]) if result.nil? result = -1 elsif result.end_with?("\r\n") result = result[0..-3] elsif result.end_with?("\n") result = result[0..-2] end end return result end def FRENAME(namespace, argv) src = Home.get_normalized_path(argv[0].to_s) dst = Home.get_normalized_path(argv[1].to_s) src_path = File.join(@aya.aya_dir, src) dst_path = File.join(@aya.aya_dir, dst) result = 0 head, tail = File.split(dst_path) if not File.exist?(src_path) @errno = 274 elsif not File.directory?(head) @errno = 275 else begin File.rename(src_path, dst_path) rescue @errno = 276 else result = 1 end end return result end def FSIZE(namespace, argv) filename = Home.get_normalized_path(argv[0].to_s) path = File.join(@aya.aya_dir, filename) size = -1 unless File.exist?(path) @errno = 279 else begin size = File.size(path) rescue @errno = 280 end end return size end def FWRITE(namespace, argv) filename = Home.get_normalized_path(argv[0].to_s) path = File.join(@aya.aya_dir, filename) norm_path = File.expand_path(path) data = [argv[1].to_s, "\n"].join('') if @aya.filelist.include?(norm_path) f = @aya.filelist[norm_path] f.write(data.encode(@fcharset[norm_path], 'ignore')) end return nil end def FWRITE2(namespace, argv) filename = Home.get_normalized_path(argv[0].to_s) path = File.join(@aya.aya_dir, filename) norm_path = File.expand_path(path) data = argv[1].to_s if @aya.filelist.include?(norm_path) f = @aya.filelist[norm_path] f.write(data.encode(@fcharset[norm_path], 'ignore')) end return nil end def GETDELIM(namespace, argv) name = argv[0].to_s target_namespace = select_namespace(namespace, name) return target_namespace.get_separator(name) end def GETLASTERROR(namespace, argv) @errno end def GETMEMINFO(namespace, argv) #pass end def GETSETTING(namespace, argv) begin value = Integer(argv[0]) rescue return '' end case value when 0 result = '5' when 1 if @aya.charset == 'CP932' result = 0 elsif @aya.charset == 'UTF-8' result = 1 else result = 2 end when 2 result = @aya.aya_dir else result = '' end return result end def GETSTRBYTES(namespace, argv) line = argv[0].to_s if argv.length > 1 begin value = Integer(argv[1]) rescue value = 0 end else value = 0 end case value when 0 result = line.encode('CP932', 'ignore').length when 1 result = line.encode('utf-8', 'ignore').length when 2 result = line.encode(@aya.charset, 'ignore').length else result = -1 end return result end def GETTICKCOUNT(namespace, argv) past = (Time.now - @aya.get_boot_time()) return (past * 1000.0).to_i end def GETTIME(namespace, argv) t = Time.now return [t.year, t.month, t.day, t.wday, t.hour, t.min, t.sec] end def GETTYPE(namespace, argv) if argv[0].is_a?(Integer) result = 1 elsif argv[0].is_a?(Float) result = 2 elsif argv[0].is_a?(String) result = 3 elsif 0 ## FIXME: array result = 4 else result = 0 end return result end def HEXSTRTOI(namespace, argv) begin return argv[0].to_s.to_i(16) rescue return 0 end end def IARRAY(namespace, argv) return [] # AyaVariable.new('', :new_array => true) ## FIXME end def INSERT(namespace, argv) line = argv[0].to_s begin start = Integer(argv[1]) rescue return '' end to_insert = argv[2].to_s if start < 0 start = 0 end return [line[0..start-1], to_insert, line[start..-1]].join('') end def ISFUNC(namespace, argv) if not argv[0].is_a?(String) return 0 elsif not @aya.dic.get_function(argv[0]).nil? return 1 elsif @aya.get_system_functions().exists(argv[0]) return 2 else return 0 end end def ISINTSTR(namespace, argv) begin Integer(argv[0].to_s) return 1 rescue return 0 end end def ISREALSTR(namespace, argv) begin Float(argv[0].to_s) return 1 rescue return 0 end end def ISVAR(namespace, argv) var = argv[0].to_s if var.start_with?('_') if namespace.exists(var) return 2 else return 0 end else if @aya.get_global_namespace().exists(var) return 1 else return 0 end end end def LETTONAME(namespace, argv) var = argv[0].to_s value = argv[1] return nil if var.empty? target_namespace = select_namespace(namespace, var) target_namespace.put(var, value) return nil end def LOADLIB(namespace, argv) dll = argv[0].to_s result = 0 unless dll.empty? if @charsetlib.nil? @charsetlib = @aya.charset end @aya.saori_library.set_charset(@charsetlib) result = @aya.saori_library.load(dll, @aya.aya_dir) @errno = 17 if result.zero? end return result end def LOG(namespace, argv) begin argv[0].to_f rescue return -1 end return 0 if argv[0].to_f.zero? result = math.log(argv[0].to_f) return select_math_type(result) end def LOG10(namespace, argv) begin argv[0].to_f rescue return -1 end return 0 if argv[0].to_f.zero? result = math.log10(argv[0].to_f) return select_math_type(result) end def LSO(namespace, argv) #pass end def MKDIR(namespace, argv) dirname = Home.get_normalized_path(argv[0].to_s) path = File.join(@aya.aya_dir, dirname) result = 0 head, tail = File.split(path) unless File.directory?(head) @errno = 283 else begin Dir.mkdir(path, 0o755) rescue @errno = 284 else result = 1 end end return result end def POW(namespace, argv) begin result = math.pow(argv[0].to_f, argv[1].to_f) rescue return -1 end return select_math_type(result) end def RAND(namespace, argv) if argv.empty? return Random.rand(0..99) else begin Integer(argv[0]) rescue return -1 end return Random.rand(0..argv[0].to_i-1) end end def RE_GETLEN(namespace, argv) #pass end def RE_GETPOS(namespace, argv) #pass end def RE_GETSTR(namespace, argv) #result_array = AyaVariable.new('', :new_array => true) #result_array.put(@re_result) #return result_array return @re_result end def RE_GREP(namespace, argv) #pass end def RE_MATCH(namespace, argv) #pass end def RE_REPLACE(namespace, argv) #pass end def RE_SEARCH(namespace, argv) #pass end def RE_SPLIT(namespace, argv) line = argv[0].to_s re_split = Regexp.new(argv[1].to_s) # if argv.length > 2 # begin # max = argv[2] # rescue # return [] # else # result = re_split.split(line, max) # end # else result = line.split(re_split) # end @re_result = line.scan(re_split) #result_array = AyaVariable.new('', :new_array => true) #result_array.put(result) #return result_array return result end def REPLACE(namespace, argv) line = argv[0].to_s old = argv[1].to_s new = argv[2].to_s return line.gsub(old, new) end def REQUESTLIB(namespace, argv) response = @aya.saori_library.request(argv[0], argv[1]) header = response.split(/\r?\n/, 0) @saori_statuscode = '' @saori_header = [] @saori_value = {} @saori_protocol = '' unless header.nil? or header.empty? line = header.shift line = line.strip() if line.include?(' ') @saori_protocol, @saori_statuscode = line.split(' ', 2) @saori_protocol.strip! @saori_statuscode.strip! end for line in header next unless line.include?(':') key, value = line.split(':', 2) key.strip! value.strip! unless key.empty? @saori_header << key @saori_value[key] = value end end end return response end def RMDIR(namespace, argv) dirname = Home.get_normalized_path(argv[0].to_s) path = File.join(@aya.aya_dir, dirname) result = 0 unless File.directory?(path) @errno = 287 else begin Dir.rmdir(path) rescue @errno = 288 else result = 1 end end return result end def ROUND(namespace, argv) begin value = math.floor(Float(argv[0]) + 0.5) rescue return -1 end return value.to_i end def SAVEVAR(namespace, argv) @aya.get_global_namespace().save_database() end def SETDELIM(namespace, argv) name = argv[0].to_s separator = argv[1].to_s target_namespace = select_namespace(namespace, name) target_namespace.set_separator(name, separator) return nil end def SETLASTERROR(namespace, argv) begin value = Integer(argv[0]) rescue return end @errno = value end def SIN(namespace, argv) begin result = math.sin(Float(argv[0])) rescue return -1 end return select_math_type(result) end def SPLIT(namespace, argv) line = argv[0].to_s result = line.split(argv[1].to_s, argv[2].to_i + 1) return result end def SPLITPATH(namespace, argv) line = argv[0].to_s drive, path = "", line # XXX dirname, filename = File.split(path) basename = File.basename(filename, ".*") ext = File.extname(filename) return [drive, dirname, basename, ext] end def SQRT(namespace, argv) begin arg = Float(argv[0]) rescue return -1 end if arg < 0.0 return -1 else result = math.sqrt(arg) return select_math_type(result) end end def STRFORM(namespace, argv) #pass end def STRLEN(namespace, argv) line = argv[0].to_s return line.length end def STRSTR(namespace, argv) line = argv[0].to_s to_find = argv[1].to_s begin start = Integer(argv[2]) rescue return -1 end result = line.index(to_find, start) return result end def SUBSTR(namespace, argv) line = argv[0].to_s begin start = Integer(argv[1]) num = Integer(argv[2]) rescue return '' end return line[start..start + num-1] end def TAN(namespace, argv) begin result = math.tan(Float(argv[0])) rescue return -1 end return select_math_type(result) end def TOBINSTR(namespace, argv) begin i = Integer(argv[0]) rescue return '' end if i < 0 i = abs(i) numsin = '-' else numsin = '' end line = '' while not i.zero? mod = (i % 2) i = (i / 2).to_i line = [mod.to_s, line].join('') end line = [numsin, line].join('') return line end def TOHEXSTR(namespace, argv) begin return argv[0].to_i.to_s(16) rescue return '' end end def TOINT(namespace, argv) token = argv[0].to_s begin value = Integer(token) rescue return 0 else return value end end def TOLOWER(namespace, argv) argv[0].to_s.downcase end def TOREAL(namespace, argv) token = argv[0].to_s begin value = Float(token) rescue return 0.0 else return value end end def TOSTR(namespace, argv) argv[0].to_s end def TOUPPER(namespace, argv) argv[0].to_s.upcase end def UNLOADLIB(namespace, argv) unless argv[0].to_s.empty? @aya.saori_library.unload(:name => argv[0].to_s) end return nil end def LOGGING(namespace, argv) ## FIXME return nil if argv[0].nil? logfile = argv[0] line = ['> function ', argv[1].to_s, ' : ', argv[2].to_s].join('') unless argv[3].nil? line = [line, ' = '].join('') if argv[3].is_a?(Integer) or argv[3].is_a?(Float) line = [line, argv[3].to_s].join('') else line = [line, '"', argv[3].to_s, '"'].join('') end end line = [line, "\n"].join('') logfile.write(line) logfile.write("\n") return nil end def select_math_type(value) if math.floor(value) == value return value.to_i else return value end end def select_namespace(namespace, name) if name.start_with?('_') return namespace else return @aya.get_global_namespace() end end end class AyaNamespace def initialize(aya, parent: nil) @aya = aya @parent = parent @table = {} end def put(name, content, index: nil) if not @parent.nil? and @parent.exists(name) @parent.put(name, content, :index => index) elsif index.nil? unless exists(name) @table[name] = AyaVariable.new(name) end @table[name].put(content) elsif exists(name) and index >=0 @table[name].put(content, :index => index) else #pass # ERROR end end def get(name, index: nil) if @table.include?(name) return @table[name].get(:index => index) elsif not @parent.nil? and @parent.exists(name) return @parent.get(name, :index => index) else return nil end end def get_array(name) if @table.include?(name) return @table[name].get_array() elsif not @parent.nil? and @parent.exists(name) return @parent.get_array(name) else return [] end end def get_separator(name) if not @parent.nil? and @parent.exists(name) return @parent.get_separator(name) elsif @table.include?(name) @table[name].get_separator() else return '' # ERROR end end def set_separator(name, separator) if not @parent.nil? and @parent.exists(name) @parent.set_separator(name, separator) elsif @table.include?(name) @table[name].set_separator(separator) else #pass # ERROR end end def get_size(name) if @table.include?(name) return @table[name].get_size() elsif not @parent.nil? and @parent.exists(name) return @parent.get_size(name) else return 0 end end def remove(name) # only works with local table if @table.include?(name) @table.delete(name) end end def exists(name) result = (@table.include?(name) or \ (not @parent.nil? and @parent.exists(name))) return result end end class AyaGlobalNamespace < AyaNamespace def load_database(aya) begin open(aya.dbpath, encoding=@aya.charset) do |f| line = f.readline() return 1 unless line.start_with?('# Format: v1.2') for line in f comma = line.find(',') if comma >= 0 key = line[:comma] else next end value = line[comma + 1..-1].strip() comma = find_not_quoted(value, ',') if comma >= 0 separator = value[comma + 1..-1].strip() separator = separator[1..-2] value = value[0..comma-1].strip() value = value[1..-2] put(key, value) @table[key].set_separator(separator) elsif value != 'None' if value.include?('.') put(key, Float(value)) else put(key, Integer(value)) end else #pass end end end rescue return 1 end return 0 end def save_database begin open(@aya.dbpath, 'w', :encoding => @aya.charset) do |f| f.write("# Format: v1.2\n") for key in @table.keys() line = @table[key].dump() f.write([line, "\n"].join('')) unless line.nil? end end rescue #except IOError: Logging::Logging.debug('aya.py: cannot write database (ignored)') return end end end class AyaStatement attr_reader :tokens SPECIAL_CHARS = '=+-*/<>|&!:,_' def initialize(line) @n_tokens = 0 @tokens = [] @position_of_next_token = 0 tokenize(line) end def tokenize(line) ## FIXME: '[', ']' token_startpoint = 0 block_nest_level = 0 length = line.length i = 0 while i < length c = line[i] if c == '(' block_nest_level += 1 unless i.zero? append_unless_empty(line[token_startpoint..i-1].strip()) end @tokens << '(' i += 1 token_startpoint = i elsif c == ')' block_nest_level -= 1 unless i.zero? append_unless_empty(line[token_startpoint..i-1].strip()) end @tokens << ')' i += 1 token_startpoint = i elsif c == '[' unless i.zero? append_unless_empty(line[token_startpoint..i-1].strip()) end @tokens << '[' i += 1 token_startpoint = i elsif c == ']' unless i.zero? append_unless_empty(line[token_startpoint..i-1].strip()) end @tokens << ']' i += 1 token_startpoint = i elsif c == '"' position = line.index('"', i + 1) unless position or position < 0 ## FIXME fail SystemExit ## FIXME end i = position @tokens << line[token_startpoint..position] token_startpoint = (position + 1) i = (position + 1) elsif c == "'" position = line.index("'", i + 1) if position.nil? or position < 0 ## FIXME fail SystemExit ## FIXME end i = position @tokens << line[token_startpoint..position] token_startpoint = position + 1 i = (position + 1) elsif c == ' ' or c == '\t' or c == ' ' unless i.zero? append_unless_empty(line[token_startpoint..i-1].strip()) end i += 1 token_startpoint = i elsif SPECIAL_CHARS.include?(c) ope_list = ['!_in_', '_in_', '+:=', '-:=', '*:=', '/:=', '%:=', ':=', '+=', '-=', '*=', '/=', '%=', '<=', '>=', '==', '!=', '&&', '||', ',=', '++', '--', ',', '=', '!', '+', '-', '/', '*', '%', '&'] break_flag = false for ope in ope_list if line[i..-1].start_with?(ope) ## FIXME unless i.zero? append_unless_empty(line[token_startpoint..i-1].strip()) end num = ope.length @tokens << line[i..i + num -1] i += num token_startpoint = i break_flag = true break end end unless break_flag i += 1 end else i += 1 end end append_unless_empty(line[token_startpoint..-1].strip()) @n_tokens = @tokens.length end def append_unless_empty(token) @tokens << token unless token.nil? or token.empty? end def has_more_tokens return (@position_of_next_token < @n_tokens) end def countTokens @n_tokens end def next_token return nil unless has_more_tokens() result = @tokens[@position_of_next_token] @position_of_next_token += 1 return result end end class AyaVariable TYPE_STRING = 0 TYPE_INT = 1 TYPE_REAL = 2 TYPE_ARRAY = 3 TYPE_NEW_ARRAY = 4 def initialize(name, new_array: false) @name = name @line = '' @separator = ',' if new_array @type = TYPE_NEW_ARRAY else @type = nil end @array = [] end def get_array @array end def get_separator @separator end def set_separator(separator) return if @type != TYPE_STRING @separator = separator reset() end def reset return if @type != TYPE_STRING @position = 0 @is_empty = false @array = [] while not @is_empty separator_position = @line.index(@separator, @position) if separator_position.nil? token = @line[@position..-1] @is_empty = true else token = @line[@position..separator_position-1] @position = (separator_position + @separator.length) end @array << token end end def get_size @array.length end def get(index: nil) if index.nil? case @type when TYPE_STRING result = @line.to_s when TYPE_INT result = @line.to_i when TYPE_REAL result = @line.to_f when TYPE_NEW_ARRAY result = @array ## FIXME(?) else result = '' end return result end if 0 <= index and index < @array.length value = @array[index] case @type when TYPE_STRING result = value.to_s when TYPE_INT result = value.to_i when TYPE_REAL result = value.to_f when TYPE_ARRAY result = value when TYPE_NEW_ARRAY result = value ## FIXME else result = nil # should not reach here end elsif index.nil? case @type when TYPE_STRING result = @line.to_s when TYPE_INT result = @line.to_i when TYPE_REAL result = @line.to_f when TYPE_NEW_ARRAY result = @array ## FIXME(?) else result = '' end else result = '' end return result end def put(value, index: nil) if index.nil? @line = value.to_s if value.is_a?(String) @type = TYPE_STRING elsif value.is_a?(Integer) @type = TYPE_INT elsif value.is_a?(Float) @type = TYPE_REAL elsif value.is_a?(Array) @type = TYPE_NEW_ARRAY # CHECK @array = value end reset() elsif index < 0 #pass else if @type == TYPE_STRING @line = '' for i in 0..@array.length-1 if i == index @line = [@line, value.to_s].join('') else @line = [@line, @array[i]].join('') end if i != (@array.length - 1) @line = [@line, @separator].join('') end end if index >= @array.length for i in @array.length..index if i == index @line = [@line, @separator, value.to_s].join('') else @line = [@line, @separator, ''].join('') end end end reset() elsif @type == TYPE_ARRAY if 0 <= index and index < @array.length @array[index] = value end elsif @type == TYPE_NEW_ARRAY if 0 <= index and index < @array.length @array[index] = value elsif index > 0 ## FIXME for _ in @array.length..index-2 @array << '' # XXX end @array << value ##print(@array, index, value) Logging::Logging.info('!!! WARNING !!!') Logging::Logging.info('NOT YET IMPLEMENTED') else #pass # ERROR end else #pass # ERROR end end end def dump line = nil if @type == TYPE_STRING line = (@name.to_s + ', "' + @line.to_s + '", "' + @separator.to_s + '"') elsif @type == TYPE_NEW_ARRAY #pass ## FIXME elsif @type != TYPE_ARRAY line = (@name.to_s + ', ' + @line.to_s) else #pass end return line end end class AyaArgument def initialize(line) @line = line.strip() @length = @line.length @current_position = 0 end def has_more_tokens return (@current_position != -1 and \ @current_position < @length) end def next_token return nil unless has_more_tokens() startpoint = @current_position @current_position = position_of_next_token() if @current_position == -1 token = @line[startpoint..-1] else token = @line[startpoint..@current_position-2] end return token.strip() end def position_of_next_token locked = true position = @current_position parenthesis_nest_level = 0 while position < @length c = @line[position] case c when '"' return position unless locked while position < @length-1 position += 1 break if @line[position] == '"' end when '(' parenthesis_nest_level += 1 when ')' parenthesis_nest_level -= 1 when ',' locked = false if parenthesis_nest_level.zero? else return position unless locked end position += 1 end return -1 end end class AyaSaoriLibrary def initialize(saori, top_dir) @saori_list = {} @saori = saori @current_charset = nil @charset = {} end def set_charset(charset) fail "assert" unless ['CP932', 'Shift_JIS', 'UTF-8'].include?(charset) @current_charset = charset end def load(name, top_dir) result = 0 head, name = File.split(name.gsub("\\", '/')) # XXX: don't encode here top_dir = File.join(top_dir, head) unless @saori.nil? or @saori_list.include?(name) module_ = @saori.request(name) @saori_list[name] = module_ unless module_.nil? end if @saori_list.include?(name) result = @saori_list[name].load(:dir => top_dir) end @charset[name] = @current_charset return result end def unload(name: nil) unless name.nil? name = File.split(name.gsub("\\", '/'))[-1] # XXX: don't encode here if @saori_list.include?(name) @saori_list[name].unload() @saori_list.delete(name) @charset.delete(name) end else for key in @saori_list.keys() @saori_list[key].unload() end end return nil end def request(name, req) result = '' # FIXME name = File.split(name.gsub("\\", '/'))[-1] # XXX: don't encode here if not name.nil? and not name.empty? and @saori_list.include?(name) result = @saori_list[name].request( req.encode(@current_charset)) end return result.encode(@current_charset) end end end ninix-aya-5.0.9/lib/ninix/dll/wmove.rb0000644000175000017500000002061213416507430015746 0ustar shyshy# -*- coding: utf-8 -*- # # wmove.rb - a wmove.dll compatible Saori module for ninix # Copyright (C) 2003-2019 by Shyouzou Sugitani # # This program is free software; you can redistribute it and/or modify it # under the terms of the GNU General Public License (version 2) as # published by the Free Software Foundation. It is distributed in the # hope that it will be useful, but WITHOUT ANY WARRANTY; without even the # implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR # PURPOSE. See the GNU General Public License for more details. # # TODO: # - STANDBY, STANDBY_INSIDE require "gtk3" require_relative "../dll" require_relative "../pix" module Wmove class Saori < DLL::SAORI def initialize super() @__sakura = nil end def need_ghost_backdoor(sakura) @__sakura = sakura end def load(dir: nil) @commands = [[], []] @timeout_id = nil @dir = dir result = 0 if @__sakura.nil? #pass elsif @loaded == 1 result = 2 else @sakura_name = @__sakura.get_selfname() @kero_name = @__sakura.get_keroname() @loaded = 1 result = 1 end return result end def finalize GLib::Source.remove(@timeout_id) unless @timeout_id.nil? @timeout_id = nil @commands = [[], []] @sakura_name = '' @kero_name = '' return 1 end def __check_argument(argument) name = argument[0] result = true list_hwnd = [@sakura_name, @kero_name] ## FIXME: HWND support case name when 'MOVE', 'MOVE_INSIDE', 'MOVETO', 'MOVETO_INSIDE' if argument.length != 4 or not list_hwnd.include?(argument[1]) result = false end when 'ZMOVE', 'WAIT' if argument.length != 3 or not list_hwnd.include?(argument[1]) result = false end when 'STANDBY', 'STANDBY_INSIDE' if argument.length != 6 or \ not list_hwnd.include?(argument[1]) or \ not list_hwnd.include?(argument[2]) result = false end when 'GET_POSITION', 'CLEAR' if argument.length != 2 or not list_hwnd.include?(argument[1]) result = false end when 'GET_DESKTOP_SIZE' if argument.length != 1 result = false end when 'NOTIFY' if argument.length < 3 or argument.length > 8 or \ not list_hwnd.include?(argument[1]) result = false end end return result end def request(req) req_type, argument = evaluate_request(req) result = case req_type when nil RESPONSE[400] when 'GET Version' RESPONSE[204] when 'EXECUTE' execute(argument) else RESPONSE[400] end return result end def execute(args) if args.nil? or args.empty? result = RESPONSE[400] elsif not __check_argument(args) result = RESPONSE[400] else name = args[0] if name == 'GET_POSITION' if args[1] == @sakura_name ## FIXME: HWND support side = 0 elsif args[1] == @kero_name ## FIXME: HWND support side = 1 else return RESPONSE[400] end begin x, y = @__sakura.get_surface_position(side) w, h = @__sakura.get_surface_size(side) result = ("SAORI/1.0 200 OK\r\n" \ + "Result: " + x.to_s + "\r\n" \ + "Value0: " + x.to_s + "\r\n" \ + "Value1: " + (x + (w / 2).to_i).to_s + "\r\n" \ + "Value2: " + (x + w).to_s + "\r\n\r\n") result = result.encode('ascii', :invalid => :replace, :undef => :replace) rescue result = RESPONSE[500] end elsif name == 'GET_DESKTOP_SIZE' begin left, top, scrn_w, scrn_h = @__sakura.get_workarea result = ("SAORI/1.0 200 OK\r\n" \ + "Result: " + scrn_w.to_s + "\r\n" \ + "Value0: " + scrn_w.to_s + "\r\n" \ + "Value1: " + scrn_h.to_s + "\r\n\r\n") result = result.encode('ascii', :invalid => :replace, :undef => :replace) rescue result = RESPONSE[500] end else enqueue_commands(name, args[1..-1]) do_idle_tasks() if @timeout_id.nil? result = RESPONSE[204] end return result end end def enqueue_commands(command, args) #fail "assert" unless ['MOVE', 'MOVE_INSIDE', 'MOVETO', 'MOVETO_INSIDE', # 'ZMOVE', 'WAIT', 'NOTIFY', # 'STANDBY', 'STANDBY_INSIDE', # 'CLEAR'].include?(command) if args[0] == @sakura_name ## FIXME: HWND support side = 0 elsif args[0] == @kero_name ## FIXME: HWND support side = 1 else return # XXX end if command == 'CLEAR' @commands[side] = [] else if ['STANDBY', 'STANDBY_INSIDE'].include?(command) @commands[0] = [] @commands[1] = [] end @commands[side] << [command, args[1..-1]] end end def do_idle_tasks for side in [0, 1] if not @commands[side].empty? command, args = @commands[side].shift case command when 'MOVE', 'MOVE_INSIDE' x, y = @__sakura.get_surface_position(side) vx = args[0].to_i speed = args[1].to_i if command == 'MOVE_INSIDE' w, h = @__sakura.get_surface_size(side) left, top, scrn_w, scrn_h = @__sakura.get_workarea if vx < 0 and x + vx <0 vx = [-x, 0].min elsif vx > 0 and x + vx + w > left + scrn_w vx = [left + scrn_w - w - x, 0].max end end if vx.abs > speed if vx > 0 @__sakura.set_surface_position( side, x + speed, y) @commands[side].insert( 0, [command, [(vx - speed).to_s, args[1]]]) elsif vx < 0 @__sakura.set_surface_position( side, x - speed, y) @commands[side].insert( 0, [command, [(vx + speed).to_s, args[1]]]) end else @__sakura.set_surface_position(side, x + vx, y) end when 'MOVETO', 'MOVETO_INSIDE' x, y = @__sakura.get_surface_position(side) to = args[0].to_i speed = args[1].to_i if command == 'MOVETO_INSIDE' w, h = @__sakura.get_surface_size(side) left, top, scrn_w, scrn_h = @__sakura.get_workarea if to < 0 to = 0 elsif to > left + scrn_w - w to = (left + scrn_w - w) end end if (to - x).abs > speed if to - x > 0 @__sakura.set_surface_position( side, x + speed, y) @commands[side].insert(0, [command, args]) elsif to - x < 0 @__sakura.set_surface_position( side, x - speed, y) @commands[side].insert(0, [command, args]) end else @__sakura.set_surface_position(side, to, y) end when 'STANDBY', 'STANDBY_INSIDE' #pass ## FIXME: not supported yet when 'ZMOVE' if args[0] == '1' @__sakura.raise_surface(side) elsif args[0] == '2' @__sakura.lower_surface(side) else #pass end when 'WAIT' begin wait = Integer(args[0]) # ms rescue wait = 0 end if wait < 25 #pass else @commands[side].insert(0, ['WAIT', (wait - 20).to_s]) end when 'NOTIFY' @__sakura.notify_event(*args) end end if @commands[0].empty? and @commands[1].empty? GLib::Source.remove(@timeout_id) unless @timeout_id.nil? @timeout_id = nil else if @timeout_id.nil? @timeout_id = GLib::Timeout.add(20) { do_idle_tasks } end end end end end end ninix-aya-5.0.9/lib/ninix/dll/mciaudior.rb0000644000175000017500000000607113416507430016570 0ustar shyshy# -*- coding: utf-8 -*- # # mciaudior.rb - a MCIAUDIOR compatible Saori module for ninix # Copyright (C) 2003-2019 by Shyouzou Sugitani # Copyright (C) 2003 by MATSUMURA Namihiko # # This program is free software; you can redistribute it and/or modify it # under the terms of the GNU General Public License (version 2) as # published by the Free Software Foundation. It is distributed in the # hope that it will be useful, but WITHOUT ANY WARRANTY; without even the # implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR # PURPOSE. See the GNU General Public License for more details. # require "uri" require "pathname" begin require "gst" rescue LoadError Gst = nil end require_relative "../home" require_relative "../dll" require_relative "../logging" module Mciaudior class Saori < DLL::SAORI def initialize super() @player = Gst::ElementFactory.make('playbin', 'player') fakesink = Gst::ElementFactory.make('fakesink', 'fakesink') @player.set_property('video-sink', fakesink) bus = @player.bus bus.add_watch do |bus, message| on_message(bus, message) true end @filepath = nil @loop = false end def check_import Gst.nil? ? 0 : 1 end def finalize @player.set_state(Gst::State::NULL) @player = nil @filepath = nil return 1 end def execute(argv) return RESPONSE[400] if argv.nil? argc = argv.length case argc when 1 fail "assert" if @player.nil? case argv[0] when 'stop' @player.set_state(Gst::State::NULL) when 'play', 'loop' @loop = true if argv[0] == 'loop' case @player.get_state(timeout=Gst::SECOND)[1] when Gst::State::PAUSED @player.set_state(Gst::State::PLAYING) return RESPONSE[204] when Gst::State::PLAYING @player.set_state(Gst::State::PAUSED) return RESPONSE[204] end if not @filepath.nil? and File.exist?(@filepath) @player.set_property( 'uri', 'file://' + URI.escape(@filepath)) @player.set_state(Gst::State::PLAYING) end end when 2 if argv[0] == 'load' @player.set_state(Gst::State::NULL) filename = Home.get_normalized_path(argv[1]) if not Pathname(filename).absolute? @filepath = File.join(@dir, filename) else @filepath = filename end end end return RESPONSE[204] end def on_message(bus, message) return if message.nil? # XXX: workaround for Gst Version < 0.11 case message.type when Gst::MessageType::EOS @player.set_state(Gst::State::NULL) @player.set_state(Gst::State::PLAYING) if @loop when Gst::MessageType::ERROR @player.set_state(Gst::State::NULL) err, debug = message.parse_error() Logging::Logging.error("Error: #{err}, #{debug}") @loop = false end end end end ninix-aya-5.0.9/lib/ninix/dll/httpc.rb0000644000175000017500000001035413416507430015735 0ustar shyshy# -*- coding: utf-8 -*- # # httpc.rb - a HTTPC compatible Saori module for ninix # Copyright (C) 2011-2019 by Shyouzou Sugitani # # This program is free software; you can redistribute it and/or modify it # under the terms of the GNU General Public License (version 2) as # published by the Free Software Foundation. It is distributed in the # hope that it will be useful, but WITHOUT ANY WARRANTY; without even the # implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR # PURPOSE. See the GNU General Public License for more details. # require "uri" require "open-uri" require "gtk3" require_relative "../dll" module Httpc class Saori < DLL::SAORI def initialize super() @__sakura = nil @__bg = {} end def finalize @__bg.each_key {|timeout_id| GLib::Source.remove(timeout_id) } return 1 end def need_ghost_backdoor(sakura) @__sakura = sakura end def check_import @__sakura.nil? ? 0 : 1 end def get(url, start: nil, end_: nil) url = URI.parse(url) unless ((url.scheme == 'http' or url.scheme == 'https') and #url.params.nil? and url.query.nil? and url.fragment.nil?) return RESPONSE[400] # XXX end data = open(url) do |f| @charset = f.charset f.read() end unless start.nil? fail "assert" if end_.nil? nc = 0 ls = start.length le = end_.length result = [] while true ns = data.index(start, nc) break if ns.nil? ns += ls ne = data.index(end_, ns) break if ne.nil? nc = (ne + le) result << data[ns..ne-1] end else result = [data] end return result end def execute(argument) return RESPONSE[400] if argument.nil? bg = nil @charset = nil process_tag = nil if argument.length >= 1 if argument[0] == 'bg' if argument.length < 2 # 'bgするならIDを指定していただけませんと。' return RESPONSE[400] end bg = argument[1] argument = argument[2..-1] end end if argument.length >= 1 case argument[0] when 'sjis', 'utf-8', 'utf-16be', 'utf-16le' @charset = argument[0] argument = argument[1..-1] when 'euc' @charset = 'EUC-JP' argument = argument[1..-1] when 'jis' @charset = 'ISO-2022-JP ' argument = argument[1..-1] end case argument[0] when 'erase_tag' process_tag = lambda {} ## FIXME: not supported yet argument = argument[1..-1] when 'translate_tag' process_tag = lambda {} ## FIXME: not supported yet argument = argument[1..-1] end end if argument.empty? ##fail "assert" unless bg.nil? and process_tag.nil? return "SAORI/1.0 200 OK\r\nResult: #{@loaded} \r\n\r\n" elsif argument.length > 3 return RESPONSE[400] elsif argument.length == 2 # FIXME: not supported yet return "SAORI/1.0 200 OK\r\nResult: 0\r\n\r\n" else if not bg.nil? # needs multi-threading? timeout_id = GLib::Timeout.add(1000) { notify(bg, argument, process_tag) } # XXX @__bg[timeout_id] = bg return nil # "SAORI/1.0 204 No Content\r\n\r\n" else data = get(argument[0], :start => argument[1], :end_ => argument[2]) return nil if data.empty? # "SAORI/1.0 204 No Content\r\n\r\n" return data if data == RESPONSE[400] # XXX result = "SAORI/1.0 200 OK\r\nResult: #{data[0]}\r\n" for n in 0..data.length-1 result = [result, "Value#{n}: #{data[n]}\r\n"].join("") end result += "\r\n" return result.encode('Shift_JIS', :invalid => :replace, :undef => :replace) end end end def notify(id, argument, process_tag) result = get(argument[0], :start => argument[1], :end_ => argument[2]) if not process_tag.nil? #pass ## FIXME: not supported yet end @__sakura.notify_event('OnHttpcNotify', id, nil, *result) end end end ninix-aya-5.0.9/lib/ninix/dll/aya.rb0000644000175000017500000036406613416507430015401 0ustar shyshy# -*- coding: utf-8 -*- # # aya.rb - an aya.dll compatible Shiori module for ninix # Copyright (C) 2002-2019 by Shyouzou Sugitani # Copyright (C) 2002, 2003 by MATSUMURA Namihiko # # This program is free software; you can redistribute it and/or modify it # under the terms of the GNU General Public License (version 2) as # published by the Free Software Foundation. It is distributed in the # hope that it will be useful, but WITHOUT ANY WARRANTY; without even the # implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR # PURPOSE. See the GNU General Public License for more details. # # require_relative "../lock" require_relative "../home" require_relative "../logging" module Aya class AyaError < StandardError # XXX #pass end def self.encrypt_char(char) c = char[0].ord j = 0 while j < 3 msb = (c & 0x80) c <<= 1 c &= 0xff unless msb.zero? c |= 0x01 else c &= 0xfe end j += 1 end c ^= 0xd2 return c.chr end def self.decrypt_char(char) c = char[0].ord c ^= 0xd2 j = 0 while j < 3 lsb = (c & 0x01) c >>= 1 unless lsb.zero? c |= 0x80 else c &= 0x7f end j += 1 end return c.chr end def self.decrypt_readline(f) line = '' while true c = f.read(1) break if c == '' line = [line, Aya.decrypt_char(c)].join('') if line.end_with?(10.chr) or \ line.end_with?(0xda.chr) break end end return line end def self.find_not_quoted(line, token) position = 0 while true pos_new = line.index(token, position) if pos_new.nil? pos_new = -1 break elsif pos_new.zero? break end position = line.index('"', position) if not position.nil? and 0 <= position and position < pos_new position += 1 while position < (line.length - 1) if line[position] == '"' position += 1 break else position += 1 next end end else break end end return pos_new end def self.find_comment(line) if line.start_with?("//") return 0, line.length end start = line.length # not line.length - 1 end_ = -1 for token in [" //", "\t//", " //", "/*"] pos_new = Aya.find_not_quoted(line, token) if 0 <= pos_new and pos_new < start start = pos_new if token == '/*' end_ = Aya.find_not_quoted(line, '*/') if end_ >= 0 end_ += 2 end else end_ = line.length end end end if start == line.length start = -1 end return start, end_ end def self.get_aya_version(filelist) return 0 if filelist.empty? dic_files = filelist for filename in dic_files if filename.downcase.end_with?('_shiori3.dic') # XXX open(filename, 'rb', :encoding => 'CP932') do |f| for line in f begin line = line.encode("UTF-8", :invalid => :replace, :undef => :replace) v4 = line.index('for 文 version 4') v5 = line.index('for AYA5') if not v4.nil? and v4 > 0 return 4 elsif not v5.nil? and v5 > 0 return 5 end rescue return 5 end end end end end return 3 end def self.find_dict(aya_dir, f) comment = 0 dic_files = [] for line in f line = line.encode("UTF-8", :invalid => :replace, :undef => :replace) unless comment.zero? end_ = Aya.find_not_quoted(line, '*/') next if end_ < 0 line = line[end_ + 2..-1] comment = 0 end while true start, end_ = Aya.find_comment(line) break if start < 0 line = "" if start.zero? if end_ < 0 comment = 1 line = line[0..start-1] break end line = [line[0..start-1], line[end_..-1]].join('') end line = line.strip() next if line.empty? next unless line.include?(',') key, value = line.split(',', 2) key.strip! value.strip! if key == 'dic' filename = Home.get_normalized_path(value) path = File.join(aya_dir, filename) dic_files << path end end return dic_files end def self.check_version(top_dir, dll_name) filename = nil if File.file?(File.join(top_dir, 'aya.txt')) filename = File.join(top_dir, 'aya.txt') elsif File.file?(File.join(top_dir, 'yaya.txt')) return 6 # XXX: YAYA elsif not dll_name.nil? and \ File.file?(File.join(top_dir, [dll_name[0..-4], 'txt'].join(''))) filename = File.join(top_dir, [dll_name[0..-4], 'txt'].join('')) end version = 0 unless filename.nil? open(filename, :encoding => 'CP932') do |f| version = Aya.get_aya_version(Aya.find_dict(top_dir, f)) ##else ## version = 0 end end return version end class Shiori attr_reader :aya_dir, :dic, :req_header, :req_command, :req_key, :dbpath, :filelist, :saori_library def initialize(dll_name) @dll_name = dll_name unless dll_name.nil? @__AYA_TXT = [dll_name[0..-4], 'txt'].join('') @__DBNAME = [dll_name[0..-5], '_variable.cfg'].join('') else @__AYA_TXT = 'aya.txt' @__DBNAME = 'aya_variable.cfg' end @saori = nil @dic_files = [] end def use_saori(saori) @saori = saori end def find(top_dir, dll_name) result = 0 version = Aya.check_version(top_dir, dll_name) if [3, 4].include?(version) result = 300 end return result end def show_description Logging::Logging.info( "Shiori: AYA compatible module for ninix\n" \ " Copyright (C) 2002-2019 by Shyouzou Sugitani\n" \ " Copyright (C) 2002, 2003 by MATSUMURA Namihiko") end def reset @boot_time = Time.new @aitalk = 0 @first_boot = 0 @dic_files = [] @dic = AyaDictionary.new(self) @global_namespace = AyaGlobalNamespace.new(self) @system_functions = AyaSystemFunctions.new(self) @logfile = nil @filelist = {} reset_request() @ver_3 = false # Ver.3 end def reset_request @req_command = '' @req_protocol = '' @req_key = [] @req_header = {} @global_namespace.reset_res_reference() end def load(dir: nil) @aya_dir = dir @dbpath = File.join(@aya_dir, @__DBNAME) @saori_library = AyaSaoriLibrary.new(@saori, @aya_dir) reset() @first_boot = @global_namespace.load_database(self) begin path = File.join(@aya_dir, @__AYA_TXT) open(path, :encoding => 'CP932') do |aya_txt| load_aya_txt(aya_txt) end rescue #except IOError: Logging::Logging.debug('cannot read aya.txt') return 0 rescue #except AyaError as error: Logging::Logging.debug(error) return 0 end # default setting unless @global_namespace.exists('log') @global_namespace.put('log', '') end unless @global_namespace.exists('logmode') @global_namespace.put('logmode', 'simple') end for path in @dic_files basename = File.basename(path, '.*') ext = File.extname(path) ext = ext.downcase if ext == '.ayc' encrypted = true else encrypted = false end begin open(path, 'rb') do |dicfile| @dic.load(dicfile, encrypted) end rescue Logging::Logging.debug('cannot read ' + path.to_s) next end end unless @global_namespace.exists('aitalkinterval') # Ver.3 @global_namespace.put('aitalkinterval', 180) end unless @global_namespace.exists('securitylevel') # Ver.3 @global_namespace.put('securitylevel', 'high') end unless @dic.get_function('OnRequest') # Ver.3 @ver_3 = true end request("NOTIFY SHIORI/3.0\r\n" \ "ID: OnLoad\r\n" \ "Sender: AYA\r\n" \ "SecurityLevel: local\r\n" \ "Path: " + @aya_dir.gsub('/', "\\") + "\r\n\r\n".encode('CP932', :invalid => :replace, :undef => :replace)) return 1 end def load_aya_txt(f) comment = 0 for line in f line = line.encode("UTF-8", :invalid => :replace, :undef => :replace) unless comment.zero? end_ = Aya.find_not_quoted(line, '*/') next if end_ < 0 line = line[end_ + 2..-1] comment = 0 end while true start, end_ = Aya.find_comment(line) break if start < 0 line = "" if start.zero? if end_ < 0 comment = 1 line = line[0..start-1] break end line = [line[0..start-1], line[end_..-1]].join('') end line = line.strip() next if line.empty? next unless line.include?(',') key, value = line.split(',', 2) evaluate_config(key.strip, value.strip) end end def evaluate_config(key, value) if key == 'dic' filename = Home.get_normalized_path(value) path = File.join(@aya_dir, filename) @dic_files << path elsif key == 'log' path = File.join(@aya_dir, value.to_s) begin f = open(path, 'w') rescue Logging::Logging.debug('cannnot open ' + path) else @logfile.close() unless @logfile.nil? @logfile = f @global_namespace.put('log', value.to_s) end elsif key == 'logmode' #pass # FIXME elsif key == 'aitalkinterval' # Ver.3 unless @global_namespace.exists('aitalkinterval') begin Integer(value) rescue Logging::Logging.debug( 'Could not convert ' + value.to_s + ' to an integer') else @global_namespace.put('aitalkinterval', value.to_i) end end elsif not key.nil? and not key.empty? begin value = Integer(value) rescue value = value.to_s end @global_namespace.put(key, value) end end def get_dictionary @dic end def get_ghost_dir @aya_dir end def get_global_namespace @global_namespace end def get_system_functions @system_functions end def get_boot_time @boot_time end def unload request(["NOTIFY SHIORI/3.0\r\n", "ID: OnUnload\r\n", "Sender: AYA\r\n", "SecurityLevel: local\r\n\r\n"].join("").encode('CP932', :invalid => :replace, :undef => :replace)) @global_namespace.save_database() @saori_library.unload() @logfile.close() unless @logfile.nil? for key in @filelist.keys() @filelist[key].close() end end # SHIORI API def request(req_string) header = req_string.force_encoding('CP932').split(/\r?\n/, 0) unless header.nil? or header.empty? line = header.shift line = line.strip() req_list = line.split(nil, -1) if req_list.length >= 2 @req_command = req_list[0].strip() @req_protocol = req_list[1].strip() end for line in header line = line.strip() next if line.empty? line = line.encode("UTF-8", :invalid => :replace, :undef => :replace) next unless line.include?(':') key, value = line.split(':', 2) key.strip! value.strip! begin value = Integer(value) rescue value = value.to_s end @req_key << key @req_header[key] = value end end unless @first_boot.zero? case @req_header['ID'] when 'OnBoot', 'OnVanished', 'OnGhostChanged' @first_boot = 0 Logging::Logging.debug( 'We lost the ' + @__DBNAME.to_s + '. Initializing....') request("NOTIFY SHIORI/3.0\r\n" \ "ID: OnFirstBoot\r\n" \ "Sender: ninix\r\n" \ "SecurityLevel: local\r\n" \ "Reference0: 0\r\n\r\n".encode('CP932', :invalid => :replace, :undef => :replace)) when 'OnFirstBoot' @first_boot = 0 end end result = '' func = @dic.get_function('OnRequest') if func.nil? and @req_header.include?('ID') # Ver.3 for i in 0..8 @global_namespace.remove(['reference', i.to_s].join('')) end for i in 0..8 key = ['Reference', i.to_s].join('') if @req_header.include?(key) @global_namespace.put(['reference', i.to_s].join(''), @req_header[key]) end end unless @req_header['ID'].start_with?('On') prefix = 'On_' else prefix = '' end func = @dic.get_function( [prefix, @req_header['ID']].join('')) end unless func.nil? result = func.call() end if @ver_3 and @req_header.include?('ID') and \ @req_header['ID'] == 'OnSecondChange' # Ver.3 aitalkinterval = @global_namespace.get('aitalkinterval') if aitalkinterval > 0 @aitalk += 1 if @aitalk > aitalkinterval @aitalk = 0 result = request("GET SHIORI/3.0\r\n" \ "ID: OnAiTalk\r\n" \ "Sender: ninix\r\n" \ "SecurityLevel: local\r\n\r\n".encode('CP932', :invalid => :replace, :undef => :replace)) reset_request() return result end end end reset_request() if @ver_3 # Ver.3 result = ("SHIORI/3.0 200 OK\r\n" \ "Sender: AYA\r\n" \ "Value: #{result}\r\n\r\n".encode('CP932', :invalid => :replace, :undef => :replace)) return result else return result.encode('CP932', :invalid => :replace, :undef => :replace) end end end class AyaSecurity DENY = 0 ACCEPT = 1 CONFIG = 'aya_security.cfg' def initialize(aya) @__aya = aya @__cfg = '' @__aya_dir = File.absolute_path(@__aya.aya_dir) @__fwrite = [[DENY, '*'], [ACCEPT, @__aya_dir]] @__fread = [[ACCEPT, '*']] @__loadlib = [[ACCEPT, '*']] @__logfile = nil load_cfg() @__fwrite.reverse! @__fread.reverse! end def load_cfg path = [] head, tail = File.split(@__aya_dir) current = head while not tail.empty? and tail != "/" path << tail head, tail = File.split(current) current = head end path << current current_dir = '' break_flag = false while not path.empty? current_dir = File.join(current_dir, path.pop) if File.exist?(File.join(current_dir, CONFIG)) @__cfg = File.join(current_dir, CONFIG) break_flag = true break end end unless break_flag # default setting Logging::Logging.warning('*WARNING : aya_security.cfg - file not found.') return end begin open(@__cfg, :encoding => 'CP932') do |f| name = '' data = {} @comment = 0 line = readline(f) break_flag = flase while line if line.include?('[') unless name.empty? name = expand_path(name) if name == '*' break_flag = true break end if @__aya_dir.start_with?(name) break_flag = true break end end data = {} start = line.index('[') if start.nil? start = -1 end end_ = line.index(']') if end_.nil? end_ = -1 end if end_ < 0 end_ = line.length end name = line[start + 1..end_-1] else if line.include?(',') key, value = line.split(',', 2) key.strip! value.strip! if key.start_with?('deny.') key = key[5..-1] list_ = data.get(key, []) list_ << [DENY, value] data[key] = list_ elsif key.start_swith?('accept.') key = key[7..-1] list_ = data.get(key, []) list_ << [ACCEPT, value] data[key] = list_ elsif key == 'log' head, tail = File.split(@__cfg) value = ['./', value].join('') value = File.join(head, value) value = File.absolute_path(value) data[key] = value else #pass # error end end end line = readline(f) end unless break_flag if name.empty? Logging::Logging.warning('*WARNING : aya_security.cfg - no entry found for ' + File.join(@__aya_dir, 'aya.dll') + '.') return end end end rescue #except IOError: Logging::Logging.debug('cannot read aya.txt') return rescue #except AyaError as error: Logging::Logging.debug(error) return end @__fwrite.concat(data.get('fwrite', [])) for i in 0..@__fwrite.length-1 @__fwrite[i][1] = expand_path(@__fwrite[i][1]) end @__fread.concat(data.get('fread', [])) for i in 0..@__fread.length-1 @__fread[i][1] = expand_path(@__fread[i][1]) end @__loadlib.concat(data.get('loadlib', [])) if data.include?('log') @__logfile = data['log'] end end def expand_path(path) head, tail = File.split(@__cfg) return path if path == '*' return head if path == '' meta = path.rfind('%CFGDIR') if meta >= 0 path = Home.get_normalized_path(path[meta + 7..-1]) path = ['./', path].join('') path = File.join(head, path) else path = Home.get_normalized_path(path) end path = File.absolute_path(path) return path end def readline(f) for line in f unless @comment.zero? end_ = Aya.find_not_quoted(line, '*/') next if end_ < 0 line = line[end_ + 2..-1] @comment = 0 end while true start, end_ = Aya.find_comment(line) break if start < 0 line = "" if start.zero? if end_ < 0 @comment = 1 line = line[0..start-1] break end line = [line[0..start-1], ' ', line[end_..-1]].join('') end line = line.strip() next if line.empty? break end return line end def check_path(path, flag: 'w') result = 0 abspath = File.absolute_path(path) head, tail = File.split(abspath) if tail != 'aya_security.cfg' if ['w', 'w+', 'r+', 'a', 'a+'].include?(flag) for perm, name in @__fwrite if name == '*' or abspath[0..name.length-1] == name case perm when ACCEPT result = 1 when DENY result = 0 else next end break end end elsif flag == 'r' result = 1 # default for perm, name in @__fread if name == '*' or abspath[0..name.length-1] == name case perm when ACCEPT result = 1 when DENY result = 0 else next end break end end end end if @__logfile and result.zero? if flag == 'r' logging('許可されていないファイルまたはディレクトリ階層の読み取りをブロックしました.', 'file', abspath) else logging('許可されていないファイルまたはディレクトリ階層への書き込みをブロックしました.', 'file', abspath) end end return result end def check_lib(dll) result = 1 # default head, tail = File.split(Home.get_normalized_path(dll)) dll_name = tail for perm, name in @__loadlib if name == '*' or dll_name == name if perm == ACCEPT result = 1 elsif perm == DENY result = 0 end end end if @__logfile and result.zero? logging('許可されていない DLL のロードをブロックしました.', 'dll ', dll_name) end return result end def logging(message, target_type, target) ## FIXME return nil if @__logfile.nil? begin open(@__logfile, 'a') do |f| Lock.lockfile(f) aya_dll = File.join(@__aya_dir, 'aya.dll') line = ['*WARNING : ', message.to_s, "\n"].join('') line = [line, 'AYA : ', aya_dll, "\n"].join('') line = [line, 'date : ', Time.now.strftime('%Y/%m/%d(%a) %H:%M:%S'), "\n"].join('') line = [line, target_type.to_s, ' : ', target.to_s, "\n"].join('') f.write(line) f.write("\n") Lock.unlockfile(f) end rescue Logging::Logging.debug('cannnot open ' + @__logfile.to_s) end return nil end end class AyaDictionary attr_reader :aya def initialize(aya) @aya = aya @functions = {} @global_macro = {} end def get_function(name) if @functions.has_key?(name) return @functions[name] else return nil end end def load(f, encrypted) all_lines = [] local_macro = {} logical_line = '' comment = 0 while true if encrypted line = Aya.decrypt_readline(f) else line = f.gets end break if line.nil? # EOF line = line.force_encoding('CP932').encode("UTF-8", :invalid => :replace, :undef => :replace) unless comment.zero? end_ = Aya.find_not_quoted(line, '*/') next if end_ < 0 line = line[end_ + 2..-1] comment = 0 end while true start, end_ = Aya.find_comment(line) break if start < 0 line = "" if start.zero? if end_ < 0 comment = 1 line = line[0..start-1] break end line = [line[0..start-1], ' ', line[end_..-1]].join('') end line = line.gsub(/^[\s ]+|[\s ]+$/, "") # 全角空白も除去 next if line.empty? if line.end_with?('/') logical_line = [logical_line, line[0..-2]].join('') else logical_line = [logical_line, line].join('') buf = line # preprocess if buf.start_with?('#') buf = buf[1..-1].strip() for (tag, target) in [['define', local_macro], ['globaldefine', @global_macro]] if buf.start_with?(tag) buf = buf[tag.length..-1].strip() i = 0 while i < buf.length if buf[i] == " " or buf[i] == "\t" or \ buf[i] == " " key = buf[0..i-1].strip() target[key] = buf[i..-1].strip() break end i += 1 end break end end logical_line = '' # reset next end for macro in [local_macro, @global_macro] logical_line = preprocess(macro, logical_line) end # multi statement list_lines = split_line(logical_line.strip()) unless list_lines.empty? all_lines.concat(list_lines) end logical_line = '' # reset end end for line in all_lines while true pos = Aya.find_not_quoted(line, ' ') if pos.zero? line = line[1..-1] elsif pos > 0 line = [line[0..pos-1], ' ', line[pos + 1..-1]].join('') else break end end end evaluate_lines(all_lines, File.split(f.path)[1]) end def split_line(line) lines = [] while true break if line.nil? or line.empty? pos = line.length # not line.length - 1 token = '' for x in ['{', '}'] pos_new = Aya.find_not_quoted(line, x) if 0 <= pos_new and pos_new < pos pos = pos_new token = x end end unless pos.zero? new = line[0..pos-1].strip() else # '{' or '}' new = "" end line = line[pos + token.length..-1].strip() unless new.empty? lines << new end if token != '' lines << token end end return lines end def preprocess(macro, line) for key in macro.keys value = macro[key] line = line.gsub(key, value) end return line end SPECIAL_CHARS = [']', '(', ')', '[', '+', '-', '*', '/', '=', ':', ';', '!', '{', '}', '%', '&', '#', '"', '<', '>', ',', '?'] def evaluate_lines(lines, file_name) prev = nil name = nil function = [] option = nil block_nest = 0 for i in 0..lines.length-1 line = lines[i] if line == '{' unless name.nil? if block_nest > 0 function << line end block_nest += 1 else if prev.nil? Logging::Logging.debug( 'syntax error in ' + file_name.to_s + ': unbalanced "{" at ' \ 'the top of file') else Logging::Logging.debug( 'syntax error in ' + file_name.to_s + ': unbalanced "{" at ' \ 'the bottom of function "' + prev.to_s + '"') end end elsif line == '}' unless name.nil? block_nest -= 1 if block_nest > 0 function << line elsif block_nest.zero? @functions[name] = AyaFunction.new(self, name, function, option) # reset prev = name name = nil function = [] option = nil end else if prev.nil? Logging::Logging.debug( 'syntax error in ' + file_name.to_s + ': unbalanced "}" at ' \ 'the top of file') else Logging::Logging.debug( 'syntax error in ' + file_name.to_s + ': unbalanced "}" at ' \ 'the bottom of function "' + prev.to_s + '"') end block_nest = 0 end elsif name.nil? if line.include?(':') name, option = line.split(':', 2) name.strip! option.strip! else name = line end for char in SPECIAL_CHARS if name.include?(char) Logging::Logging.debug( 'illegal function name "' + name.to_s + '" in ' + file_name.to_s) end end function = [] else if not name.nil? and block_nest > 0 function << line else Logging::Logging.debug('syntax error in ' + file_name + ': ' + line) end end end end end class AyaFunction TYPE_INT = 10 TYPE_FLOAT = 11 TYPE_DECISION = 12 TYPE_RETURN = 13 TYPE_BLOCK = 14 TYPE_SUBSTITUTION = 15 TYPE_INC = 16 TYPE_DEC = 17 TYPE_IF = 18 TYPE_WHILE = 19 TYPE_FOR = 20 TYPE_BREAK = 21 TYPE_CONTINUE = 22 TYPE_SWITCH = 23 TYPE_CASE = 24 TYPE_STRING_LITERAL = 25 TYPE_STRING = 26 TYPE_OPERATOR = 27 TYPE_STATEMENT = 28 TYPE_CONDITION = 29 TYPE_SYSTEM_FUNCTION = 30 TYPE_FUNCTION = 31 TYPE_ARRAY_POINTER = 32 TYPE_ARRAY = 33 TYPE_VARIABLE_POINTER = 34 TYPE_VARIABLE = 35 TYPE_TOKEN = 36 CODE_NONE = 40 CODE_RETURN = 41 CODE_BREAK = 42 CODE_CONTINUE = 43 Re_f = Regexp.new('\A[-+]?\d+(\.\d*)\z') Re_d = Regexp.new('\A[-+]?\d+\z') Re_d_ = Regexp.new('[-+]?\d+\z') Re_b = Regexp.new('\A[-+]?0[bB][01]+\z') Re_x = Regexp.new('\A[-+]?0[xX][\dA-Fa-f]+\z') Re_if = Regexp.new('\Aif\s') Re_elseif = Regexp.new('\Aelseif\s') Re_while = Regexp.new('\Awhile\s') Re_for = Regexp.new('\Afor\s') Re_switch = Regexp.new('\Aswitch\s') Re_case = Regexp.new('\Acase\s') Re_when = Regexp.new('\Awhen\s') SPECIAL_CHARS = [']', '(', ')', '[', '+', '-', '*', '/', '=', ':', ';', '!', '{', '}', '%', '&', '#', '"', '<', '>', ',', '?'] def initialize(dic, name, lines, option) @dic = dic @name = name @status = CODE_NONE @lines = parse(lines) if option == 'nonoverlap' @nonoverlap = [[], [], []] else @nonoverlap = nil end if option == 'sequential' @sequential = [[], []] else @sequential = nil end end def parse(lines) result = [] i = 0 while i < lines.length line = lines[i] if line == '--' result << [TYPE_DECISION, []] elsif line == 'return' result << [TYPE_RETURN, []] elsif line == 'break' result << [TYPE_BREAK, []] elsif line == 'continue' result << [TYPE_CONTINUE, []] elsif line == '{' inner_func = [] i, inner_func = get_block(lines, i) result << [TYPE_BLOCK, parse(inner_func)] elsif not Re_if.match(line).nil? inner_blocks = [] while true current_line = lines[i] if not Re_if.match(current_line).nil? condition_tokens = AyaStatement.new( current_line[2..-1].strip()).tokens condition = parse_condition(condition_tokens) elsif not Re_elseif.match(current_line).nil? condition_tokens = AyaStatement.new( current_line[6..-1].strip()).tokens condition = parse_condition(condition_tokens) else condition = [TYPE_CONDITION, nil] end inner_block = [] i, inner_block = get_block(lines, i + 1) if condition.nil? inner_blocks = [] break end entry = [] entry << condition entry << parse(inner_block) inner_blocks << entry if i + 1 >= lines.length break end next_line = lines[i + 1] if Re_elseif.match(next_line).nil? and \ next_line != 'else' break end i = (i + 1) end unless inner_blocks.empty? result << [TYPE_IF, inner_blocks] end elsif not Re_while.match(line).nil? condition_tokens = AyaStatement.new(line[5..-1].strip()).tokens condition = parse_condition(condition_tokens) inner_block = [] i, inner_block = get_block(lines, i + 1) result << [TYPE_WHILE, [condition, parse(inner_block)]] elsif not Re_for.match(line).nil? inner_block = [] i, inner_block = get_block(lines, i + 1) end_ = Aya.find_not_quoted(line, ';') if end_ < 0 Logging::Logging.debug( 'syntax error in function "' + @name.to_s + '": ' \ 'illegal for statement "' + line.to_s + '"') else init = parse([line[3..end_-1].strip()]) condition = line[end_ + 1..-1].strip() end_ = Aya.find_not_quoted(condition, ';') if end_ < 0 Logging::Logging.debug( 'syntax error in function "' + @name.to_s + '": ' \ 'illegal for statement "' + line.to_s + '"') else reset = parse([condition[end_ + 1..-1].strip()]) condition_tokens = AyaStatement.new( condition[0..end_-1].strip()).tokens condition = parse_condition(condition_tokens) unless condition.nil? result << [TYPE_FOR, [[init, condition, reset], parse(inner_block)]] end end end elsif not Re_switch.match(line).nil? index = parse_token(line[6..-1].strip()) ##fail "assert" unless [].include?(index[0]) # FIXME inner_block = [] i, inner_block = get_block(lines, i + 1) result << [TYPE_SWITCH, [index, parse(inner_block)]] elsif not Re_case.match(line).nil? left = parse_token(line[4..-1].strip()) ##fail "assert" unless [].include?(left[0]) # FIXME i, block = get_block(lines, i + 1) inner_blocks = [] j = 0 while true current_line = block[j] unless Re_when.match(current_line).nil? right = current_line[4..-1].strip() else # 'others' right = nil end inner_block = [] j, inner_block = get_block(block, j + 1) unless right.nil? argument = AyaArgument.new(right) while argument.has_more_tokens() entry = [] right = argument.next_token() tokens = AyaStatement.new(right).tokens if ['-', '+'].include?(tokens[0]) value_min = parse_statement([tokens.shift, tokens.shift]) else value_min = parse_statement([tokens.shift]) end value_max = value_min unless tokens.empty? if tokens[0] != '-' Logging::Logging.debug( 'syntax error in function ' \ '"' + @name.to_s + '": when ' + right.to_s) next else tokens.shift end if tokens.length > 2 or \ (tokens.length == 2 and \ not ['-', '+'].include?(tokens[0])) Logging::Logging.debug( 'syntax error in function ' \ '"' + @name + '": when ' + right.to_s) next else value_max = parse_statement(tokens) end end entry << [value_min, value_max] entry << parse(inner_block) inner_blocks << entry end else entry = [] entry << right entry << parse(inner_block) inner_blocks << entry end if j + 1 == block.length break end next_line = block[j + 1] if Re_when.match(next_line).nil? and \ next_line != 'others' break end j += 1 end result << [TYPE_CASE, [left, inner_blocks]] elsif Aya.find_not_quoted(line, ';') >= 0 end_ = Aya.find_not_quoted(line, ';') new_line = line[end_ + 1..-1].strip() line = line[0..end_-1].strip() if i > 0 new_lines = lines[0..i-1] else new_lines = [] end unless line.empty? new_lines << line end unless new_line.empty? new_lines << new_line end new_lines.concat(lines[i + 1..-1]) lines = new_lines next elsif is_substitution(line) tokens = AyaStatement.new(line).tokens left = parse_token(tokens[0]) unless [TYPE_ARRAY, TYPE_VARIABLE, TYPE_TOKEN].include?(left[0]) Logging::Logging.debug( 'syntax error in function "' + @name.to_s + '": ' \ 'illegal substitution "' + line + '"') else if left[0] == TYPE_TOKEN # this cannot be FUNCTION left[0] = TYPE_VARIABLE left[1] = [left[1], nil] end ope = [TYPE_OPERATOR, tokens[1]] right = parse_statement(tokens[2..-1]) result << [TYPE_SUBSTITUTION, [left, ope, right]] end elsif is_inc_or_dec(line) # ++/-- ope = line[-2..-1] var = parse_token(line[0..-3]) unless [TYPE_ARRAY, TYPE_VARIABLE, TYPE_TOKEN].include?(var[0]) Logging::Logging.debug( 'syntax error in function "' + @name.to_s + '": ' \ 'illegal increment/decrement "' + line.to_s + '"') else if var[0] == TYPE_TOKEN var[0] = TYPE_VARIABLE var[1] = [var[1], nil] end if ope == '++' result << [TYPE_INC, var] elsif ope == '--' result << [TYPE_DEC, var] else return nil # should not reach here end end else tokens = AyaStatement.new(line).tokens if tokens[-1] == '"' # This is kluge. Logging::Logging.debug( 'syntax error in function "' + @name.to_s + '": ' \ 'unbalanced \'"\' or \'"\' in string ' + tokens.join('')) token = tokens[0..-2].join('') if not token.nil? and token[0] == '"' token = token[1..-1] end unless token.empty? unless token.include?('%') result << [TYPE_STRING_LITERAL, token] else result << [TYPE_STRING, token] end end elsif tokens.length == 1 result << parse_token(tokens[0]) else result << parse_statement(tokens) end end i += 1 end result << [TYPE_DECISION, []] return result end def parse_statement(statement_tokens) n_tokens = statement_tokens.length statement = [] if n_tokens == 1 statement = [TYPE_STATEMENT, parse_token(statement_tokens[0])] elsif ['+', '-'].include?(statement_tokens[0]) tokens = ['0'] tokens.concat(statement_tokens) statement = parse_statement(tokens) else ope_index = nil for ope in ['+', '-'] if statement_tokens.include?(ope) new_index = statement_tokens.index(ope) if ope_index.nil? or new_index < ope_index ope_index = new_index end end end if ope_index.nil? statement_tokens.reverse! begin for ope in ['*', '/', '%'] if statement_tokens.include?(ope) new_index = statement_tokens.index(ope) if ope_index.nil? or new_index < ope_index ope_index = new_index end end end unless ope_index.nil? ope_index = (-1 - ope_index) end ensure statement_tokens.reverse! end end if [nil, -1, 0, n_tokens - 1].include?(ope_index) if statement_tokens[0].start_with?('"') and \ statement_tokens[0].end_with?('"') and \ statement_tokens[-1].start_with?('"') and \ statement_tokens[-1].end_with?('"') Logging::Logging.debug( 'syntax error in function "' + @name.to_s + '": ' \ '\'"\' in string ' + statement_tokens.join(' ')) return parse_token(statement_tokens.join(' ')) else Logging::Logging.debug( 'syntax error in function "' + @name.to_s + '": ' \ 'illegal statement "' + statement_tokens.join(' ') + '"') return [] end else ope = [TYPE_OPERATOR, statement_tokens[ope_index]] if statement_tokens[0..ope_index-1].length == 1 if statement_tokens[0].start_with?('(') tokens = AyaStatement.new(statement_tokens[0][1..-2]).tokens left = parse_statement(tokens) else left = parse_token( statement_tokens[0..ope_index-1][0]) end else left = parse_statement(statement_tokens[0..ope_index-1]) end if statement_tokens[ope_index + 1..-1].length == 1 if statement_tokens[-1].start_with?('(') tokens = AyaStatement.new( statement_tokens[ope_index + 1][1..-2]).tokens right = parse_statement(tokens) else right = parse_token( statement_tokens[ope_index + 1..-1][0]) end else right = parse_statement( statement_tokens[ope_index + 1..-1]) end statement = [TYPE_STATEMENT, left, ope, right] end end return statement end def parse_condition(condition_tokens) n_tokens = condition_tokens.length condition = nil ope_index = nil condition_tokens.reverse! begin for ope in ['&&', '||'] if condition_tokens.include?(ope) new_index = condition_tokens.index(ope) return nil if new_index.zero? # XXX if ope_index.nil? or new_index < ope_index ope_index = new_index end end end unless ope_index.nil? ope_index = (-1 - ope_index) end ensure condition_tokens.reverse! end if ope_index.nil? for ope in ['==', '!=', '>', '<', '>=', '<=', '_in_', '!_in_'] if condition_tokens.include?(ope) new_index = condition_tokens.index(ope) if ope_index.nil? or new_index < ope_index ope_index = new_index end end end if [nil, -1, 0, n_tokens - 1].include?(ope_index) Logging::Logging.debug( 'syntax error in function "' + @name.to_s + '": ' \ 'illegal condition "' + condition_tokens.join(' ') + '"') return nil end ope = [TYPE_OPERATOR, condition_tokens[ope_index]] if condition_tokens[0..ope_index-1].length == 1 left = parse_token(condition_tokens[0..ope_index-1][0]) else left = parse_statement(condition_tokens[0..ope_index-1]) end if condition_tokens[ope_index + 1..-1].length == 1 right = parse_token(condition_tokens[ope_index + 1..-1][0]) else right = parse_statement(condition_tokens[ope_index + 1..-1]) end condition = [TYPE_CONDITION, [left, ope, right]] else ope = [TYPE_OPERATOR, condition_tokens[ope_index]] left = parse_condition(condition_tokens[0..ope_index-1]) right = parse_condition(condition_tokens[ope_index + 1..-1]) unless left.nil? or right.nil? condition = [TYPE_CONDITION, [left, ope, right]] end end return condition end def parse_argument(args) argument = AyaArgument.new(args) arguments = [] while argument.has_more_tokens() token = argument.next_token() if token.start_with?('&') result = parse_token(token[1..-1]) case result[0] when TYPE_ARRAY arguments << [TYPE_ARRAY_POINTER, result[1]] when TYPE_VARIABLE arguments << [TYPE_VARIABLE_POINTER, result[1]] when TYPE_TOKEN arguments << [TYPE_VARIABLE_POINTER, [result[1], nil]] else Logging::Logging.debug( 'syntax error in function "' + @name.to_s + '": ' \ 'illegal argument "' + token.to_s + '"') end elsif token.start_with?('(') unless token.end_with?(')') Logging::Logging.debug( 'syntax error in function "' + @name.to_s + '": ' \ 'unbalanced "(" in the string(' + token.to_s + ')') return nil else statement = AyaStatement.new(token[1..-2]) arguments << parse_statement(statement.tokens) end else arguments << parse_statement([token]) end end return arguments end def parse_token(token) result = [] if not Re_f.match(token).nil? result = [TYPE_FLOAT, token] elsif not Re_d.match(token).nil? result = [TYPE_INT, token] elsif token.start_with?('"') text = token[1..-1] if text.end_with?('"') text = text[0..-2] end if text.count('"') > 0 Logging::Logging.debug( 'syntax error in function "' + @name.to_s + '": ' \ '\'"\' in string "' + text.to_s + '"') end unless text.include?('%') result = [TYPE_STRING_LITERAL, text] else result = [TYPE_STRING, text] end else pos_parenthesis_open = token.index('(') pos_block_open = token.index('[') if not pos_parenthesis_open.nil? and \ (pos_block_open.nil? or \ pos_parenthesis_open < pos_block_open) # function unless token.end_with?(')') Logging::Logging.debug( 'syntax error: unbalanced "(" in "' + token.to_s + '"') else func_name = token[0..pos_parenthesis_open-1] arguments = parse_argument( token[pos_parenthesis_open + 1..-2]) break_flag = false for char in SPECIAL_CHARS if func_name.include?(char) Logging::Logging.debug( 'illegal character "' + char + '" in ' \ 'the name of function "' + token.to_s + '"') break_flag = true break end end unless break_flag if @dic.aya.get_system_functions().exists( func_name) if func_name == 'LOGGING' result = [TYPE_SYSTEM_FUNCTION, [func_name, arguments, token[pos_parenthesis_open + 1..-2]]] else result = [TYPE_SYSTEM_FUNCTION, [func_name, arguments]] end else result = [TYPE_FUNCTION, [func_name, arguments]] end end end elsif not pos_block_open.nil? and pos_block_open != -1 # array unless token.end_with?(']') Logging::Logging.debug( 'syntax error: unbalanced "[" in "' + token.to_s + '"') else array_name = token[0..pos_block_open-1] index = parse_token(token[pos_block_open + 1..-2]) break_flag = false for char in SPECIAL_CHARS if array_name.include?(char) Logging::Logging.debug( 'illegal character "' + char.to_s + '" in ' \ 'the name of array "' + token.to_s + '"') break_flag = true break end end unless break_flag result = [TYPE_ARRAY, [array_name, index]] end end else # variable or function break_flag = false for char in SPECIAL_CHARS if token.include?(char) Logging::Logging.debug( 'syntax error in function "' + @name.to_s + '": ' \ 'illegal character "' + char + '" in the name of ' \ 'function/variable "' + token + '"') break_flag = true break end end unless break_flag result = [TYPE_TOKEN, token] end end end return result end def call(argv: nil) namespace = AyaNamespace.new(@dic.aya) _argv = [] if argv.nil? namespace.put('_argc', 0) else namespace.put('_argc', argv.length) for i in 0..argv.length-1 if argv[i].is_a?(Hash) _argv << argv[i]['value'] else _argv << argv[i] end end end namespace.put('_argv', _argv) @status = CODE_NONE result = evaluate(namespace, @lines, -1, 0) if result.nil? result = '' end unless argv.nil? for i in 0..argv.length-1 if argv[i].is_a?(Hash) value = _argv[i] name = argv[i]['name'] namespace = argv[i]['namespace'] index = argv[i]['index'] namespace.put(name, value, :index => index) end end end return result end def evaluate(namespace, lines, index_to_return, is_inner_block) result = [] alternatives = [] for line in lines next if line.nil? or line.empty? if [TYPE_DECISION, TYPE_RETURN, TYPE_BREAK, TYPE_CONTINUE].include?(line[0]) or \ [CODE_RETURN, CODE_BREAK, CODE_CONTINUE].include?(@status) unless alternatives.empty? unless is_inner_block.zero? if index_to_return < 0 result << alternatives.sample elsif index_to_return <= (alternatives.length - 1) result << alternatives[index_to_return] else # out of range result << '' end else result << alternatives end alternatives = [] end if line[0] == TYPE_RETURN or \ @status == CODE_RETURN @status = CODE_RETURN break elsif line[0] == TYPE_BREAK or \ @status == CODE_BREAK @status = CODE_BREAK break elsif line[0] == TYPE_CONTINUE or \ @status == CODE_CONTINUE @status = CODE_CONTINUE break end elsif line[0] == TYPE_BLOCK inner_func = line[1] local_namespace = AyaNamespace.new(@dic.aya, :parent => namespace) result_of_inner_func = evaluate(local_namespace, inner_func, -1, 1) unless result_of_inner_func.nil? alternatives << result_of_inner_func end elsif line[0] == TYPE_SUBSTITUTION left, ope, right = line[1] ##fail "assert" unless [TYPE_ARRAY, TYPE_VARIABLE].include?(left[0]) ##fail "assert" unless ope[0] == TYPE_OPERATOR ope = ope[1] if [':=', '+:=', '-:=', '*:=', '/:=', '%:='].include?(ope) type_float = 1 else type_float = 0 end ##fail "assert" unless right[0] == TYPE_STATEMENT right_result = evaluate_statement(namespace, right, type_float) unless ['=', ':='].include?(ope) left_result = evaluate_token(namespace, left) right_result = operation(left_result, ope[0], right_result, type_float) ope = ope[1..-1] end substitute(namespace, left, ope, right_result) elsif line[0] == TYPE_INC or \ line[0] == TYPE_DEC # ++/-- if line[0] == TYPE_INC ope = '++' elsif line[0] == TYPE_DEC ope = '--' else return nil # should not reach here end var = line[1] ##fail "assert" unless [TYPE_ARRAY, TYPE_VARIABLE].include?(var[0]) var_name = var[1][0] if var_name.start_with?('_') target_namespace = namespace else target_namespace = @dic.aya.get_global_namespace() end value = evaluate_token(namespace, var) if var[0] == TYPE_ARRAY # _argv[n] only index = evaluate_token(namespace, var[1][1]) begin index = Integer(index) rescue Logging::Logging.debug( 'index of array has to be integer: ' \ '' + var_name.to_s + '[' + var[1][1][0].to_s + ']') return nil end end if value.is_a?(Integer) or value.is_a?(Float) if ope == '++' target_namespace.put(var_name, value.to_i + 1, :index => index) elsif ope == '--' target_namespace.put(var_name, value.to_i - 1, :index => index) else return nil # should not reach here end else Logging::Logging.debug( 'illegal increment/decrement:' \ 'type of variable ' + var_name.to_s + ' is not number') end elsif line[0] == TYPE_IF inner_blocks = line[1] n_blocks = inner_blocks.length for j in 0..n_blocks-1 entry = inner_blocks[j] condition = entry[0] inner_block = entry[1] fail "assert" unless condition[0] == TYPE_CONDITION if condition.nil? or \ evaluate_condition(namespace, condition) local_namespace = AyaNamespace.new(@dic.aya, :parent => namespace) result_of_inner_block = evaluate(local_namespace, inner_block, -1, 1) unless result_of_inner_block.nil? or [0, false].include?(result_of_inner_block) or result_of_inner_block.empty? alternatives << result_of_inner_block end break end end elsif line[0] == TYPE_WHILE condition = line[1][0] inner_block = line[1][1] fail "assert" unless condition[0] == TYPE_CONDITION while evaluate_condition(namespace, condition) local_namespace = AyaNamespace.new(@dic.aya, :parent => namespace) result_of_inner_block = evaluate(local_namespace, inner_block, -1, 1) unless result_of_inner_block.nil? alternatives << result_of_inner_block end if @status == CODE_RETURN break end if @status == CODE_BREAK @status = CODE_NONE break end if @status == CODE_CONTINUE @status = CODE_NONE end end elsif line[0] == TYPE_FOR init = line[1][0][0] condition = line[1][0][1] reset = line[1][0][2] inner_block = line[1][1] evaluate(namespace, init, -1, 1) fail "assert" unless condition[0] == TYPE_CONDITION while evaluate_condition(namespace, condition) local_namespace = AyaNamespace.new(@dic.aya, :parent => namespace) result_of_inner_block = evaluate(local_namespace, inner_block, -1, 1) unless result_of_inner_block.nil? alternatives << result_of_inner_block end break if @status == CODE_RETURN if @status == CODE_BREAK @status = CODE_NONE break end if @status == CODE_CONTINUE @status = CODE_NONE end evaluate(namespace, reset, -1, 1) end elsif line[0] == TYPE_SWITCH index = evaluate_token(namespace, line[1][0]) inner_block = line[1][1] begin index = Integer(index) rescue index = 0 end local_namespace = AyaNamespace.new(@dic.aya, :parent => namespace) result_of_inner_block = evaluate(local_namespace, inner_block, index, 1) unless result_of_inner_block.nil? or [0, false].include?(result_of_inner_block) or result_of_inner_block.empty? alternatives << result_of_inner_block end elsif line[0] == TYPE_CASE left = evaluate_token(namespace, line[1][0]) inner_blocks = line[1][1] n_blocks = inner_blocks.length default_result = nil break_flag = false for j in 0..n_blocks-1 entry = inner_blocks[j] inner_block = entry[1] local_namespace = AyaNamespace.new(@dic.aya, :parent => namespace) unless entry[0].nil? value_min, value_max = entry[0] value_min = evaluate_statement(namespace, value_min, 1) value_max = evaluate_statement(namespace, value_max, 1) if value_min <= left and left <= value_max result_of_inner_block = evaluate( local_namespace, inner_block, -1, 1) unless result_of_inner_block.nil? or [0, false].include?(result_of_inner_block) or result_of_inner_block.empty? alternatives << result_of_inner_block break_flag = true break end end else default_result = evaluate(local_namespace, inner_block, -1, 1) end end unless break_flag unless default_result.nil? or default_result.empty? alternatives << default_result end end elsif line[0] == TYPE_STATEMENT result_of_func = evaluate_statement(namespace, line, 0) unless result_of_func.nil? or [0, false].include?(result_of_func) or result_of_func.empty? alternatives << result_of_func end else result_of_eval = evaluate_token(namespace, line) unless result_of_eval.nil? or [0, false].include?(result_of_eval) or result_of_eval.empty? alternatives << result_of_eval end end end if is_inner_block.zero? unless @sequential.nil? list_ = [] for alt in result list_ << alt.length end if @sequential[0] != list_ @sequential[0] = list_ @sequential[1] = ([0] * result.length) else for index in 0..result.length-1 current = @sequential[1][index] if current < result[index].length - 1 @sequential[1][index] = (current + 1) break else @sequential[1][index] = 0 end end end end unless @nonoverlap.nil? list_ = [] for alt in result list_ << alt.length end if @nonoverlap[0] != list_ @nonoverlap[0] = list_ @nonoverlap[2] = [] end if @nonoverlap[2].empty? @nonoverlap[2] << ([0] * result.length) while true new = [] new.concat(@nonoverlap[2][-1]) break_flag = false for index in 0..result.length-1 if new[index] < result[index].length - 1 new[index] += 1 @nonoverlap[2] << new break_flag = true break else new[index] = 0 end end break unless break_flag end end next_ = Random.rand(0..@nonoverlap[2].length-1) @nonoverlap[1] = @nonoverlap[2][next_] @nonoverlap[2].delete(next_) end for index in 0..result.length-1 if not @sequential.nil? result[index] = result[index][@sequential[1][index]] elsif not @nonoverlap.nil? result[index] = result[index][@nonoverlap[1][index]] else result[index] = result[index].sample end end end return nil if result.nil? or result.empty? return result[0] if result.length == 1 return result.map {|s| s.to_s}.join('') end def substitute(namespace, left, ope, right) var_name = left[1][0] if var_name.start_with?('_') target_namespace = namespace else target_namespace = @dic.aya.get_global_namespace() end if left[0] != TYPE_ARRAY target_namespace.put(var_name, right) else index = evaluate_token(namespace, left[1][1]) begin index = Integer(index) rescue Logging::Logging.debug('Could not convert ' + index.to_s + ' to an integer') else case ope when '=' elem = right when ':=' if right.is_a?(Integer) elem = right.to_f else elem = right end else return nil # should not reach here end target_namespace.put(var_name, elem, :index => index) end end end def evaluate_token(namespace, token) result = '' # default case token[0] when TYPE_TOKEN if not Re_b.match(token[1]).nil? pos = Re_d_.match(token[1]).start(0) result = token[1][pos..-1].to_i(2) elsif not Re_x.match(token[1]).nil? result = token[1].to_i(16) else func = @dic.get_function(token[1]) system_functions = @dic.aya.get_system_functions() if not func.nil? result = func.call() elsif system_functions.exists(token[1]) result = system_functions.call(namespace, token[1], []) elsif token[1].start_with?('random') # ver.3 result = Random.rand(0..99) else if token[1].start_with?('_') target_namespace = namespace else target_namespace = @dic.aya.get_global_namespace() end if target_namespace.exists(token[1]) result = target_namespace.get(token[1]) end end end when TYPE_STRING_LITERAL result = token[1] when TYPE_STRING result = evaluate_string(namespace, token[1]) when TYPE_INT result = token[1].to_i when TYPE_FLOAT result = token[1].to_f when TYPE_SYSTEM_FUNCTION system_functions = @dic.aya.get_system_functions() func_name = token[1][0] ##fail ["assert: ", 'function ', func_name, ' not found.'].join('') unless system_functions.exists(func_name) arguments = evaluate_argument(namespace, func_name, token[1][1], true) if func_name == 'CALLBYNAME' func = @dic.get_function(arguments[0]) system_functions = @dic.aya.get_system_functions() if not func.nil? result = func.call() elsif system_functions.exists(arguments[0]) result = system_functions.call(namespace, arguments[0], []) end elsif func_name == 'LOGGING' arguments.insert(0, token[1][2]) arguments.insert(0, @name) arguments.insert(0, @dic.aya.logfile) result = system_functions.call(namespace, func_name, arguments) else result = system_functions.call(namespace, func_name, arguments) end when TYPE_FUNCTION func_name = token[1][0] func = @dic.get_function(func_name) ##fail ["assert: ", 'function ', func_name, ' not found.'].join('') unless func != nil arguments = evaluate_argument(namespace, func_name, token[1][1], false) result = func.call(:argv => arguments) when TYPE_ARRAY var_name = token[1][0] if var_name.start_with?('_') target_namespace = namespace else target_namespace = @dic.aya.get_global_namespace() end index = evaluate_token(namespace, token[1][1]) begin index = Integer(index) rescue Logging::Logging.debug( 'index of array has to be integer: ' + var_name.to_s + '[' + token[1][1].to_s + ']') else if var_name == 'random' # Ver.3 result = Random.rand(0..index-1) elsif var_name == 'ascii' # Ver.3 if 0 <= index and index < 0x80 ## FIXME result = index.chr else result = ' ' end elsif target_namespace.exists(var_name) result = target_namespace.get(var_name, :index => index) end end when TYPE_VARIABLE var_name = token[1][0] if var_name.start_with?('_') target_namespace = namespace else target_namespace = @dic.aya.get_global_namespace() end if target_namespace.exists(var_name) result = target_namespace.get(var_name) end when TYPE_ARRAY_POINTER var_name = token[1][0] if var_name.start_with?('_') target_namespace = namespace else target_namespace = @dic.aya.get_global_namespace() end index = evaluate_token(namespace, token[1][1]) begin index = Integer(index) rescue Logging::Logging.debug( 'index of array has to be integer: ' + var_name.to_s + '[' + token[1][1].to_s + ']') else if var_name == 'random' # Ver.3 result = Random.rand(0..index-1) elsif var_name == 'ascii' # Ver.3 if 0 <= index and index < 0x80 ## FIXME result = index.chr else result = ' ' end else value = target_namespace.get(var_name, :index => index) result = {'name' => var_name, 'index' => index, 'namespace' => target_namespace, 'value' => value} end end when TYPE_VARIABLE_POINTER var_name = token[1][0] if var_name.start_with?('_') target_namespace = namespace else target_namespace = @dic.aya.get_global_namespace() end value = target_namespace.get(var_name) result = {'name' => var_name, 'index' => nil, 'namespace' => target_namespace, 'value' => value} else Logging::Logging.debug('error in evaluate_token: ' + token.to_s) end return result end def evaluate_condition(namespace, condition) result = false return true if condition[1].nil? left = condition[1][0] ope = condition[1][1] right = condition[1][2] fail "assert" unless ope[0] == TYPE_OPERATOR case left[0] when TYPE_CONDITION left_result = evaluate_condition(namespace, left) when TYPE_STATEMENT left_result = evaluate_statement(namespace, left, 1) else left_result = evaluate_token(namespace, left) end case right[0] when TYPE_CONDITION right_result = evaluate_condition(namespace, right) when TYPE_STATEMENT right_result = evaluate_statement(namespace, right, 1) else right_result = evaluate_token(namespace, right) end case ope[1] when '==' result = (left_result == right_result) when '!=' result = (left_result != right_result) when '_in_' if right_result.is_a?(String) and left_result.is_a?(String) if right_result.include?(left_result) result = true else result = false end else result = false end when '!_in_' if right_result.is_a?(String) and left_result.is_a?(String) unless right_result.include?(left_result) result = true else result = false end else result = false end when '<' if right_result.is_a?(String) != left_result.is_a?(String) left_result = left_result.to_f right_result = right_result.to_f end result = (left_result < right_result) when '>' if right_result.is_a?(String) != left_result.is_a?(String) left_result = left_result.to_f right_result = right_result.to_f end result = (left_result > right_result) when '<=' if right_result.is_a?(String) != left_result.is_a?(String) left_result = left_result.to_f right_result = right_result.to_f end result = (left_result <= right_result) when '>=' if right_result.is_a?(String) != left_result.is_a?(String) left_result = left_result.to_f right_result = right_result.to_f end result = (left_result >= right_result) when '||' result = (left_result or right_result) when '&&' result = (left_result and right_result) else #pass end return result end def evaluate_statement(namespace, statement, type_float) num = statement[1..-1].length return '' if num.zero? type_ = statement[0] token = statement[1] if type_ == TYPE_STATEMENT left = evaluate_statement(namespace, token, type_float) else left = evaluate_token(namespace, statement) end ##else ## Logging::Logging.debug('illegal statement: ' + statement[1].join(' ')) ## return '' ##end if num == 3 ##fail "assert" unless statement[2][0] == TYPE_OPERATOR ope = statement[2][1] type_ = statement[3][0] case type_ when TYPE_INT token = statement[3][1] unless type_float.zero? right = token.to_f else right = token.to_i end when TYPE_FLOAT token = statement[3][1] unless type_float.zero? right = token.to_f else right = token.to_f.to_i end when TYPE_STATEMENT right = evaluate_statement(namespace, statement[3], type_float) else right = evaluate_token(namespace, statement[3]) end result = operation(left, ope, right, type_float) else result = left end return result end def operation(left, ope, right, type_float) begin if not type_float.zero? left = Float(left) right = Float(right) elsif ope != '+' or \ (not left.is_a?(String) and not right.is_a?(String)) left = Integer(left) right = Integer(right) else left = left.to_s right = right.to_s end rescue left = left.to_s right = right.to_s end begin case ope when '+' return left + right when '-' return left - right when '*' return left * right when '/' if right.zero? return 0 else if left.is_a?(Integer) and right.is_a?(Integer) return (left / right).to_i else return left / right end end when '%' return left % right end rescue Logging::Logging.debug( 'illegal operation: ' + [left.to_s, ope.to_s, right.to_s].join(' ')) return '' end end def get_block(parent, startpoint) result = [] n_lines = parent.length inner_nest_level = 0 for i in startpoint..n_lines-1 inner_content = parent[i] if inner_content == '{' if inner_nest_level > 0 result << inner_content end inner_nest_level += 1 elsif inner_content == '}' inner_nest_level -= 1 if inner_nest_level > 0 result << inner_content end else result << inner_content end if inner_nest_level.zero? return i, result end end return startpoint, result end def evaluate_string(namespace, line) history = [] # %[n] buf = '' startpoint = 0 system_functions = @dic.aya.get_system_functions() while startpoint < line.length pos = line.index('%', startpoint) if pos.nil? or pos < 0 buf = [buf, line[startpoint..-1]].join('') startpoint = line.length next else unless pos.zero? buf = [buf, line[startpoint..pos-1]].join('') end startpoint = pos end endpoint = line.length for char in SPECIAL_CHARS pos = line[0..endpoint-1].index(char, startpoint + 2) if not pos.nil? and 0 < pos and pos < endpoint endpoint = pos end end if line[startpoint + 1] == '[' # history if line[endpoint] != ']' Logging::Logging.debug( 'unbalanced "%[" or illegal index in ' \ 'the string(' + line + ')') buf = '' break end index_token = parse_token(line[startpoint + 2..endpoint-1]) index = evaluate_token(namespace, index_token) begin index = Integer(index) rescue Logging::Logging.debug( 'illegal history index in the string(' + line + ')') else if 0 <= index and index < history.length buf = [buf, format(history[index])].join('') end end startpoint = (endpoint + 1) next end replaced = false while endpoint > (startpoint + 1) token = line[startpoint + 1..endpoint-1] if token == 'random' or token == 'ascii' # Ver.3 if endpoint < line.length and \ line[endpoint] == '[' end_of_block = line.index(']', endpoint + 1) if end_of_block.nil? or end_of_block < 0 Logging::Logging.debug( 'unbalanced "[" or illegal index in ' \ 'the string(' + line + ')') startpoint = line.length buf = '' break end index = parse_token(line[endpoint + 1..end_of_block-1]) content_of_var = evaluate_token( namespace, [TYPE_ARRAY, [token, index]]) if content_of_var.nil? content_of_var = '' end history << content_of_var buf = [buf, format(content_of_var)].join('') startpoint = (end_of_block + 1) replaced = true break end end func = @dic.get_function(token) is_system_func = system_functions.exists(token) if not func.nil? or is_system_func if endpoint < line.length and \ line[endpoint] == '(' end_of_parenthesis = line.index(')', endpoint + 1) if end_of_parenthesis.nil? or end_of_parenthesis < 0 Logging::Logging.debug( 'unbalanced "(" in the string(' + line + ')') startpoint = line.length buf = '' break end func_name = token arguments = parse_argument( line[endpoint + 1..end_of_parenthesis-1]) arguments = evaluate_argument( namespace, func_name, arguments, is_system_func) if is_system_func if func_name == 'CALLBYNAME' func = @dic.get_function(arguments[0]) if not func.nil? result_of_func = func.call() elsif system_functions.exists(arguments[0]) result_of_func = system_functions.call( namespace, arguments[0], []) end elsif func_name == 'LOGGING' arguments.insert( 0, line[endpoint + 1..end_of_parenthesis-1]) arguments.insert(0, @name) arguments.insert(0, @dic.aya.logfile) result_of_func = system_functions.call( namespace, func_name, arguments) else result_of_func = system_functions.call( namespace, func_name, arguments) end else result_of_func = func.call(:argv => arguments) end if result_of_func.nil? result_of_func = '' end history << result_of_func buf = [buf, format(result_of_func)].join('') startpoint = (end_of_parenthesis + 1) replaced = true break elsif not func.nil? result_of_func = func.call() history << result_of_func buf = [buf, format(result_of_func)].join('') startpoint = endpoint replaced = true break else result_of_func = system_functions.call( namespace, token, []) if result_of_func.nil? result_of_func = '' end history << result_of_func buf = [buf, format(result_of_func)].join('') startpoint = endpoint replaced = true break end else if token.start_with?('_') target_namespace = namespace else target_namespace = @dic.aya.get_global_namespace() end if target_namespace.exists(token) have_index = false index = nil if endpoint < line.length and line[endpoint] == '[' end_of_block = line.index(']', endpoint + 1) if end_of_block.nil? or end_of_block < 0 Logging::Logging.debug( 'unbalanced "[" or ' \ 'illegal index in the string(' + line + ')') startpoint = line.length buf = '' break end have_index = true index_token = parse_token( line[endpoint + 1..end_of_block-1]) index = evaluate_token(namespace, index_token) begin index = Integer(index) rescue have_index = false index = nil end end value = target_namespace.get(token, :index => index) unless value.nil? content_of_var = value history << content_of_var buf = [buf, format(content_of_var)].join('') if have_index startpoint = (end_of_block + 1) else startpoint = endpoint end replaced = true break end end end endpoint -= 1 end unless replaced buf = [buf, line[startpoint]].join('') startpoint += 1 end end return buf end def format(input_num) if input_num.is_a?(Float) result = input_num.round(6).to_s else result = input_num.to_s end return result end def evaluate_argument(namespace, name, argument, is_system_func) arguments = [] for i in 0..argument.length-1 if is_system_func and \ @dic.aya.get_system_functions().not_to_evaluate(name, i) ##fail "assert" unless [].include?(argument[i]) ## FIXME arguments << argument[i][1][1] else arguments << evaluate_statement(namespace, argument[i], 1) end end if is_system_func if name == 'NAMETOVALUE' and \ arguments.length == 1 # this is kluge arguments[0] = evaluate_statement(namespace, argument[0], 1) end end return arguments end def is_substitution(line) statement = AyaStatement.new(line) if statement.countTokens() >= 3 statement.next_token() # left ope = statement.next_token() ope_list = ['=', ':=', '+=', '-=', '*=', '/=', '%=', '+:=', '-:=', '*:=', '/:=', '%:='] if ope_list.include?(ope) return true end end return false end def is_inc_or_dec(line) if line.length <= 2 return false end if line.end_with?('++') or line.end_with?('--') return true else return false end end end class AyaSystemFunctions def initialize(aya) @aya = aya @saori_statuscode = '' @saori_header = [] @saori_value = {} @saori_protocol = '' @errno = 0 @security = AyaSecurity.new(@aya) @functions = { 'ARRAYSIZE' => ['ARRAYSIZE', [0, 1], [1, 2], nil], 'ASC' => ['ASC', [nil], [1], nil], 'BINSTRTONUM' => ['BINSTRTONUM', [nil], [1], nil], 'CALLBYNAME' => ['CALLBYNAME', [nil], [1], nil], 'CEIL' => ['CEIL', [nil], [1], nil], 'COS' => ['COS', [nil], [1], nil], 'CUTSPACE' => ['CUTSPACE', [nil], [1], nil], 'ERASE' => ['ERASE', [nil], [3], nil], 'ERASEVARIABLE' => ['ERASEVARIABLE', [nil], [1], nil], 'FCLOSE' => ['FCLOSE', [nil], [1], nil], 'FCOPY' => ['FCOPY', [nil], [2], 259], 'FDELETE' => ['FDELETE', [nil], [1], 269], 'FENUM' => ['FENUM', [nil], [1, 2], 290], 'FLOOR' => ['FLOOR', [nil], [1], nil], 'FMOVE' => ['FMOVE', [nil], [2], 264], 'FOPEN' => ['FOPEN', [nil], [2], 256], 'FREAD' => ['FREAD', [nil], [1], nil], 'FRENAME' => ['FRENAME', [nil], [2], 273], 'FSIZE' => ['FSIZE', [nil], [1], 278], ##'FUNCTIONEX' => ['FUNCTIONEX', [nil], [nil], nil], # FIXME # Ver.3 'FWRITE' => ['FWRITE', [nil], [2], nil], 'FWRITE2' => ['FWRITE2', [nil], [2], nil], 'GETLASTERROR' => ['GETLASTERROR', [nil], [nil], nil], 'HEXSTRTONUM' => ['HEXSTRTONUM', [nil], [1], nil], 'IASC' => ['IASC', [nil], [1], nil], 'INSERT' => ['INSERT', [nil], [3], nil], 'ISFUNCTION' => ['ISFUNCTION', [nil], [1], nil], 'ISINSIDE' => ['ISINSIDE', [nil], [3], nil], 'ISINTEGER' => ['ISINTEGER', [nil], [1], nil], 'ISREAL' => ['ISREAL', [nil], [1], nil], 'LETTONAME' => ['LETTONAME', [nil], [2], nil], 'LIB.HEADER' => ['LIB_HEADER', [nil], [1], nil], 'LIB.KEY' => ['LIB_HEADER', [nil], [1], nil], # alias 'LIB.PROTOCOL' => ['LIB_PROTOCOL', [nil], [0], nil], 'LIB.STATUSCODE' => ['LIB_STATUSCODE', [nil], [0], nil], 'LIB.VALUE' => ['LIB_VALUE', [nil], [1], nil], 'LOADLIB' => ['LOADLIB', [nil], [1], 16], 'LOG' => ['LOG', [nil], [1], nil], 'LOG10' => ['LOG10', [nil], [1], nil], 'LOGGING' => ['LOGGING', [nil], [4], nil], 'MERASE' => ['MERASE', [nil], [3], nil], 'MINSERT' => ['MINSERT', [nil], [3], nil], 'MKDIR' => ['MKDIR', [nil], [1], 282], 'MSTRLEN' => ['MSTRLEN', [nil], [1], nil], 'MSTRSTR' => ['MSTRSTR', [nil], [3], nil], 'MSUBSTR' => ['MSUBSTR', [nil], [3], nil], 'NAMETOVALUE' => ['NAMETOVALUE', [0], [1, 2], nil], 'POW' => ['POW', [nil], [2], nil], 'RAND' => ['RAND', [nil], [0, 1], nil], 'REPLACE' => ['REPLACE', [nil], [3], nil], 'REQ.COMMAND' => ['REQ_COMMAND', [nil], [0], nil], 'REQ.HEADER' => ['REQ_HEADER', [nil], [1], nil], 'REQ.KEY' => ['REQ_HEADER', [nil], [1], nil], # alias 'REQ.PROTOCOL' => ['REQ_PROTOCOL', [nil], [0], nil], 'REQ.VALUE' => ['REQ_VALUE', [nil], [1], nil], 'REQUESTLIB' => ['REQUESTLIB', [nil], [2], nil], 'RMDIR' => ['RMDIR', [nil], [1], 286], 'ROUND' => ['ROUND', [nil], [1], nil], ##'SAORI' => ['SAORI', [nil], [nil], nil], # FIXME # Ver.3 'SETSEPARATOR' => ['SETSEPARATOR', [0], [2], nil], 'SIN' => ['SIN', [nil], [1], nil], 'SQRT' => ['SQRT', [nil], [1], nil], 'STRLEN' => ['STRLEN', [1], [1, 2], nil], 'STRSTR' => ['STRSTR', [3], [3, 4], nil], 'SUBSTR' => ['SUBSTR', [nil], [3], nil], 'TAN' => ['TAN', [nil], [1], nil], 'TOBINSTR' => ['TOBINSTR', [nil], [1], nil], 'TOHEXSTR' => ['TOHEXSTR', [nil], [1], nil], 'TOLOWER' => ['TOLOWER', [nil], [1], nil], 'TONUMBER' => ['TONUMBER', [0], [1], nil], 'TONUMBER2' => ['TONUMBER2', [nil], [1], nil], 'TOSTRING' => ['TOSTRING', [0], [1], nil], 'TOSTRING2' => ['TOSTRING2', [nil], [1], nil], 'TOUPPER' => ['TOUPPER', [nil], [1], nil], 'UNLOADLIB' => ['UNLOADLIB', [nil], [1], nil] } end def exists(name) @functions.include?(name) end def call(namespace, name, argv) @errno = 0 if @functions.include?(name) and \ check_num_args(name, argv) return method(@functions[name][0]).call(namespace, argv) else return '' end end def not_to_evaluate(name, index) if @functions[name][1].include?(index) return true else return false end end def check_num_args(name, argv) list_num = @functions[name][2] return true if list_num == [nil] return true if list_num.include?(argv.length) list_num.sort! if argv.length < list_num[0] errno = @functions[name][3] @errno = errno unless errno.nil? Logging::Logging.debug( [name.to_s, ': called with too few argument(s)'].join('')) return false end return true end def ARRAYSIZE(namespace, argv) if argv[0].is_a?(String) line = argv[0] if line.nil? or line == '' value = 0 elsif line.start_with?('"') and line.end_with?('"') value = (line.count(',') + 1) else target_namespace = select_namespace(namespace, line) value = target_namespace.get_size(line) end else value = 0 end if argv.length == 2 var = argv[1].to_s target_namespace = select_namespace(namespace, var) target_namespace.put(var, value) return nil else return value end end def ASC(namespace, argv) begin Integer(argv[0]) rescue return '' end index = argv[0].to_i if 0 <= index and index < 0x80 return index.chr else return ' ' end end def BINSTRTONUM(namespace, argv) begin return Integer(argv[0].to_s, 2) rescue return -1 end end def CALLBYNAME(namespace, argv) # dummy nil end def CEIL(namespace, argv) begin return math.ceil(Float(argv[0])).to_i rescue return -1 end end def COS(namespace, argv) begin result = math.cos(Float(argv[0])) rescue return -1 end return select_math_type(result) end def CUTSPACE(namespace, argv) return argv[0].to_s.strip() end def ERASE(namespace, argv) line = argv[0].to_s.encode('CP932', :invalid => :replace, :undef => :replace) # XXX begin start = Integer(argv[1]) bytes = Integer(argv[2]) rescue return '' end return [line[0..start-1], line[start + bytes..-1]].join('').encode("UTF-8", :invalid => :replace, :undef => :replace) # XXX end def ERASEVARIABLE(namespace, argv) var = argv[0].to_s target_namespace = select_namespace(namespace, var) target_namespace.remove(var) end def FCLOSE(namespace, argv) filename = Home.get_normalized_path(argv[0].to_s) path = File.join(@aya.aya_dir, filename) norm_path = File.expand_path(path) if @aya.filelist.include?(norm_path) @aya.filelist[norm_path].close() @aya.filelist.delete(norm_path) end return nil end def FCOPY(namespace, argv) src = Home.get_normalized_path(argv[0].to_s) head, tail = File.split(src) dst = [Home.get_normalized_path(argv[1].to_s), '/', tail].join('') src_path = File.join(@aya.aya_dir, src) dst_path = File.join(@aya.aya_dir, dst) result = 0 if not File.file?(src_path) @errno = 260 elsif not File.directory?(dst_path) @errno = 261 elsif @security.check_path(src_path, :flag => 'r') == 1 and \ @security.check_path(dst_path) == 1 begin shutil.copyfile(src_path, dst_path) rescue @errno = 262 else result = 1 end else @errno = 263 end return result end def FDELETE(namespace, argv) filename = Home.get_normalized_path(argv[0].to_s) path = File.join(@aya.aya_dir, filename) result = 0 if not File.file?(path) @errno = 270 elsif @security.check_path(path) == 1 begin File.delete(path) rescue @errno = 271 else result = 1 end else @errno = 272 end return result end def FENUM(namespace, argv) if argv.length >= 2 separator = argv[1].to_s else separator = ',' end dirname = Home.get_normalized_path(argv[0].to_s) path = File.join(@aya.aya_dir, dirname) filelist = [] if @security.check_path(path, :flag => 'r') == 1 begin filelist = Dir.entries(path).reject{|entry| entry =~ /^\.{1,2}$/} rescue @errno = 291 end else @errno = 292 end result = '' for index in 0..filelist.length-1 path = File.join(@aya.aya_dir, dirname, filelist[index]) if File.directory?(path) result = [result, "\\"].join('') end result = [result, filelist[index]].join('') if index != (filelist.length - 1) result = [result, separator].join('') end end return result end def FLOOR(namespace, argv) begin return math.floor(Float(argv[0])).to_i rescue return -1 end end def FMOVE(namespace, argv) src = Home.get_normalized_path(argv[0].to_s) head, tail = File.split(src) dst = [Home.get_normalized_path(argv[1].to_s), '/', tail].join('') src_path = File.join(@aya.aya_dir, src) dst_path = File.join(@aya.aya_dir, dst) result = 0 head, tail = File.split(dst_path) if not File.file?(src_path) @errno = 265 elsif not File.directory?(head) @errno = 266 elsif @security.check_path(src_path) == 1 and \ @security.check_path(dst_path) == 1 begin File.rename(src_path, dst_path) rescue @errno = 267 else result = 1 end else @errno = 268 end return result end def FOPEN(namespace, argv) filename = Home.get_normalized_path(argv[0].to_s) accessmode = argv[1].to_s result = 0 path = File.join(@aya.aya_dir, filename) if @security.check_path(path, :flag => accessmode[0]) == 1 norm_path = File.expand_path(path) if @aya.filelist.include?(norm_path) result = 2 else begin @aya.filelist[norm_path] = open(path, accessmode[0], encoding='Shift_JIS') rescue @errno = 257 else result = 1 end end else @errno = 258 end return result end def FREAD(namespace, argv) filename = Home.get_normalized_path(argv[0].to_s) path = File.join(@aya.aya_dir, filename) norm_path = File.expand_path(path) result = -1 if @aya.filelist.include?(norm_path) f = @aya.filelist[norm_path] result = f.readline() if result.nil? result = -1 elsif result.end_with?("\r\n") result = result[0..-3] elsif result.end_with?("\n") result = result[0..-2] end end return result end def FRENAME(namespace, argv) src = Home.get_normalized_path(argv[0].to_s) dst = Home.get_normalized_path(argv[1].to_s) src_path = File.join(@aya.aya_dir, src) dst_path = File.join(@aya.aya_dir, dst) result = 0 head, tail = File.split(dst_path) if not File.exist?(src_path) @errno = 274 elsif not File.directory?(head) @errno = 275 elsif @security.check_path(dst_path) == 1 begin File.rename(src_path, dst_path) rescue @errno = 276 else result = 1 end else @errno = 277 end return result end def FSIZE(namespace, argv) filename = Home.get_normalized_path(argv[0].to_s) path = File.join(@aya.aya_dir, filename) size = -1 if not File.exist?(path) @errno = 279 elsif @security.check_path(path, :flag => 'r') == 1 begin size = File.size(path) rescue @errno = 280 end else @errno = 281 end return size end def FUNCTIONEX(namespace, argv) # FIXME # Ver.3 return nil end def FWRITE(namespace, argv) filename = Home.get_normalized_path(argv[0].to_s) path = File.join(@aya.aya_dir, filename) norm_path = File.expand_path(path) data = [argv[1].to_s, "\n"].join('') if @aya.filelist.include?(norm_path) f = @aya.filelist[norm_path] f.write(data) end return nil end def FWRITE2(namespace, argv) filename = Home.get_normalized_path(argv[0].to_s) path = File.join(@aya.aya_dir, filename) norm_path = File.expand_path(path) data = argv[1].to_s if @aya.filelist.include?(norm_path) f = @aya.filelist[norm_path] f.write(data) end return nil end def GETLASTERROR(namespace, argv) @errno end def HEXSTRTONUM(namespace, argv) begin return Integer(argv[0].to_s, 16) rescue return -1 end end def IASC(namespace, argv) return -1 unless argv[0].is_a?(String) begin code = argv[0][0].ord rescue return -1 end return code end def INSERT(namespace, argv) line = argv[0].to_s.encode('CP932', :invalid => :replace, :undef => :replace) # XXX begin start = Integer(argv[1]) rescue return '' end to_insert = argv[2].to_s.encode('CP932', :invalid => :replace, :undef => :replace) # XXX if start < 0 start = 0 end return [line[0..start-1], to_insert, line[start..-1]].join('').encode("UTF-8", :invalid => :replace, :undef => :replace) # XXX end def ISFUNCTION(namespace, argv) if not argv[0].is_a?(String) return 0 elsif not @aya.dic.get_function(argv[0]).nil? return 1 elsif @aya.get_system_functions().exists(argv[0]) return 2 else return 0 end end def ISINSIDE(namespace, argv) if argv[1] <= argv[0] and argv[0] <= argv[2] return 1 else return 0 end end def ISINTEGER(namespace, argv) if argv[0].is_a?(Integer) return 1 else return 0 end end def ISREAL(namespace, argv) if argv[0].is_a?(Integer) or argv[0].is_a?(Float) return 1 else return 0 end end def LETTONAME(namespace, argv) var = argv[0].to_s value = argv[1] if var.nil? or var.empty? return nil end target_namespace = select_namespace(namespace, var) target_namespace.put(var, value) return nil end def LIB_HEADER(namespace, argv) begin Integer(argv[0]) rescue return '' end result = '' header_list = @saori_header if not header_list.nil? and argv[0].to_i < header_list.length result = header_list[argv[0].to_i] end return result end def LIB_PROTOCOL(namespace, argv) @aya.saori_protocol end def LIB_STATUSCODE(namespace, argv) @saori_statuscode end def LIB_VALUE(namespace, argv) result = '' if argv[0].is_a?(Integer) header_list = @saori_header if not header_list.nil? and argv[0].to_i < header_list.length key = header_list[argv[0].to_i] end else key = argv[0].to_s end if @saori_value.include?(key) result = @saori_value[key] end return result end def LOADLIB(namespace, argv) dll = argv[0].to_s result = 0 unless dll.empty? if @security.check_lib(dll) == 1 result = @aya.saori_library.load(dll, @aya.aya_dir) if result.zero? @errno = 17 end else @errno = 18 end end return result end def LOG(namespace, argv) begin Float(argv[0]) rescue return -1 end if argv[0].to_f.zero? return 0 end result = math.log(argv[0].to_f) return select_math_type(result) end def LOG10(namespace, argv) begin Float(argv[0]) rescue return -1 end if argv[0].to_f.zero? return 0 end result = math.log10(argv[0].to_f) return select_math_type(result) end def LOGGING(namespace, argv) return nil if argv[0].nil? logfile = argv[0] line = ['> function ', argv[1].to_s, ' : ', argv[2].to_s].join('') unless argv[3].nil? line = [line, ' = '].join('') if argv[3].is_a?(Integer) or argv[3].is_a?(Float) line = [line, argv[3].to_s].join('') else line = [line, '"', argv[3].to_s, '"'].join('') end end line = [line, "\n"].join('') logfile.write(line) logfile.write("\n") return nil end def MERASE(namespace, argv) line = argv[0].to_s begin start = Integer(argv[1]) end_ = Integer(argv[2]) rescue return '' end return [line[0..start-1], line[end_..-1]].join('') end def MINSERT(namespace, argv) line = argv[0].to_s begin start = Integer(argv[1]) rescue return '' end to_insert = argv[2].to_s return [line[0..start-1], to_insert, line[start..-1]].join('') end def MKDIR(namespace, argv) dirname = Home.get_normalized_path(argv[0].to_s) path = File.join(@aya.aya_dir, dirname) result = 0 head, tail = File.split(path) if not File.directory?(head) @errno = 283 elsif @security.check_path(path) == 1 begin Dir.mkdir(path, 0o755) rescue @errno = 284 else result = 1 end else @errno = 285 end return result end def MSTRLEN(namespace, argv) argv[0].to_s.length end def MSTRSTR(namespace, argv) line = argv[0].to_s to_find = argv[1].to_s begin start = Integer(argv[2]) rescue return -1 end return line.rfind(to_find, start) end def MSUBSTR(namespace, argv) line = argv[0].to_s begin start = Integer(argv[1]) end_ = Integer(argv[2]) rescue return '' end return line[start..end_-1] end def NAMETOVALUE(namespace, argv) if argv.length == 2 var = argv[0].to_s name = argv[1].to_s else name = argv[0].to_s end target_namespace = select_namespace(namespace, name) value = target_namespace.get(name) if argv.length == 2 target_namespace = select_namespace(namespace, var) target_namespace.put(var, value) return nil else return value end end def POW(namespace, argv) begin result = math.pow(Float(argv[0]), Float(argv[1])) rescue return -1 end return select_math_type(result) end def RAND(namespace, argv) unless argv return Random.rand(0..99) else begin Integer(argv[0]) rescue return -1 end return Random.rand(0..argv[0].to_i-1) end end def REPLACE(namespace, argv) line = argv[0].to_s old = argv[1].to_s new = argv[2].to_s return line.gsub(old, new) end def REQ_COMMAND(namespace, argv) return @aya.req_command end def REQ_HEADER(namespace, argv) begin Integer(argv[0]) rescue return '' end if @aya.req_key.length > argv[0].to_i return @aya.req_key[argv[0].to_i] else return '' end end def REQ_PROTOCOL(namespace, argv) @aya.req_protocol end def REQ_VALUE(namespace, argv) if argv[0].is_a?(Integer) name = REQ_HEADER(namespace, [argv[0]]) else name = argv[0].to_s end if @aya.req_header.include?(name) return @aya.req_header[name] else return '' end end def REQUESTLIB(namespace, argv) response = @aya.saori_library.request( argv[0].to_s, argv[1].to_s.encode('Shift_JIS', :invalid => :replace, :undef => :replace)) ## FIXME header = response.encode("UTF-8", :invalid => :replace, :undef => :replace).split(/\r?\n/, 0) @saori_statuscode = '' @saori_header = [] @saori_value = {} @saori_protocol = '' unless header.nil? or header.empty? line = header.shift line = line.strip() if line.include?(' ') @saori_protocol, @saori_statuscode = line.split(' ', 2) @saori_protocol.strip! @saori_statuscode.strip! end for line in header next unless line.include?(':') key, value = line.split(':', 2) key.strip! value.strip! unless key.empty? @saori_header << key @saori_value[key] = value end end end return nil end def RMDIR(namespace, argv) dirname = Home.get_normalized_path(argv[0].to_s) path = File.join(@aya.aya_dir, dirname) result = 0 if not File.directory?(path) @errno = 287 elsif @security.check_path(path) == 1 begin Dir.rmdir(path) rescue @errno = 288 else result = 1 end else @errno = 289 end return result end def ROUND(namespace, argv) begin value = math.floor(Float(argv[0]) + 0.5) rescue return -1 end return value.to_i end def SAORI(namespace, argv) # FIXME # Ver.3 return nil end def SETSEPARATOR(namespace, argv) name = argv[0].to_s separator = argv[1].to_s target_namespace = select_namespace(namespace, name) target_namespace.set_separator(name, separator) return nil end def SIN(namespace, argv) begin result = math.sin(Float(argv[0])) rescue return -1 end return select_math_type(result) end def SQRT(namespace, argv) begin arg = Float(argv[0]) rescue return -1 end if arg < 0.0 return -1 else result = math.sqrt(arg) return select_math_type(result) end end def STRLEN(namespace, argv) line = argv[0].to_s.encode('CP932', :invalid => :replace, :undef => :replace) # XXX if argv.length == 2 var = argv[1].to_s target_namespace = select_namespace(namespace, var) target_namespace.put(var, line.length) return nil else return line.length end end def STRSTR(namespace, argv) line = argv[0].to_s.encode('CP932', :invalid => :replace, :undef => :replace) # XXX to_find = argv[1].to_s.encode('CP932', :invalid => :replace, :undef => :replace) # XXX begin start = Integer(argv[2]) rescue return -1 end result = line.index(to_find, start) if argv.length == 4 var = argv[3].to_s target_namespace = select_namespace(namespace, var) target_namespace.put(var, result) return nil else return result end end def SUBSTR(namespace, argv) line = argv[0].to_s.encode('CP932', :invalid => :replace, :undef => :replace) # XXX begin start = Integer(argv[1]) num = Integer(argv[2]) rescue return '' end return line[start..start + num-1].encode("UTF-8", :invalid => :replace, :undef => :replace) # XXX end def TAN(namespace, argv) begin result = math.tan(Float(argv[0])) rescue return -1 end return select_math_type(result) end def TOBINSTR(namespace, argv) begin i = Integer(argv[0]) rescue return '' end if i < 0 i = abs(i) numsin = '-' else numsin = '' end line = '' while not i.zero? mod = (i % 2) i = (i / 2).to_i line = [mod.to_s, line].join('') end line = [numsin, line].join('') return line end def TOHEXSTR(namespace, argv) begin return Integer(argv[0]).to_s(16) rescue return '' end end def TOLOWER(namespace, argv) argv[0].to_s.downcase end def TONUMBER(namespace, argv) var = argv[0].to_s target_namespace = select_namespace(namespace, var) token = target_namespace.get(var) begin if token.include?('.') result = Float(token) else result = Integer(token) end rescue result = 0 end target_namespace.put(var, result) return nil end def TONUMBER2(namespace, argv) token = argv[0].to_s begin if token.include?('.') value = Float(token) else value = Integer(token) end rescue return 0 else return value end end def TOSTRING(namespace, argv) name = argv[0].to_s target_namespace = select_namespace(namespace, name) value = target_namespace.get(name).to_s target_namespace.put(name, value) end def TOSTRING2(namespace, argv) argv[0].to_s end def TOUPPER(namespace, argv) argv[0].to_s.upcase end def UNLOADLIB(namespace, argv) unless argv[0].to_s.empty? @aya.saori_library.unload(:name => argv[0].to_s) end return nil end def select_math_type(value) if math.floor(value) == value return value.to_i else return value end end def select_namespace(namespace, name) if name.start_with?('_') return namespace else return @aya.get_global_namespace() end end end class AyaNamespace def initialize(aya, parent: nil) @aya = aya @parent = parent @table = {} end def put(name, content, index: nil) if not @parent.nil? and @parent.exists(name) @parent.put(name, content, :index => index) elsif index.nil? unless exists(name) @table[name] = AyaVariable.new(name) end @table[name].put(content) elsif exists(name) and index >=0 @table[name].put(content, :index => index) else #pass # ERROR end end def get(name, index: nil) if @table.include?(name) return @table[name].get(:index => index) elsif not @parent.nil? and @parent.exists(name) return @parent.get(name, :index => index) else return nil end end def set_separator(name, separator) if not @parent.nil? and @parent.exists(name) @parent.set_separator(name, separator) elsif @table.include?(name) @table[name].set_separator(separator) else #pass # ERROR end end def get_size(name) if @table.include?(name) return @table[name].get_size() elsif not @parent.nil? and @parent.exists(name) return @parent.get_size(name) else return 0 end end def remove(name) # only works with local table if @table.include?(name) @table.delete(name) end end def exists(name) result = (@table.include?(name) or \ (not @parent.nil? and @parent.exists(name))) return result end end class AyaGlobalNamespace < AyaNamespace SYS_VARIABLES = ['year', 'month', 'day', 'weekday', 'hour', '12hour', 'ampm', 'minute', 'second', 'systemuptickcount', 'systemuptime', 'systemuphour', 'systemupminute', 'systemupsecond', 'memoryload', 'memorytotalphys', 'memoryavailphys', 'memorytotalvirtual', 'memoryavailvirtual', 'random', 'ascii' # Ver.3 ] # except for 'aitalkinterval', etc. Re_res = Regexp.new('\Ares_reference\d+z') def reset_res_reference for key in @table.keys @table.delete(key) unless Re_res.match(key).nil? end end def get(name, index: nil) t = Time.now past = t - @aya.get_boot_time() result = case name when 'year' t.year when 'month' t.month when 'day' t.day when 'weekday' t.wday when 'hour' t.hour when '12hour' t.hour % 12 when 'ampm' if t.hour >= 12 1 # pm else 0 # am end when 'minute' t.min when 'second' t.sec when 'systemuptickcount' (past * 1000.0).to_i when 'systemuptime' past.to_i when 'systemuphour' (past / 60.0 / 60.0).to_i when 'systemupminute' ((past / 60.0).to_i % 60) when 'systemupsecond' (past.to_i % 60) when 'memoryload', 'memorytotalphys', 'memoryavailphys', 'memorytotalvirtual', 'memoryavailvirtual' 0 # FIXME else super(name, :index => index) end return result end def exists(name) return true if SYS_VARIABLES.include?(name) return super(name) end def load_database(aya) begin open(aya.dbpath, 'rb') do |f| line = f.readline() unless line.start_with?('# Format: v1.0') or \ line.start_with?('# Format: v1.1') or \ line.start_with?('# Format: v2.1') return 1 end if line.start_with?('# Format: v1.0') or \ line.start_with?('# Format: v1.1') charset = 'EUC-JP' # XXX else charset = 'utf-8' end for line in f line = line.force_encoding(charset).encode("UTF-8", :invalid => :replace, :undef => :replace) comma = line.index(',') if not comma.nil? and comma >= 0 key = line[0..comma-1] else next end value = line[comma + 1..-1].strip() comma = Aya.find_not_quoted(value, ',') if not comma.nil? and comma >= 0 separator = value[comma + 1..-1].strip() separator = separator[1..-2] value = value[0..comma-1].strip() value = value[1..-2] put(key, value.to_s) @table[key].set_separator(separator.to_s) elsif value.start_with?('"') # Format: v1.0 value = value[1..-2] put(key, value.to_s) elsif value != 'None' if value.include?('.') put(key, value.to_f) else put(key, value.to_i) end else #pass end end end rescue return 1 end return 0 end def save_database begin open(@aya.dbpath, 'w') do |f| f.write("# Format: v2.1\n") for key in @table.keys() line = @table[key].dump() f.write([line, "\n"].join('')) unless line.nil? end end rescue #except IOError: Logging::Logging.debug('aya.py: cannot write database (ignored)') return end end end class AyaStatement attr_reader :tokens SPECIAL_CHARS = '=+-*/<>|&!:' def initialize(line) @n_tokens = 0 @tokens = [] @position_of_next_token = 0 tokenize(line) end def tokenize(line) token_startpoint = 0 block_nest_level = 0 length = line.length i = 0 while i < length c = line[i] if c == '(' block_nest_level += 1 i += 1 elsif c == ')' block_nest_level -= 1 i += 1 elsif c == '"' if block_nest_level.zero? unless i.zero? append_unless_empty(line[token_startpoint..i-1].strip()) token_startpoint = i end end position = i while position < (length - 1) position += 1 if line[position] == '"' break end end i = position if block_nest_level.zero? @tokens << line[token_startpoint..position] token_startpoint = (position + 1) end i += 1 elsif block_nest_level.zero? and (c == ' ' or c == "\t") append_unless_empty(line[token_startpoint..i-1].strip()) i += 1 token_startpoint = i elsif block_nest_level.zero? and line[i] == ' ' append_unless_empty(line[token_startpoint..i-1].strip()) i += 1 token_startpoint = i elsif block_nest_level.zero? and \ line[i..-1].strip()[0..3] == '_in_' append_unless_empty(line[token_startpoint..i-1].strip()) @tokens << '_in_' i += 4 token_startpoint = i elsif block_nest_level.zero? and \ line[i..-1].strip()[0..4] == '!_in_' append_unless_empty(line[token_startpoint..i-1].strip()) @tokens << '!_in_' i += 5 token_startpoint = i elsif block_nest_level.zero? and SPECIAL_CHARS.include?(c) append_unless_empty(line[token_startpoint..i-1].strip()) ope_list = [':=', '+=', '-=', '*=', '/=', '%=', '<=', '>=', '==', '!=', '&&', '||', '+:=', '-:=', '*:=', '/:=', '%:='] if ope_list.include?(line[i..i + 1]) @tokens << line[i..i + 1] i += 2 token_startpoint = i elsif ope_list.include?(line[i..i + 2]) @tokens << line[i..i + 2] i += 3 token_startpoint = i else @tokens << line[i..i] i += 1 token_startpoint = i end else i += 1 end end append_unless_empty(line[token_startpoint..-1].strip()) @n_tokens = @tokens.length end def append_unless_empty(token) @tokens << token unless token.nil? or token.empty? end def has_more_tokens (@position_of_next_token < @n_tokens) end def countTokens @n_tokens end def next_token return nil unless has_more_tokens() result = @tokens[@position_of_next_token] @position_of_next_token += 1 return result end end class AyaVariable TYPE_STRING = 0 TYPE_INT = 1 TYPE_REAL = 2 TYPE_ARRAY = 3 def initialize(name) @name = name @line = '' @separator = ',' @type = nil @array = [] end def set_separator(separator) return if @type != TYPE_STRING @separator = separator reset() end def reset return if @type != TYPE_STRING @position = 0 @is_empty = false @array = [] while not @is_empty separator_position = @line.index(@separator, @position) if separator_position.nil? token = @line[@position..-1] @is_empty = true else token = @line[@position..separator_position-1] @position = separator_position + @separator.length end @array << token end end def get_size @array.length end def get(index: nil) if index.nil? case @type when TYPE_STRING return @line.to_s when TYPE_INT return @line.to_i when TYPE_REAL return @line.to_f else return '' end elsif 0 <= index and index < @array.length value = @array[index] case @type when TYPE_STRING return value.to_s when TYPE_INT return value.to_i when TYPE_REAL return value.to_f when TYPE_ARRAY return value else return nil # should not reach here end else return '' end end def put(value, index: nil) if index.nil? @line = value.to_s if value.is_a?(String) @type = TYPE_STRING elsif value.is_a?(Integer) @type = TYPE_INT elsif value.is_a?(Float) @type = TYPE_REAL elsif value.is_a?(Array) @type = TYPE_ARRAY @array = value end reset() elsif index < 0 #pass else if @type == TYPE_STRING @line = '' for i in 0..@array.length-1 if i == index @line = [@line, value.to_s].join('') else @line = [@line, @array[i]].join('') end if i != (@array.length - 1) @line = [@line, @separator].join('') end end if index >= @array.length for i in @array.length..index if i == index @line = [@line, @separator, value.to_s].join('') else @line = [@line, @separator, ''].join('') end end end reset() elsif @type == TYPE_ARRAY if 0 <= index and index < @array.length @array[index] = value end else #pass # ERROR end end end def dump line = nil if @type == TYPE_STRING line = (@name.to_s + ', "' + @line.to_s + '", "' + @separator.to_s + '"') elsif @type != TYPE_ARRAY line = (@name.to_s + ', ' + @line.to_s) else #pass end return line end end class AyaArgument def initialize(line) @line = line.strip() @length = @line.length @current_position = 0 end def has_more_tokens return (@current_position != -1 and \ @current_position < @length) end def next_token return nil unless has_more_tokens() startpoint = @current_position @current_position = position_of_next_token() if @current_position == -1 token = @line[startpoint..-1] else token = @line[startpoint..@current_position-2] end return token.strip() end def position_of_next_token locked = true position = @current_position parenthesis_nest_level = 0 while position < @length c = @line[position] case c when '"' return position unless locked while position < (@length - 1) position += 1 if @line[position] == '"' break end end when '(' parenthesis_nest_level += 1 when ')' parenthesis_nest_level -= 1 when ',' if parenthesis_nest_level.zero? locked = false end else return position unless locked end position += 1 end return -1 end end class AyaSaoriLibrary def initialize(saori, top_dir) @saori_list = {} @saori = saori end def load(name, top_dir) result = 0 head, name = File.split(name.gsub("\\", '/')) # XXX: don't encode here top_dir = File.join(top_dir, head) unless @saori.nil? or @saori_list.include?(name) module_ = @saori.request(name) @saori_list[name] = module_ unless module_.nil? end if @saori_list.include?(name) result = @saori_list[name].load(:dir => top_dir) end return result end def unload(name: nil) unless name.nil? name = File.split(name.gsub("\\", '/'))[-1] # XXX: don't encode here if @saori_list.include?(name) @saori_list[name].unload() @saori_list.delete(name) end else for key in @saori_list.keys() @saori_list[key].unload() end end return nil end def request(name, req) result = '' # FIXME name = File.split(name.gsub("\\", '/'))[-1] # XXX: don't encode here if not name.nil? and @saori_list.include?(name) result = @saori_list[name].request(req) end return result end end end ninix-aya-5.0.9/lib/ninix/dll/misaka.rb0000644000175000017500000021764613416507430016075 0ustar shyshy# -*- coding: utf-8 -*- # # misaka.rb - a "美坂" compatible Shiori module for ninix # Copyright (C) 2002 by Tamito KAJIYAMA # Copyright (C) 2002, 2003 by MATSUMURA Namihiko # Copyright (C) 2002-2019 by Shyouzou Sugitani # # This program is free software; you can redistribute it and/or modify it # under the terms of the GNU General Public License (version 2) as # published by the Free Software Foundation. It is distributed in the # hope that it will be useful, but WITHOUT ANY WARRANTY; without even the # implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR # PURPOSE. See the GNU General Public License for more details. # # TODO: # error recovery # - ${foo} # - $(foo) ? require "stringio" require_relative "../home" require_relative "../logging" begin require 'charlock_holmes' rescue LoadError CharlockHolmes = nil end module Misaka class MisakaError < StandardError # ValueError # pass end def self.lexical_error(path: nil, position: nil) if path.nil? and position.nil? error = 'lexical error' elsif path.nil? at = ('line ' + position[0].to_s + ', column ' + position[1].to_s) error = ('lexical error at ' + at.to_s) elsif position.nil? error = ('lexical error in ' + path.to_s) else at = ('line ' + position[0].to_s + ', column ' + position[1].to_s) error = ('lexical error at ' + at.to_s + ' in ' + path.to_s) end fail MisakaError.new(error) end def self.syntax_error(message, path: nil, position: nil) if path.nil? and position.nil? error = ('syntax error (' + message.to_s + ')') elsif path.nil? at = ('line ' + position[0].to_s + ', column ' + position[1].to_s) error = ('syntax error at ' + at.to_s + ' (' + message.to_s{1} + ')') elsif position.nil? error = ('syntax error in ' + path.to_s + ' (' + message.to_s{1} + ')') else at = ('line ' + position[0].to_s + ', column ' + position[1].to_s) error = ('syntax error at ' + at.to_s + ' in ' + path.to_s + ' (' + message.to_s + ')') end fail MisakaError.new(error) end def self.list_dict(top_dir) buf = [] begin filelist, debug, error = Misaka.read_misaka_ini(top_dir) rescue #except Exception as e: filelist = [] end for filename in filelist buf << File.join(top_dir, filename) end return buf end def self.read_misaka_ini(top_dir) path = File.join(top_dir, 'misaka.ini') filelist = [] debug = 0 error = 0 open(path) do |f| while true line = f.gets break if line.nil? line = line.strip() next if line.empty? or line.start_with?('//') case line when 'dictionaries' line = f.gets if line.strip() != '{' Misaka.syntax_error('expected an open brace', :path => path) end while true line = f.gets Misaka.syntax_error('unexpected end of file', :path => path) if line.nil? line = line.strip() break if line == '}' next if line.empty? or line.start_with?('//') filelist << Home.get_normalized_path(line) end when 'debug,0' debug = 0 when 'debug,1' debug = 1 when 'error,0' error = 0 when 'error,1' error = 1 when 'propertyhandler,0' #pass when 'propertyhandler,1' #pass else Logging::Logging.debug("misaka.rb: unknown directive '#{line}'") end end end return filelist, debug, error end ### LEXER ### TOKEN_NEWLINE = 1 TOKEN_WHITESPACE = 2 TOKEN_OPEN_BRACE = 3 TOKEN_CLOSE_BRACE = 4 TOKEN_OPEN_PAREN = 5 TOKEN_CLOSE_PAREN = 6 TOKEN_OPEN_BRACKET = 7 TOKEN_CLOSE_BRACKET = 8 TOKEN_DOLLAR = 9 TOKEN_COMMA = 11 TOKEN_SEMICOLON = 12 TOKEN_OPERATOR = 13 TOKEN_DIRECTIVE = 14 TOKEN_TEXT = 15 class Lexer attr_reader :buffer Re_comment = Regexp.new('\A[ \t]*//[^\r\n]*') Re_newline = Regexp.new('\A(\r\n|\r|\n)') Patterns = [ [TOKEN_NEWLINE, Re_newline], [TOKEN_WHITESPACE, Regexp.new('\A[ \t]+')], [TOKEN_OPEN_BRACE, Regexp.new('\A{')], [TOKEN_CLOSE_BRACE, Regexp.new('\A}')], [TOKEN_OPEN_PAREN, Regexp.new('\A\(')], [TOKEN_CLOSE_PAREN, Regexp.new('\A\)')], [TOKEN_OPEN_BRACKET, Regexp.new('\A\[')], [TOKEN_CLOSE_BRACKET, Regexp.new('\A\]')], [TOKEN_DOLLAR, Regexp.new('\A\$')], [TOKEN_COMMA, Regexp.new('\A,')], [TOKEN_SEMICOLON, Regexp.new('\A;')], [TOKEN_OPERATOR, Regexp.new('\A([!=]=|[<>]=?|=[<>]|&&|\|\||\+\+|--|[+\-*/]?=|[+\-*/%\^])')], [TOKEN_DIRECTIVE, Regexp.new('\A#[_A-Za-z][_A-Za-z0-9]*')], #[TOKEN_TEXT, Regexp.new("[!&|]|(\\[,\"]|[\x01\x02#'.0-9:?@A-Z\\_`a-z~]|[\x80-\xff].)+")], [TOKEN_TEXT, Regexp.new("\\A((\"[^\"]*\")|[!&|]|(\\\\[,\"]|[\x01\x02#'.0-9:?@A-Z\\\\_`a-z~\"]|[\\u0080-\\uffff]+)+)")], ] Token_names = { TOKEN_WHITESPACE => 'whitespace', TOKEN_NEWLINE => 'a newline', TOKEN_OPEN_BRACE => 'an open brace', TOKEN_CLOSE_BRACE => 'a close brace', TOKEN_OPEN_PAREN => 'an open paren', TOKEN_CLOSE_PAREN => 'a close paren', TOKEN_OPEN_BRACKET => 'an open bracket', TOKEN_CLOSE_BRACKET => 'a close bracket', TOKEN_DOLLAR => 'a dollar', TOKEN_COMMA => 'a comma', TOKEN_SEMICOLON => 'a semicolon', TOKEN_OPERATOR => 'an operator', TOKEN_DIRECTIVE => 'an directive', TOKEN_TEXT => 'text', } def initialize(charset: 'CP932') @buffer = [] @charset = charset end def _match(data, line, column, path) temp = data.clone while temp.length > 0 if column.zero? match = Re_comment.match(temp) unless match.nil? column = (column + match[0].length) temp = match.post_match next end end break_flag = false for token, pattern in Patterns match = pattern.match(temp) unless match.nil? lexeme = match[0] if token == TOKEN_TEXT and \ lexeme.start_with?('"') and lexeme.end_with?('"') unless lexeme.include?('$') @buffer << [token, lexeme[1..-2], [line, column]] else # XXX line, column = _match(lexeme[1..-2], line, column + 1, path) column += 1 end else @buffer << [token, lexeme, [line, column]] end temp = match.post_match break_flag = true break end end unless break_flag ###print(temp[0..100 - 1]) Misaka.lexical_error(:path => path, :position => [line, column]) end if token == TOKEN_NEWLINE line = (line + 1) column = 0 else column = (column + lexeme.length) end end return line, column end def read(f, path: nil) line = 1 column = 0 data = f.read() line, column = _match(data, line, column, path) @buffer << [TOKEN_NEWLINE, '', [line, column]] @path = path end def get_position @position end def pop begin token, lexeme, @position = @buffer.shift rescue #except IndexError: Misaka.syntax_error('unexpected end of file', :path => @path) end ###print(token, repr(lexeme)) return token, lexeme end def pop_check(expected) token, lexeme = pop() if token != expected Misaka.syntax_error(['exptected ', Token_names[expected], ', but returns ', Token_names[token]].join(''), :path => @path, :position => get_position()) end return lexeme end def look_ahead(index: 0) begin token, lexeme, position = @buffer[index] rescue #except IndexError: Misaka.syntax_error('unexpected end of file', :path => @path) end return token, lexeme end def skip_space(accept_eof: false) while not @buffer.empty? token, lexeme = look_ahead() return false unless [TOKEN_NEWLINE, TOKEN_WHITESPACE].include?(token) pop() end unless accept_eof Misaka.syntax_error('unexpected end of file', :path => @path) end return true end def skip_line while not @buffer.empty? token, lexeme = pop() break if token == TOKEN_NEWLINE end end end ### PARSER ### NODE_TEXT = 1 NODE_STRING = 2 NODE_VARIABLE = 3 NODE_INDEXED = 4 NODE_ASSIGNMENT = 5 NODE_FUNCTION = 6 NODE_IF = 7 NODE_CALC = 8 NODE_AND_EXPR = 9 NODE_OR_EXPR = 10 NODE_COMP_EXPR = 11 NODE_ADD_EXPR = 12 NODE_MUL_EXPR = 13 NODE_POW_EXPR = 14 class Parser def initialize(charset) @lexer = Lexer.new(:charset => charset) @charset = charset end def read(f, path: nil) @lexer.read(f, :path => path) @path = path end def get_dict common = nil groups = [] # skip junk tokens at the beginning of the file while true if @lexer.skip_space(:accept_eof => true) return common, groups end token, lexeme = @lexer.look_ahead() if token == TOKEN_DIRECTIVE and lexeme == '#_Common' or \ token == TOKEN_DOLLAR break end @lexer.skip_line() end token, lexeme = @lexer.look_ahead() if token == TOKEN_DIRECTIVE and lexeme == '#_Common' common = get_common() if @lexer.skip_space(:accept_eof => true) return common, groups end end while true groups << get_group() if @lexer.skip_space(:accept_eof => true) return common, groups end end return nil, [] end def get_common @lexer.pop() token, lexeme = @lexer.look_ahead() if token == TOKEN_WHITESPACE @lexer.pop() end @lexer.pop_check(TOKEN_NEWLINE) condition = get_brace_expr() @lexer.pop_check(TOKEN_NEWLINE) return condition end def get_group # get group name buf = [] @lexer.pop_check(TOKEN_DOLLAR) while true token, lexeme = @lexer.look_ahead() ##if token == TOKEN_TEXT or \ ## token == TOKEN_OPERATOR and \ ## ['+', '-', '*', '/', '%'].include?(lexeme) ## XXX unless [TOKEN_NEWLINE, TOKEN_COMMA, TOKEN_SEMICOLON].include?(token) token, lexeme = @lexer.pop() buf << unescape(lexeme) else break end end if buf.empty? Misaka.syntax_error('null identifier', :path => @path, :position => @lexer.get_position()) end name = ['$', buf.join('')].join('') # get group parameters parameters = [] while true token, lexeme = @lexer.look_ahead() if token == TOKEN_WHITESPACE @lexer.pop() end token, lexeme = @lexer.look_ahead() if token == TOKEN_NEWLINE break elsif [TOKEN_COMMA, TOKEN_SEMICOLON].include?(token) @lexer.pop() else Misaka.syntax_error('expected a delimiter', :path => @path, :position => @lexer.get_position()) end token, lexeme = @lexer.look_ahead() if token == TOKEN_WHITESPACE @lexer.pop() end token, lexeme = @lexer.look_ahead() break if token == TOKEN_NEWLINE token, lexeme = @lexer.look_ahead() if token == TOKEN_OPEN_BRACE parameters << get_brace_expr() elsif token == TOKEN_TEXT token, lexeme = @lexer.pop() parameters << [NODE_TEXT, unescape(lexeme)] else Misaka.syntax_error('expected a parameter or brace expression', :path => @path, :position => @lexer.get_position()) end end # get sentences @lexer.pop_check(TOKEN_NEWLINE) sentences = [] while true break if @lexer.skip_space(:accept_eof => true) token, lexeme = @lexer.look_ahead() break if token == TOKEN_DOLLAR sentence = get_sentence() next if sentence.nil? sentences << sentence end return [name, parameters, sentences] end def get_sentence token, lexeme = @lexer.look_ahead() if token == TOKEN_OPEN_BRACE token, lexeme = @lexer.look_ahead(:index => 1) if token == TOKEN_NEWLINE @lexer.pop_check(TOKEN_OPEN_BRACE) token, lexeme = @lexer.look_ahead() if token == TOKEN_WHITESPACE @lexer.pop() end @lexer.pop_check(TOKEN_NEWLINE) line = [] while true @lexer.skip_space() token, lexeme = @lexer.look_ahead() break if token == TOKEN_CLOSE_BRACE for node in get_line() line << node end @lexer.pop_check(TOKEN_NEWLINE) end @lexer.pop_check(TOKEN_CLOSE_BRACE) token, lexeme = @lexer.look_ahead() if token == TOKEN_WHITESPACE @lexer.pop() end else begin line = get_line() # beginning with brace expression rescue # except MisakaError as error: Logging::Logging.debug(error) @lexer.skip_line() return nil end end else begin line = get_line() rescue => error # except MisakaError as error: Logging::Logging.debug(error) @lexer.skip_line() return nil end end @lexer.pop_check(TOKEN_NEWLINE) return line end def is_whitespace(node) return (node[0] == NODE_TEXT and node[1].strip().empty?) end def unescape(text) text = text.gsub('\\,', ',') text = text.gsub('\\"', '"') return text end def get_word buf = [] while true token, lexeme = @lexer.look_ahead() if token == TOKEN_TEXT token, lexeme = @lexer.pop() if unescape(lexeme).start_with?('"') and \ unescape(lexeme).end_with?('"') buf << [NODE_STRING, unescape(lexeme)[1..-2]] else buf << [NODE_TEXT, unescape(lexeme)] end elsif [TOKEN_WHITESPACE, TOKEN_DOLLAR].include?(token) token, lexeme = @lexer.pop() buf << [NODE_TEXT, lexeme] elsif token == TOKEN_OPEN_BRACE buf << get_brace_expr() else break end end # strip whitespace at the beginning and/or end of line if not buf.empty? and is_whitespace(buf[0]) buf.delete_at(0) end if not buf.empty? and is_whitespace(buf[-1]) buf.delete_at(-1) end return buf end def get_line buf = [] while true token, lexeme = @lexer.look_ahead() if [TOKEN_NEWLINE, TOKEN_CLOSE_BRACE].include?(token) break elsif token == TOKEN_TEXT token, lexeme = @lexer.pop() buf << [NODE_TEXT, unescape(lexeme)] elsif [TOKEN_WHITESPACE, TOKEN_DOLLAR, TOKEN_OPEN_PAREN, TOKEN_CLOSE_PAREN, TOKEN_OPEN_BRACKET, TOKEN_CLOSE_BRACKET, TOKEN_OPERATOR, TOKEN_COMMA, TOKEN_SEMICOLON, TOKEN_DIRECTIVE].include?(token) token, lexeme = @lexer.pop() buf << [NODE_TEXT, lexeme] elsif token == TOKEN_OPEN_BRACE buf << get_brace_expr() else fail RuntimeError.new('should not reach here') end end # strip whitespace at the beginning and/or end of line if not buf.empty? and is_whitespace(buf[0]) buf.delete_at(0) end if not buf.empty? and is_whitespace(buf[-1]) buf.delete_at(-1) end return buf end def get_brace_expr @lexer.pop_check(TOKEN_OPEN_BRACE) @lexer.skip_space() @lexer.pop_check(TOKEN_DOLLAR) @lexer.skip_space() # get identifier (function or variable) nodelist = [[NODE_TEXT, '$']] while true token, lexeme = @lexer.look_ahead() if token == TOKEN_TEXT or \ (token == TOKEN_OPERATOR and ['+', '-', '*', '/', '%'].include?(lexeme)) token, lexeme = @lexer.pop() nodelist << [NODE_TEXT, unescape(lexeme)] elsif token == TOKEN_OPEN_BRACE nodelist << get_brace_expr() else break end end if nodelist.length == 1 Misaka.syntax_error('null identifier', :path => @path, :position => @lexer.get_position()) end @lexer.skip_space() token, lexeme = @lexer.look_ahead() if token == TOKEN_OPEN_PAREN # function name = nodelist.map {|node| node[1] }.join('') if name == '$if' cond_expr = get_cond_expr() then_clause = [[NODE_TEXT, 'true']] else_clause = [[NODE_TEXT, 'false']] @lexer.skip_space() token, lexeme = @lexer.look_ahead() if token == TOKEN_OPEN_BRACE @lexer.pop_check(TOKEN_OPEN_BRACE) @lexer.skip_space() then_clause = [] while true line = get_line() for node in line then_clause << node end token, lexem = @lexer.pop() break if token == TOKEN_CLOSE_BRACE fail "assert" unless token == TOKEN_NEWLINE end @lexer.skip_space() token, lexeme = @lexer.look_ahead() if token == TOKEN_TEXT and lexeme == 'else' @lexer.pop() @lexer.skip_space() @lexer.pop_check(TOKEN_OPEN_BRACE) @lexer.skip_space() else_clause = [] while true line = get_line() for node in line else_clause << node end token, lexem = @lexer.pop() break if token == TOKEN_CLOSE_BRACE fail "assert" unless token == TOKEN_NEWLINE end elsif token == TOKEN_OPEN_BRACE ## XXX @lexer.pop_check(TOKEN_OPEN_BRACE) @lexer.skip_space() else_clause = [] while true line = get_line() for node in line else_clause << node end token, lexem = @lexer.pop() break if token == TOKEN_CLOSE_BRACE fail "assert" unless token == TOKEN_NEWLINE end else else_clause = [[NODE_TEXT, '']] end end node = [NODE_IF, cond_expr, then_clause, else_clause] elsif name == '$calc' node = [NODE_CALC, get_add_expr()] else @lexer.pop_check(TOKEN_OPEN_PAREN) @lexer.skip_space() args = [] token, lexeme = @lexer.look_ahead() if token != TOKEN_CLOSE_PAREN while true args << get_argument() @lexer.skip_space() token, lexeme = @lexer.look_ahead() break if token != TOKEN_COMMA @lexer.pop() @lexer.skip_space() end end @lexer.pop_check(TOKEN_CLOSE_PAREN) node = [NODE_FUNCTION, name, args] end else # variable token, lexeme = @lexer.look_ahead() if token == TOKEN_OPERATOR operator = @lexer.pop_check(TOKEN_OPERATOR) case operator when '=', '+=', '-=', '*=', '/=' @lexer.skip_space() value = get_add_expr() when '++' operator = '+=' value = [[NODE_TEXT, '1']] when '--' operator = '-=' value = [[NODE_TEXT, '1']] else Misaka.syntax_error(['bad operator ', operator].join(''), :path => @path, :position => @lexer.get_position()) end node = [NODE_ASSIGNMENT, nodelist, operator, value] elsif token == TOKEN_OPEN_BRACKET @lexer.pop_check(TOKEN_OPEN_BRACKET) @lexer.skip_space() index = get_word() @lexer.skip_space() @lexer.pop_check(TOKEN_CLOSE_BRACKET) node = [NODE_INDEXED, nodelist, index] else node = [NODE_VARIABLE, nodelist] end end @lexer.skip_space() @lexer.pop_check(TOKEN_CLOSE_BRACE) return node end def get_argument buf = [] while true token, lexeme = @lexer.look_ahead() case token when TOKEN_TEXT token, lexeme = @lexer.pop() if unescape(lexeme).start_with?('"') and \ unescape(lexeme).end_with?('"') buf << [NODE_STRING, unescape(lexeme)[1..-2]] else buf << [NODE_TEXT, unescape(lexeme)] end when TOKEN_WHITESPACE, TOKEN_DOLLAR, TOKEN_OPEN_BRACKET, TOKEN_CLOSE_BRACKET, TOKEN_OPERATOR, TOKEN_SEMICOLON token, lexeme = @lexer.pop() buf << [NODE_TEXT, lexeme] when TOKEN_OPEN_BRACE buf << get_brace_expr() when TOKEN_NEWLINE @lexer.skip_space() else break end end # strip whitespace at the beginning and/or end of line if not buf.empty? and is_whitespace(buf[0]) buf.delete_at(0) end if not buf.empty? and is_whitespace(buf[-1]) buf.delete_at(-1) end return buf end def get_cond_expr @lexer.pop_check(TOKEN_OPEN_PAREN) @lexer.skip_space() or_expr = get_or_expr() @lexer.skip_space() @lexer.pop_check(TOKEN_CLOSE_PAREN) return or_expr end def get_or_expr buf = [NODE_OR_EXPR] buf << get_and_expr() while true @lexer.skip_space() token, lexeme = @lexer.look_ahead() break unless token == TOKEN_OPERATOR and lexeme == '||' @lexer.pop() @lexer.skip_space() buf << get_and_expr() end if buf.length > 2 return buf else return buf[1] end end def get_and_expr buf = [NODE_AND_EXPR] buf << get_sub_expr() while true @lexer.skip_space() token, lexeme = @lexer.look_ahead() break unless token == TOKEN_OPERATOR and lexeme == '&&' @lexer.pop() @lexer.skip_space() buf << get_sub_expr() end if buf.length > 2 return buf else return buf[1] end end def get_sub_expr token, lexeme = @lexer.look_ahead() if token == TOKEN_OPEN_PAREN return get_cond_expr() else return get_comp_expr() end end def get_comp_expr buf = [NODE_COMP_EXPR] buf << get_add_expr() @lexer.skip_space() token, lexeme = @lexer.look_ahead() if token == TOKEN_OPERATOR and \ ['==', '!=', '<', '<=', '=<', '>', '>=', '=>'].include?(lexeme) if lexeme == '=<' lexeme = '<=' elsif lexeme == '=>' lexeme = '>=' end buf << lexeme @lexer.pop() @lexer.skip_space() buf << get_add_expr() end if buf.length == 2 buf << '==' buf << [[NODE_TEXT, 'true']] end return buf end def get_add_expr buf = [NODE_ADD_EXPR] buf << get_mul_expr() while true @lexer.skip_space() token, lexeme = @lexer.look_ahead() break unless token == TOKEN_OPERATOR and ['+', '-'].include?(lexeme) buf << lexeme @lexer.pop() @lexer.skip_space() buf << get_mul_expr() end if buf.length > 2 return [buf] else return buf[1] end end def get_mul_expr buf = [NODE_MUL_EXPR] buf << get_pow_expr() while true @lexer.skip_space() token, lexeme = @lexer.look_ahead() break unless token == TOKEN_OPERATOR and ['*', '/', '%'].include?(lexeme) buf << lexeme @lexer.pop() @lexer.skip_space() buf << get_pow_expr() end if buf.length > 2 return [buf] else return buf[1] end end def get_pow_expr buf = [NODE_POW_EXPR] buf << get_unary_expr() while true @lexer.skip_space() token, lexeme = @lexer.look_ahead() break unless token == TOKEN_OPERATOR and lexeme == '^' @lexer.pop() @lexer.skip_space() buf << get_unary_expr() end if buf.length > 2 return [buf] else return buf[1] end end def get_unary_expr token, lexeme = @lexer.look_ahead() if token == TOKEN_OPERATOR and ['+', '-'].include?(lexeme) buf = [NODE_ADD_EXPR, [[NODE_TEXT, '0']], lexeme] @lexer.pop() @lexer.skip_space() buf << get_unary_expr() return [buf] else return get_factor() end end def get_factor token, lexeme = @lexer.look_ahead() if token == TOKEN_OPEN_PAREN @lexer.pop_check(TOKEN_OPEN_PAREN) @lexer.skip_space() add_expr = get_add_expr() @lexer.skip_space() @lexer.pop_check(TOKEN_CLOSE_PAREN) return add_expr else return get_word() end end # for debug def dump_list(nodelist, depth: 0) for node in nodelist dump_node(node, depth) end end def dump_node(node, depth) indent = ' ' * depth case node[0] when NODE_TEXT print([indent, 'TEXT'].join(''), " ", \ ['"', node[1], '"'].join(''), "\n") when NODE_STRING print([indent, 'STRING'].join(''), " ", \ ['"', node[1], '"'].join(''), "\n") when NODE_VARIABLE print([indent, 'VARIABLE'].join(''), "\n") dump_list(node[1], :depth => depth + 1) when NODE_INDEXED print([indent, 'INDEXED'].join(''), "\n") dump_list(node[1], :depth => depth + 1) print([indent, 'index'].join(''), "\n") dump_list(node[2], :depth => depth + 1) when NODE_ASSIGNMENT print([indent, 'ASSIGNMENT'].join(''), "\n") dump_list(node[1], :depth => depth + 1) print([indent, 'op'].join(''), " ", node[2], "\n") dump_list(node[3], :depth => depth + 1) when NODE_FUNCTION print([indent, 'FUNCTION'].join(''), " ", node[1], "\n") for i in 0..node[2].length-1 print([indent, 'args[' + i.to_s + ']'].join(''), "\n") dump_list(node[2][i], :depth => depth + 1) end when NODE_IF print([indent, 'IF'].join(''), "\n") dump_node(node[1], depth + 1) print([indent, 'then'].join(''), "\n") dump_list(node[2], :depth => depth + 1) print([indent, 'else'].join(''), "\n") dump_list(node[3], :depth => depth + 1) when NODE_CALC print([indent, 'CALC'].join(''), "\n") dump_list(node[1], :depth => depth + 1) when NODE_AND_EXPR print([indent, 'AND_EXPR'].join(''), "\n") for i in 1..node.length-1 dump_node(node[i], depth + 1) end when NODE_OR_EXPR print([indent, 'OR_EXPR'].join(''), "\n") for i in 1..node.length-1 dump_node(node[i], depth + 1) end when NODE_COMP_EXPR print([indent, 'COMP_EXPR'].join(''), "\n") dump_list(node[1], :depth => depth + 1) print([indent, 'op'].join(''), " ", node[2], "\n") dump_list(node[3], :depth => depth + 1) when NODE_ADD_EXPR print([indent, 'ADD_EXPR'].join(''), "\n") dump_list(node[1], :depth => depth + 1) for i in 2.step(node.length-1, 2) print([indent, 'op'].join(''), " ", node[i], "\n") dump_list(node[i + 1], :depth => depth + 1) end when NODE_MUL_EXPR print([indent, 'MUL_EXPR'].join(''), "\n") dump_list(node[1], :depth => depth + 1) for i in 2.step(node.length-1, 2) print([indent, 'op'].join(''), " ", node[i], "\n") dump_list(node[i + 1], :depth => depth + 1) end when NODE_POW_EXPR print([indent, 'POW_EXPR'].join(''), "\n") dump_list(node[1], :depth => depth + 1) for i in 2..node.length-1 print([indent, 'op ^'].join(''), "\n") dump_list(node[i], :depth => depth + 1) end else print(node, "\n") fail RuntimeError.new('should not reach here') end end end # <<< syntax >>> # dict := sp ( common sp )? ( group sp )* # sp := ( NEWLINE | WHITESPACE )* # common := "#_Common" NEWLINE brace_expr NEWLINE # group := group_name ( delimiter ( STRING | brace_expr ) )* # ( delimiter )? NEWLINE ( sentence )* # group_name := DOLLAR ( TEXT )+ # delimiter := ( WHITESPACE )? ( COMMA | SEMICOLON ) ( WHITESPACE )? # sentence := line NEWLINE | # OPEN_BRACE NEWLINE ( line NEWLINE )* CLOSE_BRACE NEWLINE # word := ( TEXT | WHITESPACE | DOLLAR | brace_expr | string )* # line := ( TEXT | WHITESPACE | DOLLAR | brace_expr | QUOTE | # OPEN_PAREN | CLOSE_PAREN | OPEN_BRACKET | CLOSE_BRACKET | # OPERATOR | COMMA | SEMICOLON | DIRECTIVE )* # string := QUOTE ( # TEXT | WHITESPACE | DOLLAR | OPEN_BRACE | CLOSE_BRACE | # OPEN_PAREN | CLOSE_PAREN | OPEN_BRACKET | CLOSE_BRACKET | # OPERATOR | COMMA | SEMICOLON | DIRECTIVE )* # QUOTE # brace_expr := variable | indexed | assignment | increment | # function | if | calc # variable := OPEN_BRACE sp var_name sp CLOSE_BRACE # indexed := OPEN_BRACE sp var_name sp # OPEN_BRACKET sp word sp CLOSE_BRACKET sp CLOSE_BRACE # var_name := DOLLAR ( TEXT | brace_expr )+ # assignment := OPEN_BRACE sp var_name sp assign_ops sp add_expr sp CLOSE_BRACE # assign_ops := "=" | "+=" | "-=" | "*=" | "/=" # increment := OPEN_BRACE sp var_name sp inc_ops sp CLOSE_BRACE # inc_ops := "+" "+" | "-" "-" # function := OPEN_BRACE sp DOLLAR sp ( TEXT )+ sp OPEN_PAREN # ( sp argument ( sp COMMA sp argument )* sp )? # CLOSE_PAREN sp CLOSE_BRACE # argument := ( TEXT | WHITESPACE | DOLLAR | brace_expr | string | # OPEN_BRACKET | CLOSE_BRACKET | OPERATOR | SEMICOLON )* # if := OPEN_BRACE sp DOLLAR sp "if" sp cond_expr # ( sp OPEN_BRACE ( sp line )* sp CLOSE_BRACE ( sp "else" # sp OPEN_BRACE ( sp line )* sp CLOSE_BRACE )? sp )? # sp CLOSE_BRACE # calc := OPEN_BRACE sp DOLLAR sp "calc" sp add_expr sp CLOSE_BRACE # cond_expr := OPEN_PAREN sp or_expr sp CLOSE_PAREN # or_expr := and_expr ( sp "||" sp and_expr )* # and_expr := sub_expr ( sp "&&" sp sub_expr )* # sub_expr := comp_expr | cond_expr # comp_expr := add_expr ( sp comp_op sp add_expr )? # comp_op := "==" | "!=" | "<" | "<=" | ">" | ">=" # add_expr := mul_expr (sp add_op sp mul_expr)* # add_op := "+" | "-" # mul_expr := pow_expr (sp mul_op sp pow_expr)* # mul_op := "*" | "/" | "%" # pow_expr := unary_expr (sp pow_op sp unary_expr)* # pow_op := "^" # unary_expr := unary_op sp unary_expr | factor # unary_op := "+" | "-" # factor := OPEN_PAREN sp add_expr sp CLOSE_PAREN | word ### INTERPRETER ### class Group def initialize(misaka, name, item_list: nil) @misaka = misaka @name = name if item_list.nil? ## FIXME @list = [] else @list = item_list end end def __len__ @list.length end def __getitem__(index) @list[index] end def get return nil if @list.empty? return @list.sample end def copy(name) MisakaArray.new(@misaka, name, @list) end end class NonOverlapGroup < Group def initialize(misaka, name, item_list: nil) super(misaka, name, :item_list => item_list) @indexes = [] end def get return nil if @list.empty? if @indexes.empty? @indexes = Array(0..@list.length-1) end i = Array(0..@indexes.length-1).sample index = @indexes.delete_at(i) return @list[index] end end class SequentialGroup < Group def initialize(misaka, name, item_list: nil) super(misaka, name, :item_list => item_list) @indexes = [] end def get return nil if @list.empty? if @indexes.empty? @indexes = Array(0..@list.length-1) end index = @indexes.shift return @list[index] end end class MisakaArray < Group def append(s) @list << [[NODE_TEXT, s.to_s]] end def index(s) for i in 0..@list.length-1 if s == @misaka.expand(@list[i]) return i end end return -1 end def pop return nil if @list.empty? i = Array(0..@list.length-1).sample item = @list.delete_at(i) return item end def popmatchl(s) buf = [] for i in 0..@list.length-1 t = @misaka.expand(@list[i]) if t.start_with?(s) buf << i end end return nil if buf.empty? i = buf.sample item = @list.delete_at(i) return item end end TYPE_SCHOLAR = 1 TYPE_ARRAY = 2 class Shiori DBNAME = 'misaka.db' def initialize(dll_name) @dll_name = dll_name @charset = 'CP932' end def use_saori(saori) @saori = saori end def find(top_dir, dll_name) result = 0 if not Misaka.list_dict(top_dir).empty? result = 210 elsif File.file?(File.join(top_dir, 'misaka.ini')) result = 200 end return result end def show_description Logging::Logging.info( "Shiori: MISAKA compatible module for ninix\n" \ " Copyright (C) 2002 by Tamito KAJIYAMA\n" \ " Copyright (C) 2002, 2003 by MATSUMURA Namihiko\n" \ " Copyright (C) 2002-2019 by Shyouzou Sugitani") end def reset @dict = {} @variable = {} @constant = {} @misaka_debug = 0 @misaka_error = 0 @reference = [nil, nil, nil, nil, nil, nil, nil, nil] @mouse_move_count = {} @random_talk = -1 @otherghost = [] @communicate = ['', ''] reset_request() end def load(dir: Dir::getwd) @misaka_dir = dir @dbpath = File.join(@misaka_dir, DBNAME) @saori_library = SaoriLibrary.new(@saori, @misaka_dir) reset() begin filelist, debug, error = Misaka.read_misaka_ini(@misaka_dir) rescue #except IOError: Logging::Logging.debug('cannot read misaka.ini') return 0 rescue #except MisakaError as error: Logging::Logging.debug(error) return 0 end @misaka_debug = debug @misaka_error = error global_variables = [] global_constants = [] # charset auto-detection unless CharlockHolmes.nil? detector = CharlockHolmes::EncodingDetector.new for filename in filelist path = File.join(@misaka_dir, filename) begin f = open(path, 'rb') rescue #except IOError: Logging::Logging.debug('cannot read ' + filename.to_s) next end ext = File.extname(filename) if ext == '.__1' result = detector.detect(crypt(f.read())) else result = detector.detect(f.read()) end f.close() if result[:confidence] > 98 and \ result[:encoding] != 'ISO-8859-1' # XXX @charset = result[:encoding] if @charset == 'Shift_JIS' @charset = 'CP932' # XXX end Logging::Logging.debug("CharlockHolmes(misaka.rb): '" + @charset.to_s + "'") break end end end for filename in filelist path = File.join(@misaka_dir, filename) basename = File.basename(filename, ".*") ext = File.extname(filename) begin if ext == '.__1' # should read lines as bytes f = open(path, 'rb') else f = open(path, :encoding => @charset + ":utf-8") end rescue #except IOError: Logging::Logging.debug('cannot read ' + filename.to_s) next end if ext == '.__1' f = StringIO.new(crypt(f.read())) end begin variables, constants = read(f, :path => path) rescue => error #except MisakaError as error: Logging::Logging.debug(error) next end global_variables |= variables global_constants |= constants end eval_globals(global_variables, false) load_database() eval_globals(global_constants, true) if @dict.include?('$_OnRandomTalk') @random_talk = 0 end return 1 end def crypt(data) return data.chars.map {|c| (c.ord ^ 0xff).chr }.join('').force_encoding(@charset).encode("UTF-8", :invalid => :replace, :undef => :replace) end def eval_globals(sentences, constant) for sentence in sentences for node in sentence if node[0] == NODE_ASSIGNMENT eval_assignment(node[1], node[2], node[3], :constant => constant) elsif node[0] == NODE_FUNCTION eval_function(node[1], node[2]) end end end end def read(f, path: nil) parser = Parser.new(@charset) parser.read(f, :path => path) common, dic = parser.get_dict() variables = [] constants = [] for name, parameters, sentences in dic if sentences.nil? next elsif name == '$_Variable' variables |= sentences next elsif name == '$_Constant' constants |= sentences next end _GroupClass = Group conditions = [] conditions << common unless common.nil? for node in parameters if node[0] == NODE_IF conditions << node elsif node[0] == NODE_TEXT and node[1] == 'nonoverlap' _GroupClass = NonOverlapGroup elsif node[0] == NODE_TEXT and node[1] == 'sequential' _GroupClass = SequentialGroup else #pass # ignore unknown parameters end end group = _GroupClass.new(self, name, :item_list => sentences) if @dict.include?(name) grouplist = @dict[name] else grouplist = @dict[name] = [] end grouplist << [group, conditions] end return variables, constants end def strip_newline(line) line.chomp end def load_database begin open(@dbpath) do |f| while true begin line = f.gets rescue #except UnicodeError: Logging::Logging.debug( 'misaka.rb: malformed database (ignored)') break end break if line.nil? header = strip_newline(line).split(nil, -1) if header[0] == 'SCHOLAR' name = header[1] value = strip_newline(f.gets) @variable[name] = [TYPE_SCHOLAR, value] elsif header[0] == 'ARRAY' begin size = Integer(header[1]) rescue #except ValueError: Logging::Logging.debug( 'misaka.rb: malformed database (ignored)') break end name = header[2] array = MisakaArray.new(self, name) for _ in 0..size-1 value = strip_newline(f.gets) array << value end @variable[name] = [TYPE_ARRAY, array] else fail RuntimeError.new('should not reach here') end end end rescue #except IOError: return end end def save_database begin open(@dbpath, 'w') do |f| for name in @variable.keys value_type, value = @variable[name] if value_type == TYPE_SCHOLAR next if value == '' f.write('SCHOLAR ' + name.to_s + "\n" + value.to_s + "\n") elsif value_type == TYPE_ARRAY f.write('ARRAY ' + value.length.to_s + ' ' + name.to_s + "\n") for item in value f.write(expand(item).to_s + "\n") end else fail RuntimeError.new('should not reach here') end end end rescue #except IOError: Logging::Logging.debug('misaka.rb: cannot write database (ignored)') return end end def unload save_database() @saori_library.unload() return 1 end def reset_request @req_command = '' @req_protocol = '' @req_key = [] @req_header = {} end def request(req_string) header = req_string.split(/\r?\n/, 0) line = header.shift unless line.nil? line = line.force_encoding(@charset).encode('utf-8', :invalid => :replace, :undef => :replace).strip() req_list = line.split(nil, -1) if req_list.length >= 2 @req_command = req_list[0].strip() @req_protocol = req_list[1].strip() end for line in header line = line.force_encoding(@charset).encode('utf-8', :invalid => :replace, :undef => :replace).strip() next if line.empty? next unless line.include?(':') key, value = line.split(':', 2) key = key.strip() begin value = Integer(value.strip()) rescue value = value.strip() end @req_key << key @req_header[key] = value end end # FIXME ref0 = get_ref(0) ref1 = get_ref(1) ref2 = get_ref(2) ref3 = get_ref(3) ref4 = get_ref(4) ref5 = get_ref(5) ref6 = get_ref(6) ref7 = get_ref(7) event = @req_header['ID'] script = nil if event == 'otherghostname' n = 0 refs = [] while true ref = get_ref(n) unless ref.nil? refs << ref else break end n += 1 end @otherghost = [] for ref in refs name, s0, s1 = ref.split(1.chr, 3) @otherghost << [name, s0, s1] end end if event == 'OnMouseMove' key = [ref3, ref4] # side, part if @mouse_move_count.include?(key) count = @mouse_move_count[key] else count = 0 end @mouse_move_count[key] = count + 5 end if event == 'OnCommunicate' @communicate = [ref0, ref1] script = get('$_OnGhostCommunicateReceive', :default => nil) elsif event == 'charset' script = @charset else @reference = [ref0, ref1, ref2, ref3, ref4, ref5, ref6, ref7] script = get(['$', event].join(''), :default => nil) end if event == 'OnSecondChange' if @random_talk.zero? reset_random_talk_interval() elsif @random_talk > 0 @random_talk -= 1 if @random_talk.zero? script = get('$_OnRandomTalk', :default => nil) end end end unless script.nil? script = script.strip() else script = '' end reset_request() response = ["SHIORI/3.0 200 OK\r\n", "Sender: Misaka\r\n", "Charset: ", @charset.encode(@charset, :invalid => :replace, :undef => :replace), "\r\n", "Value: ", script.encode(@charset, :invalid => :replace, :undef => :replace), "\r\n"].join('') to = eval_variable([[NODE_TEXT, '$to']]) unless to.nil? @variable.delete('$to') response = [response, "Reference0: ", to.encode(@charset, :invalid => :replace, :undef => :replace), "\r\n"].join('') end response = [response, "\r\n"].join('') return response end def get_ref(num) key = ['Reference', num.to_s].join('') return @req_header[key] end # internal def reset_random_talk_interval interval = 0 if @variable.include?('$_talkinterval') value_type, value = @variable['$_talkinterval'] else value_type, value = TYPE_SCHOLAR, '' end if value_type == TYPE_SCHOLAR begin interval = [Integer(value), 0].max rescue #except ValueError: #pass end end @random_talk = (interval * Array(5..15).sample / 10.0).to_i end def get(name, default: '') grouplist = @dict[name] return default if grouplist.nil? for group, conditions in grouplist if eval_and_expr(conditions) == 'true' return expand(group.get()) end end return default end def expand(nodelist) return '' if nodelist.nil? buf = [] for node in nodelist buf << eval_(node) end return buf.join('') end def expand_args(nodelist) tmp = expand(nodelist) if tmp.start_with?('$') return eval_variable(nodelist) else return tmp end end def eval_(node) case node[0] when NODE_TEXT result = node[1] when NODE_STRING result = node[1] when NODE_VARIABLE result = eval_variable(node[1]) when NODE_INDEXED result = eval_indexed(node[1], node[2]) when NODE_ASSIGNMENT result = eval_assignment(node[1], node[2], node[3]) when NODE_FUNCTION result = eval_function(node[1], node[2]) when NODE_IF result = eval_if(node[1], node[2], node[3]) when NODE_CALC result = eval_calc(node[1]) when NODE_COMP_EXPR result = eval_comp_expr(node[1], node[2], node[3]) when NODE_AND_EXPR result = eval_and_expr(node[1..-1]) when NODE_OR_EXPR result = eval_or_expr(node[1..-1]) when NODE_ADD_EXPR result = eval_add_expr(node[1..-1]) when NODE_MUL_EXPR result = eval_mul_expr(node[1..-1]) when NODE_POW_EXPR result = eval_pow_expr(node[1..-1]) else ##print(node) fail RuntimeError.new('should not reach here') end return result end SYSTEM_VARIABLES = [ # current date and time '$year', '$month', '$day', '$hour', '$minute', '$second', # day of week (0 = Sunday) '$dayofweek', # ghost uptime '$elapsedhour', '$elapsedminute', '$elapsedsecond', # OS uptime '$elapsedhouros', '$elapsedminuteos', '$elapsedsecondos', # OS accumlative uptime '$elapsedhourtotal', '$elapsedminutetotal', '$elapsedsecondtotal', # system information '$os.version', '$os.name', '$os.phisicalmemorysize', '$os.freememorysize', '$os.totalmemorysize', '$cpu.vendorname', '$cpu.name', '$cpu.clockcycle', # number of days since the last network update '$daysfromlastupdate', # number of days since the first boot '$daysfromfirstboot', # name of the ghost with which it has communicated ##'$to', '$sender', '$lastsentence', '$otherghostlist', ] def eval_variable(name) now = Time.now name = expand(name) case name when '$year' result = now.year when '$month' result = now.month when '$day' result = now.day when '$hour' result = now.hour when '$minute' result = now.min when '$second' result = now.sec when '$dayofweek' result = now.wday when '$lastsentence' result = @communicate[1] when '$otherghostlist' unless @otherghost.empty? ghost = @otherghost.sample result = ghost[0] else result = '' end when '$sender' result = @communicate[0] when *SYSTEM_VARIABLES result = '' when *@dict.keys result = get(name) when *@constant.keys value_type, value = @constant[name] if value_type == TYPE_ARRAY result = expand(value.get()) else result = value.to_s end when *@variable.keys value_type, value = @variable[name] if value_type == TYPE_ARRAY result = expand(value.get()) else result = value.to_s end else result = '' end return result end def eval_indexed(name, index) name = expand(name) index = expand(index) begin index = Integer(index) rescue #except ValueError: index = 0 end case name when '$otherghostlist' group = [] for ghost in @otherghost group << ghost[0] end when *SYSTEM_VARIABLES return '' when *@dict.keys grouplist = @dict[name] group, constraints = grouplist[0] when *@constant.keys return '' when *@variable.keys value_type, group = @variable[name] return '' if value_type != TYPE_ARRAY else return '' end return '' if index < 0 or index >= group.length return expand(group[index]) end def eval_assignment(name, operator, value, constant: false) name = expand(name) value = expand(value) if ['+=', '-=', '*=', '/='].include?(operator) begin operand = Integer(value) rescue #except ValueError: operand = 0 end if @constant.include?(name) value_type, value = @constant[name] else value_type, value = nil, '' end if value_type.nil? if @variable.include?(name) value_type, value = @variable[name] else value_type, value = TYPE_SCHOLAR, '' end end return '' if value_type == TYPE_ARRAY begin value = Integer(value) rescue #except ValueError: value = 0 end if operator == '+=' value += operand elsif operator == '-=' value -= operand elsif operator == '*=' value *= operand elsif operator == '/=' and not operand.zero? value /= operand value = value.to_i end end if constant or @constant.include?(name) @constant[name] = [TYPE_SCHOLAR, value] else @variable[name] = [TYPE_SCHOLAR, value] end if name == '$_talkinterval' reset_random_talk_interval() end return '' end def eval_comp_expr(operand1, operator, operand2) value1 = expand(operand1) value2 = expand(operand2) begin operand1 = Integer(value1) operand2 = Integer(value2) rescue #except ValueError: operand1 = value1 operand2 = value2 end if (operator == '==' and operand1 == operand2) or \ (operator == '!=' and operand1 != operand2) or \ (operator == '<' and operand1 < operand2) or \ (operator == '<=' and operand1 <= operand2) or \ (operator == '>' and operand1 > operand2) or \ (operator == '>=' and operand1 >= operand2) return 'true' end return 'false' end def eval_and_expr(conditions) for condition in conditions boolean = eval_(condition) if boolean.strip() != 'true' return 'false' end end return 'true' end def eval_or_expr(conditions) for condition in conditions boolean = eval_(condition) if boolean.strip() == 'true' return 'true' end end return 'false' end def eval_add_expr(expression) value = expand(expression[0]) begin value = Integer(value) rescue #except ValueError: value = 0 end for i in 1.step(expression.length-1, 2) operand = expand(expression[i + 1]) begin operand = Integer(operand) rescue #except ValueError: operand = 0 end if expression[i] == '+' value += operand elsif expression[i] == '-' value -= operand end end return value.to_s end def eval_mul_expr(expression) value = expand(expression[0]) begin value = Integer(value) rescue #except ValueError: value = 0 end for i in 1.step(expression.length-1, 2) operand = expand(expression[i + 1]) begin operand = Integer(operand) rescue #except ValueError: operand = 0 end if expression[i] == '*' value *= operand elsif expression[i] == '/' and not operand.zero? value /= operand value = value.to_i elsif expression[i] == '%' and not operand.zero? value = (value % operand) end end return value.to_s end def eval_pow_expr(expression) value = expand(expression[-1]) begin value = Integer(value) rescue #except ValueError: value = 0 end for i in 1..expression.length-1 operand = expand(expression[-i - 1]) begin operand = Integer(operand) rescue #except ValueError: operand = 0 end value = (operand**value) end return value.to_s end def eval_if(condition, then_clause, else_clause) boolean = eval_(condition) if boolean.strip() == 'true' return expand(then_clause) else return expand(else_clause) end end def eval_calc(expression) expand(expression) end def eval_function(name, args) function = SYSTEM_FUNCTIONS[name] return '' if function.nil? return method(function).call(args) end def is_number(s) return (s and s.chars.map {|c| '0123456789'.include?(c) }.all?) end def split(s) buf = [] i, j = 0, s.length while i < j if s[i].ord < 0x80 buf << s[i] i += 1 else buf << s[i..i + 1] i += 2 end end return buf end def exec_reference(args) return '' if args.length != 1 n = expand_args(args[0]) if '01234567'.include?(n) value = @reference[n.to_i] return value.to_s unless value.nil? end return '' end def exec_random(args) return '' if args.length != 1 n = expand_args(args[0]) begin return Array(0..Integer(n)-1).sample.to_s rescue #except ValueError: return '' # n < 1 or not a number end end def exec_choice(args) return '' if args.empty? return expand_args(args.sample) end def exec_getvalue(args) return '' if args.length != 2 begin n = Integer(expand_args(args[1])) rescue #except ValueError: return '' end if n < 0 return '' end value_list = expand_args(args[0]).split(',', 0) begin return value_list[n] rescue #except IndexError: return '' end end def exec_search(args) namelist = [] for arg in args name = expand_args(arg) if name.strip() namelist << name end end return '' if namelist.empty? keylist = [] for key in keys() # dict, variable, constant break_flag = false for name in namelist unless key.include?(name) break_flag = true break end end unless break_flag keylist << key end end return '' if keylist.empty? return eval_variable([[NODE_TEXT, keylist.sample]]) end def keys buf = @dict.keys() buf |= @variable.keys() buf |= @constant.keys() return buf end def exec_backup(args) if args.empty? save_database() end return '' end def exec_getmousemovecount(args) if args.length == 2 side = expand_args(args[0]) part = expand_args(args[1]) begin key = [Integer(side), part] rescue #except ValueError: #pass else if @mouse_move_count.include?(key) return @mouse_move_count[key].to_s else return 0 end end end return '' end def exec_resetmousemovecount(args) if args.length == 2 side = expand_args(args[0]) part = expand_args(args[1]) begin key = [Integer(side), part] rescue #except ValueError: pass else @mouse_move_count[key] = 0 end end return '' end def exec_substring(args) return '' if args.length != 3 value = expand_args(args[2]) begin count = Integer(value) rescue #except ValueError: return '' end return '' if count < 0 value = expand_args(args[1]) begin offset = Integer(value) rescue #except ValueError: return '' end return '' if offset < 0 s = expand_args(args[0]).encode(@charset, :invalid => :replace, :undef => :replace) return s[offset..offset + count - 1].force_encoding(@charset).encode('utf-8', :invalid => :replace, :undef => :replace) end def exec_substringl(args) return '' if args.length != 2 value = expand_args(args[1]) begin count = Integer(value) rescue #except ValueError: return '' end return '' if count < 0 s = expand_args(args[0]).encode(@charset, :invalid => :replace, :undef => :replace) return s[0..count-1].force_encoding(@charset).encode('utf-8', :invalid => :replace, :undef => :replace) end def exec_substringr(args) return '' if args.length != 2 value = expand_args(args[1]) begin count = Integer(value) rescue #except ValueError: return '' end return '' if count < 0 s = expand_args(args[0]).encode(@charset, :invalid => :replace, :undef => :replace) return s[s.length - count..-1].force_encoding(@charset).encode('utf-8', :invalid => :replace, :undef => :replace) end def exec_substringfirst(args) return '' if args.length != 1 buf = expand_args(args[0]) return '' if buf.empty? return buf[0] end def exec_substringlast(args) return '' if args.length != 1 buf = expand_args(args[0]) return '' if buf.empty? return buf[-1] end def exec_length(args) return '' if args.length != 1 return expand_args(args[0]).encode(@charset, :invalid => :replace, :undef => :replace).length.to_s end Katakana2hiragana = { 'ァ' => 'ぁ', 'ア' => 'あ', 'ィ' => 'ぃ', 'イ' => 'い', 'ゥ' => 'ぅ', 'ウ' => 'う', 'ェ' => 'ぇ', 'エ' => 'え', 'ォ' => 'ぉ', 'オ' => 'お', 'カ' => 'か', 'ガ' => 'が', 'キ' => 'き', 'ギ' => 'ぎ', 'ク' => 'く', 'グ' => 'ぐ', 'ケ' => 'け', 'ゲ' => 'げ', 'コ' => 'こ', 'ゴ' => 'ご', 'サ' => 'さ', 'ザ' => 'ざ', 'シ' => 'し', 'ジ' => 'じ', 'ス' => 'す', 'ズ' => 'ず', 'セ' => 'せ', 'ゼ' => 'ぜ', 'ソ' => 'そ', 'ゾ' => 'ぞ', 'タ' => 'た', 'ダ' => 'だ', 'チ' => 'ち', 'ヂ' => 'ぢ', 'ッ' => 'っ', 'ツ' => 'つ', 'ヅ' => 'づ', 'テ' => 'て', 'デ' => 'で', 'ト' => 'と', 'ド' => 'ど', 'ナ' => 'な', 'ニ' => 'に', 'ヌ' => 'ぬ', 'ネ' => 'ね', 'ノ' => 'の', 'ハ' => 'は', 'バ' => 'ば', 'パ' => 'ぱ', 'ヒ' => 'ひ', 'ビ' => 'び', 'ピ' => 'ぴ', 'フ' => 'ふ', 'ブ' => 'ぶ', 'プ' => 'ぷ', 'ヘ' => 'へ', 'ベ' => 'べ', 'ペ' => 'ぺ', 'ホ' => 'ほ', 'ボ' => 'ぼ', 'ポ' => 'ぽ', 'マ' => 'ま', 'ミ' => 'み', 'ム' => 'む', 'メ' => 'め', 'モ' => 'も', 'ャ' => 'ゃ', 'ヤ' => 'や', 'ュ' => 'ゅ', 'ユ' => 'ゆ', 'ョ' => 'ょ', 'ヨ' => 'よ', 'ラ' => 'ら', 'リ' => 'り', 'ル' => 'る', 'レ' => 'れ', 'ロ' => 'ろ', 'ヮ' => 'ゎ', 'ワ' => 'わ', 'ヰ' => 'ゐ', 'ヱ' => 'ゑ', 'ヲ' => 'を', 'ン' => 'ん', 'ヴ' => 'う゛', } def exec_hiraganacase(args) return '' if args.length != 1 buf = [] string = expand_args(args[0]).force_encoding(@charset).encode('utf-8', :invalid => :replace, :undef => :replace) #string = args[0].force_encoding(@charset).encode('utf-8', :invalid => :replace, :undef => :replace) for c in string.chars if Katakana2hiragana.include?(c) c = Katakana2hiragana[c] else #pass end buf << c end return buf.join('') end def exec_isequallastandfirst(args) return 'false' if args.length != 2 buf0 = expand_args(args[0]) buf1 = expand_args(args[1]) if not buf0.empty? and not buf1.empty? and buf0[-1] == buf1[0] return 'true' else return 'false' end end def exec_append(args) if args.length == 2 name = expand_args(args[0]) if not name.empty? and name.start_with?('$') if @variable.include?(name) value_type, value = @variable[name] else value_type, value = TYPE_SCHOLAR, '' end if value_type == TYPE_SCHOLAR value_type = TYPE_ARRAY array = MisakaArray.new(self, name) if value != '' array.append(value) end value = array end member_list = expand_args(args[1]).split(1.chr, 0) for member in member_list value.append(member) end @variable[name] = [value_type, value] end end return '' end def exec_stringexists(args) return '' if args.length != 2 name = expand_args(args[0]) if @variable.include?(name) value_type, value = @variable[name] else value_type, value = TYPE_SCHOLAR, '' end return '' if value_type == TYPE_SCHOLAR if value.index(expand_args(args[1])).nil? # value.index(expand_args(args[1])) < 0 return 'false' else return 'true' end end def exec_copy(args) return '' if args.length != 2 src_name = expand_args(args[0]) return '' unless not src_name.empty? and src_name.start_with?('$') new_name = expand_args(args[1]) return '' unless not new_name.empty? and new_name.start_with?('$') source = nil if @dict.include?(src_name) grouplist = @dict[src_name] source, conditions = grouplist[0] elsif @variable.include?(src_name) value_type, value = @variable[src_name] if value_type == TYPE_ARRAY source = value end end if source.nil? value = MisakaArray.new(self, new_name) else value = source.copy(new_name) end @variable[new_name] = [TYPE_ARRAY, value] return '' end def exec_pop(args) if args.length == 1 name = expand_args(args[0]) if @variable.include?(name) value_type, value = @variable[name] else value_type, value = TYPE_SCHOLAR, '' end if value_type == TYPE_ARRAY return expand(value.pop()) end end return '' end def exec_popmatchl(args) if args.length == 2 name = expand_args(args[0]) if @variable.include?(name) value_type, value = @variable[name] else value_type, value = TYPE_SCHOLAR, '' end if value_type == TYPE_ARRAY return expand(value.popmatchl(expand_args(args[1]))) end end return '' end def exec_index(args) if args.length == 2 pos = expand_args(args[1]).find(expand_args(args[0])) if pos > 0 s = expand_args(args[0])[pos] pos = s.encode(@charset, :invalid => :replace, :undef => :replace).length end return pos.to_s end return '' end def exec_insentence(args) return '' if args.empty? s = expand_args(args[0]) for i in 1..args.length-1 unless s.include?(expand_args(args[i])) return 'false' end end return 'true' end def exec_substringw(args) return '' if args.length != 3 value = expand_args(args[2]) begin count = Integer(value) rescue #except ValueError: return '' end if count < 0 return '' end value = expand_args(args[1]) begin offset = Integer(value) rescue #except ValueError: return '' end return '' if offset < 0 buf = expand_args(args[0]) return buf[offset..offset + count - 1] end def exec_substringwl(args) return '' if args.length != 2 value = expand_args(args[1]) begin count = Integer(value) rescue #except ValueError: return '' end return '' if count < 0 buf = expand_args(args[0]) return buf[0..count-1] end def exec_substringwr(args) return '' if args.length != 2 value = expand_args(args[1]) begin count = Integer(value) rescue #except ValueError: return '' end return '' if count < 0 buf = expand_args(args[0]) return buf[buf.length - count..-1] end Prefixes = ['さん', 'ちゃん', '君', 'くん', '様', 'さま', '氏', '先生'] def exec_adjustprefix(args) return '' if args.length != 2 s = expand_args(args[0]) for prefix in Prefixes if s.end_with?(prefix) s = [s[0..-prefix.length-1], expand_args(args[1])].join('') break end end return s end def exec_count(args) return '' if args.length != 1 name = expand_args(args[0]) begin value_type, value = @variable[name] if value_type == TYPE_SCHOLAR return '1' end return value.length.to_s rescue #except KeyError: return '-1' end end def exec_inlastsentence(args) return '' if args.empty? s = @communicate[1] for i in 1..args.length-1 unless s.include?(expand_args(args[i])) return 'false' end end return 'true' end def saori(args) saori_statuscode = '' saori_header = [] saori_value = {} saori_protocol = '' req = ["EXECUTE SAORI/1.0\r\n", "Sender: MISAKA\r\n", "SecurityLevel: local\r\n", "Charset: ", @charset.encode(@charset, :invalid => :replace, :undef => :replace), "\r\n"].join('') for i in 1..args.length-1 req = [req, 'Argument', i.to_s.encode(@charset, :invalid => :replace, :undef => :replace), ': ', expand_args(args[i]).encode(@charset, :invalid => :replace, :undef => :replace), "\r\n"].join('') end req = [req, "\r\n"].join('') response = @saori_library.request(expand_args(args[0]), req) header = response.split(/\r?\n/, 0) unless header.empty? line = header.shift line = line.force_encoding(@charset).encode('utf-8', :invalid => :replace, :undef => :replace).strip() if line.include?(' ') saori_protocol, saori_statuscode = line.split(' ', 2) saori_protocol.strip! saori_statuscode.strip! end for line in header line = line.force_encoding(@charset).encode('utf-8', :invalid => :replace, :undef => :replace).strip() next if line.empty? nexy unless line.include?(':') key, value = line.split(':', 2) key.strip! value.strip! unless key.empty? saori_header << key saori_value[key] = value end end end if saori_value.include?('Result') return saori_value['Result'] else return '' end end def load_saori(args) return '' if args.empty? result = @saori_library.load(expand_args(args[0]), @misaka_dir) if result.zero? return '' else result.to_s end end def unload_saori(args) return '' if args.empty? result = @saori_library.unload(:name => expand_args(args[0])) if result.zero? return '' else return result.to_s end end def exec_isghostexists(args) result = 'false' if args.length != 1 if @otherghost.length > 0 result = 'true' @variable['$to'] = [TYPE_SCHOLAR, @otherghost.sample[0]] end else for ghost in @otherghost if ghost[0] == expand_args(args[0]) result = 'true' break end end end return result end end SYSTEM_FUNCTIONS = { '$reference' => 'exec_reference', '$random' => 'exec_random', '$choice' => 'exec_choice', '$getvalue' => 'exec_getvalue', '$inlastsentence' => 'exec_inlastsentence', '$isghostexists' => 'exec_isghostexists', '$search' => 'exec_search', '$backup' => 'exec_backup', '$getmousemovecount' => 'exec_getmousemovecount', '$resetmousemovecount' => 'exec_resetmousemovecount', '$substring' => 'exec_substring', '$substringl' => 'exec_substringl', '$substringr' => 'exec_substringr', '$substringfirst' => 'exec_substringfirst', '$substringlast' => 'exec_substringlast', '$length' => 'exec_length', '$hiraganacase' => 'exec_hiraganacase', '$isequallastandfirst' => 'exec_isequallastandfirst', '$append' => 'exec_append', '$stringexists' => 'exec_stringexists', '$copy' => 'exec_copy', '$pop' => 'exec_pop', '$popmatchl' => 'exec_popmatchl', '$index' => 'exec_index', '$insentence' => 'exec_insentence', '$substringw' => 'exec_substringw', '$substringwl' => 'exec_substringwl', '$substringwr' => 'exec_substringwr', '$adjustprefix' => 'exec_adjustprefix', '$count' => 'exec_count', '$saori' => 'saori', '$loadsaori' => 'load_saori', '$unloadsaori' => 'unload_saori', } class SaoriLibrary def initialize(saori, top_dir) @saori_list = {} @saori = saori end def load(name, top_dir) result = 0 head, name = File.split(name.gsub('\\', '/')) # XXX: don't encode here top_dir = File.join(top_dir, head) unless @saori.nil? or @saori_list.include?(name) module_ = @saori.request(name) unless module_.nil? @saori_list[name] = module_ end end if @saori_list.include?(name) result = @saori_list[name].load(:dir => top_dir) end return result end def unload(name: nil) unless name.nil? name = File.split(name.gsub('\\', '/'))[-1] # XXX: don't encode here if @saori_list.include?(name) @saori_list[name].unload() @saori_list.delete(name) end else for key in @saori_list.keys @saori_list[key].unload() end end return nil end def request(name, req) result = '' # FIXME name = File.split(name.gsub('\\', '/'))[-1] # XXX: don't encode here if not name.empty? and @saori_list.include?(name) result = @saori_list[name].request(req) end return result end end end ninix-aya-5.0.9/lib/ninix/pix.rb0000644000175000017500000003162613416507430014645 0ustar shyshy# -*- coding: utf-8 -*- # # Copyright (C) 2003-2019 by Shyouzou Sugitani # # This program is free software; you can redistribute it and/or modify it # under the terms of the GNU General Public License (version 2) as # published by the Free Software Foundation. It is distributed in the # hope that it will be useful, but WITHOUT ANY WARRANTY; without even the # implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR # PURPOSE. See the GNU General Public License for more details. # require "narray" require "digest/md5" require "gtk3" require_relative "logging" module Pix def self.surface_to_region(surface) region = Cairo::Region.new() width = surface.width pix_na = NArray.to_na(surface.data, NArray::BYTE) pix_na.reshape!(4, pix_na.size / 4) curr_y = nil start_x = nil end_x = nil pix_na[3, true].where.each {|i| y, x = i.divmod(width) unless start_x.nil? if y != curr_y or x != (end_x + 1) region.union!(start_x, curr_y, end_x - start_x + 1, 1) curr_y = y start_x = x end else curr_y = y start_x = x end end_x = x } region.union!(start_x, curr_y, end_x - start_x + 1, 1) unless start_x.nil? return region end def self.surface_to_region_with_hints(surface, device_extents) device_x1, device_y1, device_x2, device_y2 = device_extents region = Cairo::Region.new() width = surface.width height = surface.height stride = surface.stride device_x1 =[0, [device_x1, width - 1].min].max device_x2 =[0, [device_x2, width - 1].min].max device_y1 =[0, [device_y1, height - 1].min].max device_y2 =[0, [device_y2, height - 1].min].max device_w = device_x2 - device_x1 + 1 device_h = device_y2 - device_y1 + 1 pix_na = NArray.to_na(surface.data[device_y1 * stride, device_h * stride], NArray::BYTE) pix_na.reshape!(4, width, device_h) pix_na = pix_na.slice(3, device_x1..device_x2, true) curr_y = nil start_x = nil end_x = nil pix_na.where.each {|i| y, x = i.divmod(device_w) unless start_x.nil? if y != curr_y or x != (end_x + 1) region.union!(start_x, curr_y, end_x - start_x + 1, 1) curr_y = y start_x = x end else curr_y = y start_x = x end end_x = x } region.union!(start_x, curr_y, end_x - start_x + 1, 1) unless start_x.nil? region.translate!(device_x1, device_y1) return region end class BaseTransparentWindow < Gtk::Window alias :base_move :move attr_reader :supports_alpha def initialize(type: Gtk::WindowType::TOPLEVEL) super(type) set_decorated(false) #set_resizable(false) signal_connect("screen-changed") do |widget, old_screen| screen_changed(widget, :old_screen => old_screen) next true end screen_changed(self) end def screen_changed(widget, old_screen: nil) if composited? set_visual(screen.rgba_visual) else set_visual(screen.system_visual) Logging::Logging.debug("screen does NOT support alpha.\n") end @supports_alpha = composited? fail "assert" unless not visual.nil? maximize # not fullscreen end end class TransparentWindow < BaseTransparentWindow attr_accessor :darea def initialize super() set_app_paintable(true) set_focus_on_map(false) @__surface_position = [0, 0] @prev_position = [0, 0] # create drawing area @darea = Gtk::DrawingArea.new @darea.set_size_request(*size) # XXX @darea.show() add(@darea) @region = nil @device_extents = nil end def move(x, y) @__surface_position = [x, y] @darea.queue_draw() end def get_draw_offset return @__surface_position end def winpos_to_surfacepos(x, y, scale) surface_x, surface_y = @__surface_position new_x = ((x - surface_x) * 100 / scale).to_i new_y = ((y - surface_y) * 100 / scale).to_i return new_x, new_y end def set_surface(cr, surface, scale) cr.save() # clear cr.set_operator(Cairo::OPERATOR_SOURCE) cr.set_source_rgba(0, 0, 0, 0) cr.paint # translate the user-space origin cr.translate(*get_draw_offset) # XXX cr.scale(scale / 100.0, scale / 100.0) cr.set_source(surface, 0, 0) cr.set_operator(Cairo::OPERATOR_SOURCE) # copy rectangle on the destination cr.rectangle(0, 0, surface.width, surface.height) extents = cr.path_extents # cr.fill_extents device_x1, device_y1 = cr.user_to_device( extents[0], extents[1]) device_x2, device_y2 = cr.user_to_device( extents[2], extents[3]) @device_extents = [device_x1, device_y1, device_x2, device_y2].map {|f| f.to_i} cr.fill() cr.restore() end def set_shape(cr, reshape) return if RbConfig::CONFIG['host_os'] =~ /mswin|mingw|cygwin/ if @region.nil? or reshape if @device_extents.nil? @region = Pix.surface_to_region(cr.target.map_to_image) else @region = Pix.surface_to_region_with_hints(cr.target.map_to_image, @device_extents) end else dx, dy = @__surface_position.zip(@prev_position).map {|new, prev| new - prev} @region.translate!(dx, dy) end @prev_position = @__surface_position if @supports_alpha input_shape_combine_region(nil) input_shape_combine_region(@region) else shape_combine_region(nil) shape_combine_region(@region) end end end class TransparentApplicationWindow < Gtk::ApplicationWindow def initialize(application) super(application) set_decorated(false) set_app_paintable(true) set_focus_on_map(false) if composited? input_shape_combine_region(Cairo::Region.new) # empty region end signal_connect("screen-changed") do |widget, old_screen| screen_changed(widget, :old_screen => old_screen) next true end screen_changed(self) end def screen_changed(widget, old_screen: nil) if composited? set_visual(screen.rgba_visual) else set_visual(screen.system_visual) Logging::Logging.debug("screen does NOT support alpha.\n") end fail "assert" unless not visual.nil? maximize # not fullscreen end end def self.get_png_size(path) return 0, 0 if not File.exists?(path) buf = case File.extname(path) when '.dgp' get_DGP_IHDR(path) when '.ddp' get_DDP_IHDR(path) else get_png_IHDR(path) end fail "assert" unless buf[0] == 137.chr # png format # XXX != "\x89" fail "assert" unless buf[1..7] == "PNG\r\n\x1a\n" # png format fail "assert" unless buf[12..15] == "IHDR" # name of the first chunk in a PNG datastream w = buf[16, 4] h = buf[20, 4] width = ((w[0].ord << 24) + (w[1].ord << 16) + (w[2].ord << 8) + w[3].ord) height = ((h[0].ord << 24) + (h[1].ord << 16) + (h[2].ord << 8) + h[3].ord) return width, height end def self.get_png_lastpix(path) return nil if not File.exists?(path) pixbuf = pixbuf_new_from_file(path) fail "assert" unless [3, 4].include?(pixbuf.n_channels) fail "assert" unless pixbuf.bits_per_sample == 8 '#%02x%02x%02x' % [pixbuf.pixels[-3].ord, pixbuf.pixels[-2].ord, pixbuf.pixels[-1].ord] end def self.get_DGP_IHDR(path) head, tail = File.split(path) # XXX filename = tail m_half = Digest::MD5.hexdigest(filename[0..filename.length/2-1]) m_full = Digest::MD5.hexdigest(filename) tmp = [m_full, filename].join('') key = '' j = 0 for i in 0..tmp.length-1 value = (tmp[i].ord ^ m_half[j].ord) break if value.zero? key << value.chr j += 1 j = 0 if j >= m_half.length end key_length = key.length if key_length.zero? # not encrypted Logging::Logging.warning([filename, ' generates a null key.'].join('')) return get_png_IHDR(path) end key = [key[1..-1], key[0]].join('') key_pos = 0 buf = '' f = File.open(path, 'rb') for i in 0..23 c = f.read(1) buf << (c[0].ord ^ key[key_pos].ord).chr key_pos += 1 key_pos = 0 if key_pos >= key_length end return buf end def self.get_DDP_IHDR(path) size = File.size(path) key = (size << 2) buf = "" f = File.open(path, 'rb') for i in 0..23 c = f.read(1) key = ((key * 0x08088405 + 1) & 0xffffffff) buf << ((c[0].ord ^ key >> 24) & 0xff).chr end return buf end def self.get_png_IHDR(path) File.open(path, 'rb') {|f| f.read(24) } end def self.pixbuf_new_from_file(path) GdkPixbuf::Pixbuf.new(:file => path) end def self.surface_new_from_file(path) Cairo::ImageSurface.from_png(path) end def self.create_icon_pixbuf(path) begin pixbuf = pixbuf_new_from_file(path) rescue # compressed icons are not supported. :-( return nil end pixbuf.scale(16, 16, GdkPixbuf::InterpType::BILINEAR) end def self.create_blank_surface(width, height) Cairo::ImageSurface.new(Cairo::FORMAT_ARGB32, width, height) end def self.create_pixbuf_from_DGP_file(path) head, tail = File.split(path) # XXX filename = tail m_half = Digest::MD5.hexdigest(filename[0..filename.length/2-1]) m_full = Digest::MD5.hexdigest(filename) tmp = [m_full, filename].join('') key = '' j = 0 for i in 0..tmp.length-1 value = (tmp[i].ord ^ m_half[j].ord) break if value.zero? key << value.chr j += 1 j = 0 if j >= m_half.length end key_length = key.length if key_length.zero? # not encrypted Logging::Logging.warning([filename, ' generates a null key.'].join('')) pixbuf = pixbuf_new_from_file(filename) return pixbuf end key = [key[1..-1], key[0]].join('') key_pos = 0 loader = Gdk::PixbufLoader.new('png') f = File.open(path, 'rb') while true c = f.read(1) break if c.nil? # EOF loader.write((c[0].ord ^ key[key_pos].ord).chr) key_pos += 1 key_pos = 0 if key_pos >= key_length end pixbuf = loader.pixbuf loader.close() return pixbuf end def self.create_pixbuf_from_DDP_file(path) f = File.open(path, 'rb') buf = f.read() key = buf.length << 2 loader = Gdk::PixbufLoader.new('png') for i in 0..buf.length-1 key = ((key * 0x08088405 + 1) & 0xffffffff) loader.write(((buf[i].ord ^ key >> 24) & 0xff).chr) end pixbuf = loader.pixbuf loader.close() return pixbuf end def self.create_surface_from_file(path, is_pnr: true, use_pna: false) pixbuf = create_pixbuf_from_file(path, :is_pnr => is_pnr, :use_pna => use_pna) surface = Cairo::ImageSurface.new(Cairo::FORMAT_ARGB32, pixbuf.width, pixbuf.height) cr = Cairo::Context.new(surface) cr.set_source_pixbuf(pixbuf, 0, 0) cr.set_operator(Cairo::OPERATOR_SOURCE) cr.paint() return surface end def self.create_pixbuf_from_file(path, is_pnr: false, use_pna: false) head = File.dirname(path) basename = File.basename(path, '.*') pixbuf = case File.extname(path) when '.dgp' create_pixbuf_from_DGP_file(path) when '.ddp' create_pixbuf_from_DDP_file(path) else pixbuf_new_from_file(path) end # Currently cannot get a pointer to the actual pixels with # the pixels method. Temporary use the read_pixel_bytes method and # create another Pixbuf. if is_pnr pixels = pixbuf.read_pixel_bytes.to_s unless pixbuf.has_alpha? r, g, b = pixels[0, 3].bytes pixbuf = pixbuf.add_alpha(true, r, g, b) else pix_na = NArray.to_na(pixels, NArray::INT) rgba = pix_na[0] pix_na[pix_na.eq rgba] = 0 pixbuf = GdkPixbuf::Pixbuf.new( :bytes => pix_na.to_s, :has_alpha => true, :width => pixbuf.width, :height => pixbuf.height, :row_stride => pixbuf.rowstride) end end if use_pna path = File.join(head, basename + '.pna') if File.exists?(path) pna_pixbuf = pixbuf_new_from_file(path) pix_na = NArray.to_na(pixbuf.read_pixel_bytes.to_s, NArray::BYTE) pix_na.reshape!(4, pix_na.size / 4) unless pna_pixbuf.has_alpha? pna_pixbuf = pna_pixbuf.add_alpha(false, 0, 0, 0) end pna_na = NArray.to_na(pna_pixbuf.read_pixel_bytes.to_s, NArray::BYTE) pna_na.reshape!(4, pna_na.size / 4) pix_na[3, true] = pna_na[0, true] pixbuf = GdkPixbuf::Pixbuf.new( :bytes => pix_na.to_s, :has_alpha => true, :width => pixbuf.width, :height => pixbuf.height, :row_stride => pixbuf.rowstride) end end return pixbuf end end ninix-aya-5.0.9/lib/ninix/keymap.rb0000644000175000017500000002346213416507430015332 0ustar shyshy# -*- coding: utf-8 -*- # # Copyright (C) 2002 by Tamito KAJIYAMA # Copyright (C) 2003-2019 by Shyouzou Sugitani # # This program is free software; you can redistribute it and/or modify it # under the terms of the GNU General Public License (version 2) as # published by the Free Software Foundation. It is distributed in the # hope that it will be useful, but WITHOUT ANY WARRANTY; without even the # implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR # PURPOSE. See the GNU General Public License for more details. # require "gtk3" module Keymap Keymap_old = { Gdk::Keyval::KEY_BackSpace => 'back', Gdk::Keyval::KEY_Tab => 'tab', Gdk::Keyval::KEY_KP_Tab => 'tab', Gdk::Keyval::KEY_Clear => 'clear', Gdk::Keyval::KEY_Return => 'return', Gdk::Keyval::KEY_KP_Enter => 'return', Gdk::Keyval::KEY_Menu => '', Gdk::Keyval::KEY_Pause => 'pause', Gdk::Keyval::KEY_Kanji => '', Gdk::Keyval::KEY_Escape => 'escape', Gdk::Keyval::KEY_Henkan => '', Gdk::Keyval::KEY_Muhenkan => '', Gdk::Keyval::KEY_space => 'space', Gdk::Keyval::KEY_Prior => 'prior', Gdk::Keyval::KEY_Next => 'next', Gdk::Keyval::KEY_End => 'end', Gdk::Keyval::KEY_Home => 'home', Gdk::Keyval::KEY_Left => 'left', Gdk::Keyval::KEY_Up => 'up', Gdk::Keyval::KEY_Right => 'right', Gdk::Keyval::KEY_Down => 'down', Gdk::Keyval::KEY_Select => '', Gdk::Keyval::KEY_Print => '', Gdk::Keyval::KEY_Execute => '', Gdk::Keyval::KEY_Insert => '', Gdk::Keyval::KEY_Delete => 'delete', Gdk::Keyval::KEY_Help => '', Gdk::Keyval::KEY_0 => '0', Gdk::Keyval::KEY_1 => '1', Gdk::Keyval::KEY_2 => '2', Gdk::Keyval::KEY_3 => '3', Gdk::Keyval::KEY_4 => '4', Gdk::Keyval::KEY_5 => '5', Gdk::Keyval::KEY_6 => '6', Gdk::Keyval::KEY_7 => '7', Gdk::Keyval::KEY_8 => '8', Gdk::Keyval::KEY_9 => '9', Gdk::Keyval::KEY_a => 'a', Gdk::Keyval::KEY_b => 'b', Gdk::Keyval::KEY_c => 'c', Gdk::Keyval::KEY_d => 'd', Gdk::Keyval::KEY_e => 'e', Gdk::Keyval::KEY_f => 'f', Gdk::Keyval::KEY_g => 'g', Gdk::Keyval::KEY_h => 'h', Gdk::Keyval::KEY_i => 'i', Gdk::Keyval::KEY_j => 'j', Gdk::Keyval::KEY_k => 'k', Gdk::Keyval::KEY_l => 'l', Gdk::Keyval::KEY_m => 'm', Gdk::Keyval::KEY_n => 'n', Gdk::Keyval::KEY_o => 'o', Gdk::Keyval::KEY_p => 'p', Gdk::Keyval::KEY_q => 'q', Gdk::Keyval::KEY_r => 'r', Gdk::Keyval::KEY_s => 's', Gdk::Keyval::KEY_t => 't', Gdk::Keyval::KEY_u => 'u', Gdk::Keyval::KEY_v => 'v', Gdk::Keyval::KEY_w => 'w', Gdk::Keyval::KEY_x => 'x', Gdk::Keyval::KEY_y => 'y', Gdk::Keyval::KEY_z => 'z', Gdk::Keyval::KEY_KP_0 => '0', Gdk::Keyval::KEY_KP_1 => '1', Gdk::Keyval::KEY_KP_2 => '2', Gdk::Keyval::KEY_KP_3 => '3', Gdk::Keyval::KEY_KP_4 => '4', Gdk::Keyval::KEY_KP_5 => '5', Gdk::Keyval::KEY_KP_6 => '6', Gdk::Keyval::KEY_KP_7 => '7', Gdk::Keyval::KEY_KP_8 => '8', Gdk::Keyval::KEY_KP_9 => '9', Gdk::Keyval::KEY_KP_Multiply => '*', Gdk::Keyval::KEY_KP_Add => '+', Gdk::Keyval::KEY_KP_Separator => '', Gdk::Keyval::KEY_KP_Subtract => '-', Gdk::Keyval::KEY_KP_Decimal => '', Gdk::Keyval::KEY_KP_Divide => '/', Gdk::Keyval::KEY_F1 => 'f1', Gdk::Keyval::KEY_F2 => 'f2', Gdk::Keyval::KEY_F3 => 'f3', Gdk::Keyval::KEY_F4 => 'f4', Gdk::Keyval::KEY_F5 => 'f5', Gdk::Keyval::KEY_F6 => 'f6', Gdk::Keyval::KEY_F7 => 'f7', Gdk::Keyval::KEY_F8 => 'f8', Gdk::Keyval::KEY_F9 => 'f9', Gdk::Keyval::KEY_F10 => 'f10', Gdk::Keyval::KEY_F11 => 'f11', Gdk::Keyval::KEY_F12 => 'f12', Gdk::Keyval::KEY_F13 => 'f13', Gdk::Keyval::KEY_F14 => 'f14', Gdk::Keyval::KEY_F15 => 'f15', Gdk::Keyval::KEY_F16 => 'f16', Gdk::Keyval::KEY_F17 => 'f17', Gdk::Keyval::KEY_F18 => 'f18', Gdk::Keyval::KEY_F19 => 'f19', Gdk::Keyval::KEY_F20 => 'f20', Gdk::Keyval::KEY_F21 => 'f21', Gdk::Keyval::KEY_F22 => 'f22', Gdk::Keyval::KEY_F23 => 'f23', Gdk::Keyval::KEY_F24 => 'f24', Gdk::Keyval::KEY_Num_Lock => '', Gdk::Keyval::KEY_Scroll_Lock => '', Gdk::Keyval::KEY_Shift_L => '', Gdk::Keyval::KEY_Shift_R => '', Gdk::Keyval::KEY_Control_L => '', Gdk::Keyval::KEY_Control_R => '', } Keymap_new = { Gdk::Keyval::KEY_BackSpace => '8', Gdk::Keyval::KEY_Tab => '9', Gdk::Keyval::KEY_KP_Tab => '9', Gdk::Keyval::KEY_Clear => '12', Gdk::Keyval::KEY_Return => '13', Gdk::Keyval::KEY_KP_Enter => '13', Gdk::Keyval::KEY_Menu => '18', Gdk::Keyval::KEY_Pause => '19', Gdk::Keyval::KEY_Kanji => '25', Gdk::Keyval::KEY_Escape => '27', Gdk::Keyval::KEY_Henkan => '28', Gdk::Keyval::KEY_Muhenkan => '29', Gdk::Keyval::KEY_space => '32', Gdk::Keyval::KEY_Prior => '33', Gdk::Keyval::KEY_Next => '34', Gdk::Keyval::KEY_End => '35', Gdk::Keyval::KEY_Home => '36', Gdk::Keyval::KEY_Left => '37', Gdk::Keyval::KEY_Up => '38', Gdk::Keyval::KEY_Right => '39', Gdk::Keyval::KEY_Down => '40', Gdk::Keyval::KEY_Select => '41', Gdk::Keyval::KEY_Print => '42', Gdk::Keyval::KEY_Execute => '43', Gdk::Keyval::KEY_Insert => '45', Gdk::Keyval::KEY_Delete => '46', Gdk::Keyval::KEY_Help => '47', Gdk::Keyval::KEY_0 => '48', Gdk::Keyval::KEY_1 => '49', Gdk::Keyval::KEY_2 => '50', Gdk::Keyval::KEY_3 => '51', Gdk::Keyval::KEY_4 => '52', Gdk::Keyval::KEY_5 => '53', Gdk::Keyval::KEY_6 => '54', Gdk::Keyval::KEY_7 => '55', Gdk::Keyval::KEY_8 => '56', Gdk::Keyval::KEY_9 => '57', Gdk::Keyval::KEY_a => '65', Gdk::Keyval::KEY_b => '66', Gdk::Keyval::KEY_c => '67', Gdk::Keyval::KEY_d => '68', Gdk::Keyval::KEY_e => '69', Gdk::Keyval::KEY_f => '70', Gdk::Keyval::KEY_g => '71', Gdk::Keyval::KEY_h => '72', Gdk::Keyval::KEY_i => '73', Gdk::Keyval::KEY_j => '74', Gdk::Keyval::KEY_k => '75', Gdk::Keyval::KEY_l => '76', Gdk::Keyval::KEY_m => '77', Gdk::Keyval::KEY_n => '78', Gdk::Keyval::KEY_o => '79', Gdk::Keyval::KEY_p => '80', Gdk::Keyval::KEY_q => '81', Gdk::Keyval::KEY_r => '82', Gdk::Keyval::KEY_s => '83', Gdk::Keyval::KEY_t => '84', Gdk::Keyval::KEY_u => '85', Gdk::Keyval::KEY_v => '86', Gdk::Keyval::KEY_w => '87', Gdk::Keyval::KEY_x => '88', Gdk::Keyval::KEY_y => '89', Gdk::Keyval::KEY_z => '90', Gdk::Keyval::KEY_KP_0 => '96', Gdk::Keyval::KEY_KP_1 => '97', Gdk::Keyval::KEY_KP_2 => '98', Gdk::Keyval::KEY_KP_3 => '99', Gdk::Keyval::KEY_KP_4 => '100', Gdk::Keyval::KEY_KP_5 => '101', Gdk::Keyval::KEY_KP_6 => '102', Gdk::Keyval::KEY_KP_7 => '103', Gdk::Keyval::KEY_KP_8 => '104', Gdk::Keyval::KEY_KP_9 => '105', Gdk::Keyval::KEY_KP_Multiply => '106', Gdk::Keyval::KEY_KP_Add => '107', Gdk::Keyval::KEY_KP_Separator => '108', Gdk::Keyval::KEY_KP_Subtract => '109', Gdk::Keyval::KEY_KP_Decimal => '110', Gdk::Keyval::KEY_KP_Divide => '111', Gdk::Keyval::KEY_F1 => '112', Gdk::Keyval::KEY_F2 => '113', Gdk::Keyval::KEY_F3 => '114', Gdk::Keyval::KEY_F4 => '115', Gdk::Keyval::KEY_F5 => '116', Gdk::Keyval::KEY_F6 => '117', Gdk::Keyval::KEY_F7 => '118', Gdk::Keyval::KEY_F8 => '119', Gdk::Keyval::KEY_F9 => '120', Gdk::Keyval::KEY_F10 => '121', Gdk::Keyval::KEY_F11 => '122', Gdk::Keyval::KEY_F12 => '123', Gdk::Keyval::KEY_F13 => '124', Gdk::Keyval::KEY_F14 => '125', Gdk::Keyval::KEY_F15 => '126', Gdk::Keyval::KEY_F16 => '127', Gdk::Keyval::KEY_F17 => '128', Gdk::Keyval::KEY_F18 => '129', Gdk::Keyval::KEY_F19 => '130', Gdk::Keyval::KEY_F20 => '131', Gdk::Keyval::KEY_F21 => '132', Gdk::Keyval::KEY_F22 => '133', Gdk::Keyval::KEY_F23 => '134', Gdk::Keyval::KEY_F24 => '135', Gdk::Keyval::KEY_Num_Lock => '144', Gdk::Keyval::KEY_Scroll_Lock => '145', Gdk::Keyval::KEY_Shift_L => '160', Gdk::Keyval::KEY_Shift_R => '161', Gdk::Keyval::KEY_Control_L => '162', Gdk::Keyval::KEY_Control_R => '163', } end ninix-aya-5.0.9/lib/ninix/logging.rb0000644000175000017500000000255613416507430015473 0ustar shyshy# -*- coding: utf-8 -*- # # Copyright (C) 2015-2019 by Shyouzou Sugitani # # This program is free software; you can redistribute it and/or modify it # under the terms of the GNU General Public License (version 2) as # published by the Free Software Foundation. It is distributed in the # hope that it will be useful, but WITHOUT ANY WARRANTY; without even the # implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR # PURPOSE. See the GNU General Public License for more details. # require "logger" module Logging class Logging @@logger = [] @@logger << Logger.new(STDOUT) @@level = Logger::WARN def initialize ##@@logger.level = Logger::WARN end def self.set_level(level) @@logger.each do |logger| logger.level = level end @@level = level end def self.add_logger(logger) @@logger << logger logger.level = @@level end def self.info(message) @@logger.each do |logger| logger.info(message) end end def self.error(message) @@logger.each do |logger| logger.error(message) end end def self.warning(message) @@logger.each do |logger| logger.warn(message) end end def self.debug(message) @@logger.each do |logger| logger.debug(message) end end end end ninix-aya-5.0.9/lib/ninix/script.rb0000644000175000017500000003414613416507430015351 0ustar shyshy# -*- coding: utf-8 -*- # # script.rb - a Sakura Script parser # Copyright (C) 2001, 2002 by Tamito KAJIYAMA # Copyright (C) 2004-2019 by Shyouzou Sugitani # # This program is free software; you can redistribute it and/or modify it # under the terms of the GNU General Public License (version 2) as # published by the Free Software Foundation. It is distributed in the # hope that it will be useful, but WITHOUT ANY WARRANTY; without even the # implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR # PURPOSE. See the GNU General Public License for more details. # module Script TOKEN_TAG = 1 TOKEN_META = 2 TOKEN_OPENED_SBRA = 3 TOKEN_CLOSED_SBRA = 4 TOKEN_NUMBER = 5 TOKEN_STRING = 6 SCRIPT_TAG = 1 SCRIPT_TEXT = 2 TEXT_META = 1 TEXT_STRING = 2 class ParserError < StandardError attr_writer :script def initialize( error: 'strict', script: nil, src: nil, column: nil, length: nil, skip: nil) super() unless ['strict', 'loose'].include?(error) fail ValueError('unknown error scheme: ' + error.to_s) end @error = error @script = (script or []) @src = (src or '') @column = column @length = (length or 0) @skip = (skip or 0) end def get_item if @error == 'strict' done = [] else done = @script end if @error == 'strict' or @column.nil? script = '' else script = @src[@column + @skip, @src.length] end return done, script end def format unless @column.nil? column = @column unless @src.empty? dump = [@src[0..@column-1], "\x1b[7m", (@src[column, @length] or ' '), "\x1b[m", @src[column+@length..@src.length-1]].join('') else dump = '' end else column = '??' dump = @src end return 'ParserError: column ' + column.to_s + ': ' + message + "\n" + dump end end class Parser def initialize(error: 'strict') unless ['strict', 'loose'].include?(error) fail ArgumentError('unknown error scheme: ' + error.to_s) end @error = error end def perror(position: 'column', skip: nil) unless ['column', 'eol'].include?(position) fail ArgumentError('unknown position scheme: ', position.to_s) end unless ['length', 'rest', nil].include?(skip) fail ArgumentError('unknown skip scheme: ', skip.to_s) end if position == 'column' column = @column length = @length case skip when 'length' skip = length when 'rest' skip = @src[column, @src.length].length else skip = 0 end else column = @src.length length = 0 skip = 0 end return ParserError.new( \ :error => @error, \ :script => @script, \ :src => @src, \ :column => column, \ :length => length, \ :skip => skip \ ) end def tokenize(s) patterns = [ [TOKEN_TAG, Regexp.new(/\\[Cehunjcxtqzy*v0123456789fmia!&+-]|\\[sbp][0-9]?|\\w[0-9]|\\_[wqslvVbe+cumna]|\\__[ct]|\\URL/)], [TOKEN_META, Regexp.new(/%month|%day|%hour|%minute|%second|%username|%selfname2?|%keroname|%friendname|%songname|%screen(width|height)|%exh|%et|%m[szlchtep?]|%dms|%j|%c|%wronghour|%\*/)], [TOKEN_NUMBER, Regexp.new(/[0-9]+/)], [TOKEN_OPENED_SBRA, Regexp.new(/\[/)], [TOKEN_CLOSED_SBRA, Regexp.new(/\]/)], [TOKEN_STRING, Regexp.new(/(\\\\|\\%|\\\]|[^\\\[\]%0-9])+/)], [TOKEN_STRING, Regexp.new(/[%\\]/)], ] tokens = [] pos = 0 end_ = s.length while pos < end_ for token, pattern in patterns match = pattern.match(s, pos) break if not match.nil? and match.begin(0) == pos end fail RuntimeError('should not reach here') if match.nil? tokens << [token, match[0]] pos = match.end(0) end return tokens end def next_token begin token, lexeme = @tokens.shift rescue IndexError raise perror(:position => 'eol'), 'unexpected end of script' end return "", "" if token.nil? @column += @length @length = lexeme.length return token, lexeme end def parse(s) return [] if s.nil? or s.empty? # tokenize the script @src = s @tokens = tokenize(@src) @column = 0 @length = 0 # parse the sequence of tokens @script = [] text = [] string_chunks = [] scope = 0 anchor = nil while not @tokens.empty? token, lexeme = next_token() if token == TOKEN_STRING and lexeme == '\\' unless string_chunks.empty? text << [TEXT_STRING, string_chunks.join('')] end unless text.empty? @script << [SCRIPT_TEXT, text, @column] end fail perror(:skip => 'length'), 'unknown tag' elsif token == TOKEN_STRING and lexeme == '%' string_chunks << lexeme text << [TEXT_STRING, string_chunks.join('')] @script << [SCRIPT_TEXT, text, @column] fail perror(:skip => 'length'), 'unknown meta string' return [] end if [TOKEN_NUMBER, TOKEN_OPENED_SBRA, TOKEN_STRING, TOKEN_CLOSED_SBRA].include?(token) lexeme = lexeme.gsub('\\\\', '\\') lexeme = lexeme.gsub('\\%', '%') string_chunks << lexeme next end unless string_chunks.empty? text << [TEXT_STRING, string_chunks.join('')] string_chunks = [] end if token == TOKEN_META if lexeme == '%j' argument = read_sbra_id() text << [TEXT_META, lexeme, argument] elsif lexeme == '%*' unless text.empty? @script << [SCRIPT_TEXT, text, @column] text = [] end @script << [SCRIPT_TAG, "\\!", [[TEXT_STRING, '*'],], @column] else text << [TEXT_META, lexeme] end next end unless text.empty? @script << [SCRIPT_TEXT, text, @column] text = [] end if ["\\a", "\\c", "\\e", "\\t", "\\_e", "\\v", "\\y", "\\z", "\\_q", "\\4", "\\5", "\\6", "\\7", "\\2", "\\*", "\\-", "\\+", "\\_+", "\\_n", "\\_V", "\\__c", "\\__t", "\\C"].include?(lexeme) @script << [SCRIPT_TAG, lexeme, @column] elsif ["\\0", "\\h"].include?(lexeme) @script << [SCRIPT_TAG, lexeme, @column] scope = 0 elsif ["\\1", "\\u"].include?(lexeme) @script << [SCRIPT_TAG, lexeme, @column] scope = 1 elsif ["\\s", "\\b", "\\p"].include?(lexeme) argument = read_sbra_id() @script << [SCRIPT_TAG, lexeme, argument, @column] elsif lexeme.start_with?("\\s") or \ lexeme.start_with?("\\b") or \ lexeme.start_with?("\\p") or \ lexeme.start_with?("\\w") num = lexeme[2] if lexeme.start_with?("\\s") and scope == 1 num = (num.to_i + 10).to_s end @script << [SCRIPT_TAG, lexeme[0, 2], num, @column] elsif ["\\_w"].include?(lexeme) argument = read_sbra_number() @script << [SCRIPT_TAG, lexeme, argument, @column] elsif ["\\i", "\\j", "\\&", "\\_u", "\\_m"].include?(lexeme) argument = read_sbra_id() @script << [SCRIPT_TAG, lexeme, argument, @column] elsif ["\\_b", "\\_c", "\\_l", "\\_v", "\\m", "\\3", "\\8", "\\9"].include?(lexeme) argument = read_sbra_text() @script << [SCRIPT_TAG, lexeme, argument, @column] elsif ["\\n", "\\x"].include?(lexeme) if not @tokens.empty? and @tokens[0][0] == TOKEN_OPENED_SBRA argument = read_sbra_text() @script << [SCRIPT_TAG, lexeme, argument, @column] else @script << [SCRIPT_TAG, lexeme, @column] end elsif ["\\URL"].include?(lexeme) buf = [read_sbra_text()] while not @tokens.empty? and @tokens[0][0] == TOKEN_OPENED_SBRA buf << read_sbra_text() buf << read_sbra_text() end @script << [SCRIPT_TAG, lexeme] + buf + [@column, ] elsif ["\\!"].include?(lexeme) args = split_params(read_sbra_text()) @script << [SCRIPT_TAG, lexeme] + args + [@column, ] elsif ["\\q"].include?(lexeme) if not @tokens.empty? and @tokens[0][0] == TOKEN_OPENED_SBRA args = split_params(read_sbra_text()) if args.length != 2 fail perror(:skip => 'length'), 'wrong number of arguments' return [] end if args[1].length != 1 or args[1][0][1].empty? fail perror(:skip => 'length'), 'syntax error (expected an ID)' return [] end arg1 = args[0] arg2 = args[1][0][1] @script << [SCRIPT_TAG, lexeme, arg1, arg2, @column] else arg1 = read_number() arg2 = read_sbra_id() arg3 = read_sbra_text() @script << [SCRIPT_TAG, lexeme, arg1, arg2, arg3, @column] end elsif ["\\_s"].include?(lexeme) if not @tokens.empty? and @tokens[0][0] == TOKEN_OPENED_SBRA args = [] for arg in split_params(read_sbra_text()) args << arg[0][1] end @script << [SCRIPT_TAG, lexeme] + args + [@column, ] else @script << [SCRIPT_TAG, lexeme, @column] end elsif ["\\_a"].include?(lexeme) if anchor.nil? anchor = perror(:skip => 'rest') @script << [SCRIPT_TAG, lexeme, read_sbra_id(), @column] else anchor = nil @script << [SCRIPT_TAG, lexeme, @column] end elsif ["\\f"].include?(lexeme) args = [] for arg in split_params(read_sbra_text()) args << arg[0][1] end @script << [SCRIPT_TAG, lexeme] + args + [@column, ] else fail perror(:skip => 'length'), 'unknown tag (' + lexeme + ')' return [] end end unless anchor.nil? if @script[-1][0, 2] == [SCRIPT_TAG, '\e'] @script.insert(@script.length - 1, [SCRIPT_TAG, '\_a', @script[-1][2]]) else @script << [SCRIPT_TAG, '\_a', @column] end anchor.script = @script fail anchor, 'syntax error (unbalanced \_a tag)' end unless string_chunks.empty? text << [TEXT_STRING, string_chunks.join('')] end unless text.empty? @script << [SCRIPT_TEXT, text, @column] end return @script end def read_number token, number = next_token() fail perror, 'syntax error (expected a number)' if token != TOKEN_NUMBER return number end def read_sbra_number token, lexeme = next_token() fail perror, 'syntax error (expected a square bracket)' if token != TOKEN_OPENED_SBRA token, number = next_token() fail perror(:skip => 'length'), 'syntax error (expected a number)' if token != TOKEN_NUMBER token, lexeme = next_token() fail perror(:skip => 'length'), 'syntax error (expected a square bracket)' if token != TOKEN_CLOSED_SBRA return number end def read_sbra_id text = read_sbra_text() if text.length != 1 fail perror(:skip => 'length'), 'syntax error (expected a single ID)' return [] end begin sbra_id = Integer(text[0][1]).to_s rescue # pass else return sbra_id end return text[0][1] end def read_sbra_text token, lexeme = next_token() fail perror, 'syntax error (expected a square bracket)' if token != TOKEN_OPENED_SBRA text = [] string_chunks = [] close_flag = false while not @tokens.empty? token, lexeme = next_token() if [TOKEN_NUMBER, TOKEN_STRING, TOKEN_OPENED_SBRA, TOKEN_TAG].include?(token) lexeme = lexeme.gsub('\\', '\\') lexeme = lexeme.gsub('\%', '%') lexeme = lexeme.gsub('\]', ']') string_chunks << lexeme next end unless string_chunks.empty? text << [TEXT_STRING, string_chunks.join('')] string_chunks = [] end case token when TOKEN_CLOSED_SBRA close_flag = true break when TOKEN_META text << [TEXT_META, lexeme] else fail perror(:skip => 'length'), 'syntax error (wrong type of argument)' return [] end end fail perror(:position => 'eol'), 'unexpected end of script' unless close_flag return text end def split_params(text) re_param = Regexp.new(/("[^"]*"|[^,])*/) re_quote = Regexp.new(/"([^"]*)"/) params = [] buf = [] for token, lexeme in text i = 0 j = lexeme.length if token == TEXT_STRING while i < j match = re_param.match(lexeme, i) break if match.nil? or match.begin(0) != i param = re_quote.match(match[0]) unless param.nil? param = (param.pre_match + param[1] + param.post_match) else param = match[0] end unless param.nil? and buf.empty? buf << [token, param] end i = match.end(0) if i < j fail "assert" unless lexeme[i] == ',' params << buf buf = [] i += 1 end end end if i < j buf << [token, lexeme[i, lexeme.length]] end end unless buf.empty? params << buf end return params end end end ninix-aya-5.0.9/lib/ninix/seriko.rb0000644000175000017500000005617113416507430015343 0ustar shyshy# -*- coding: utf-8 -*- # # Copyright (C) 2002 by Tamito KAJIYAMA # Copyright (C) 2002, 2003 by MATSUMURA Namihiko # Copyright (C) 2002-2019 by Shyouzou Sugitani # # This program is free software; you can redistribute it and/or modify it # under the terms of the GNU General Public License (version 2) as # published by the Free Software Foundation. It is distributed in the # hope that it will be useful, but WITHOUT ANY WARRANTY; without even the # implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR # PURPOSE. See the GNU General Public License for more details. # require "gtk3" require_relative "metamagic" require_relative "logging" module Seriko class Controller < MetaMagic::Holon DEFAULT_FPS = 30.0 # current default def initialize(seriko) super("") # FIXME @seriko = seriko @exclusive_actor = nil @base_id = nil @timeout_id = nil reset_overlays() @queue = [] @fps = DEFAULT_FPS @next_tick = 0 @prev_tick = 0 # XXX @active = [] @move = nil @dirty = true end def set_base_id(window, surface_id) case surface_id when '-2' terminate(window) @base_id = window.get_surface_id when '-1' @base_id = window.get_surface_id else @base_id = surface_id end @dirty = true end def get_base_id() @base_id end def move_surface(xoffset, yoffset) @move = [xoffset, yoffset] end def append_actor(frame, actor) @active << [frame, actor] end def update_frame(window) frame, actor = get_actor_next(window) last_actor = actor while not actor.nil? actor.update(window, frame) last_actor = actor frame, actor = get_actor_next(window) end if not last_actor.nil? and last_actor.exclusive? and \ last_actor.terminate? and @exclusive_actor.nil? # XXX invoke_restart(window) end end def get_actor_next(window) unless @active.empty? @active.sort! {|x| x[0]} # (key=lambda {|x| return x[0]}) return @active.shift if @active[0][0] <= @next_tick end return nil, nil end def update(window) ## FIXME: use GLib.get_monotonic_time current_tick = (Time.now.to_f * 1000000).to_i # [microsec] quality = @parent.handle_request('GET', 'get_preference', 'animation_quality') @fps = DEFAULT_FPS * quality if @prev_tick.zero? ## First time delta_tick = (1000.0 / @fps) # [msec] else delta_tick = ((current_tick - @prev_tick) / 1000) # [msec] end @next_tick += delta_tick @prev_tick = current_tick update_frame(window) window.update_frame_buffer() if @dirty @dirty = false window.move_surface(*@move) if not @move.nil? @move = nil @timeout_id = GLib::Timeout.add((1000.0 / @fps).to_i) { update(window) } # [msec] return false end def lock_exclusive(window, actor) fail "assert" unless @exclusive_actor.nil? terminate(window) @exclusive_actor = actor actor.set_post_proc( ->(w, a) { unlock_exclusive(w, a) }, [window, actor]) @dirty = true # XXX end def unlock_exclusive(window, actor) fail "assert" unless @exclusive_actor == actor @exclusive_actor = nil end def reset_overlays() @overlays = {} @dirty = true end def remove_overlay(actor) @dirty = true unless @overlays.delete(actor).nil? end def add_overlay(window, actor, surface_id, x, y, method) case surface_id when '-2' terminate(window) remove_overlay(actor) return when '-1' remove_overlay(actor) return end @overlays[actor] = [surface_id, x, y, method] @dirty = true end def invoke_actor(window, actor) if not @exclusive_actor.nil? interval = actor.get_interval() return if interval.start_with?('talk') or interval == 'yen-e' @queue << actor return end lock_exclusive(window, actor) if actor.exclusive? actor.invoke(window, @next_tick) end def invoke(window, actor_id, update: 0) return unless @seriko.include?(@base_id) for actor in @seriko[@base_id] if actor_id == actor.get_id() invoke_actor(window, actor) break end end end def invoke_yen_e(window, surface_id) return unless @seriko.include?(surface_id) for actor in @seriko[surface_id] if actor.get_interval() == 'yen-e' invoke_actor(window, actor) break end end end def invoke_talk(window, surface_id, count) return false unless @seriko.include?(surface_id) interval_count = nil for actor in @seriko[surface_id] interval = actor.get_interval() if interval.start_with?('talk') interval_count = interval[5].to_i # XXX break end end if not interval_count.nil? and count >= interval_count invoke_actor(window, actor) return true else return false end end def invoke_runonce(window) return unless @seriko.include?(@base_id) for actor in @seriko[@base_id] if actor.get_interval() == 'runonce' invoke_actor(window, actor) end end end def invoke_always(window) return unless @seriko.include?(@base_id) for actor in @seriko[@base_id] interval = actor.get_interval() if ['always', 'sometimes', 'rarely'].include?(interval) or \ interval.start_with?('random') or \ interval.start_with?('periodic') invoke_actor(window, actor) end end end def invoke_restart(window) return unless @seriko.include?(@base_id) for actor in @seriko[@base_id] if @queue.include?(actor) @queue.remove(actor) invoke_actor(window, actor) end end end def invoke_kinoko(window) # XXX return unless @seriko.include?(@base_id) for actor in @seriko[@base_id] if ['always', 'runonce', 'sometimes', 'rarely',].include?(actor.get_interval()) invoke_actor(window, actor) end end end def reset(window, surface_id) @queue = [] terminate(window) @next_tick = 0 @prev_tick = 0 # XXX set_base_id(window, window.get_surface_id) if @seriko.include?(surface_id) @base_id = surface_id else @base_id = window.get_surface_id end @dirty = true # XXX end def start(window) invoke_runonce(window) invoke_always(window) GLib::Source.remove(@timeout_id) if not @timeout_id.nil? @timeout_id = GLib::Timeout.add((1000.0 / @fps).to_i) { update(window) } # [msec] end def terminate(window) if @seriko.include?(@base_id) for actor in @seriko[@base_id] actor.terminate() end end reset_overlays() @active = [] @move = nil @dirty = true end def stop_actor(actor_id) return unless @seriko.include?(@base_id) for actor in @seriko[@base_id] if actor.get_id() == actor_id actor.terminate() end end end def destroy() GLib::Source.remove(@timeout_id) unless @timeout_id.nil? @timeout_id = nil end def iter_overlays() actors = @overlays.keys() temp = [] for actor in actors temp << [actor.get_id(), actor] end actors = temp actors.sort! temp = [] for actor_id, actor in actors temp << actor end actors = temp result = [] for actor in actors surface_id, x, y, method = @overlays[actor] ##Logging::Logging.debug( ## 'actor=' + actor.get_id().to_s + ## ', id=' + surface_id.to_s + ## ', x=' + x.to_s + ## ', y=' + y.to_s) result << [surface_id, x, y, method] end return result end end class Actor def initialize(actor_id, interval) @id = actor_id @interval = interval @patterns = [] @last_method = nil @exclusive = 0 @post_proc = nil @terminate_flag = true end def terminate? @terminate_flag end def exclusive? @exclusive.zero? ? false : true end def set_post_proc(post_proc, args) fail "assert" unless @post_proc.nil? @post_proc = [post_proc, args] end def set_exclusive() @exclusive = 1 end def get_id() @id end def get_interval() @interval end def get_patterns() @patterns end def add_pattern(surface, interval, method, args) @patterns << [surface, interval, method, args] end def invoke(window, base_frame) @terminate_flag = false end def update(window, base_frame) return false if @terminate_flag end def terminate() @terminate_flag = true unless @post_proc.nil? post_proc, args = @post_proc @post_proc = nil post_proc.call(*args) end end def get_surface_ids() surface_ids = [] for surface, interval, method, args in @patterns if method == 'base' surface_ids << surface end end return surface_ids end OVERLAY_SET = ['overlay', 'overlayfast', 'interpolate', 'reduce', 'replace', 'asis'] def show_pattern(window, surface, method, args) if OVERLAY_SET.include?(@last_method) window.remove_overlay(self) end case method when 'move' window.get_seriko.move_surface(args[0], args[1]) when *OVERLAY_SET window.add_overlay(self, surface, args[0], args[1], method) when 'base' window.get_seriko.set_base_id(window, surface) when 'start' window.invoke(args[0], :update => 1) when 'alternativestart' window.invoke(args.sample, :update => 1) when 'stop' window.get_seriko.stop_actor(args[0]) when 'alternativestop' window.get_seriko.stop_actor(args.sample) else fail RuntimeError('should not reach here') end @last_method = method end end class ActiveActor < Actor # always def initialize(actor_id, interval) super(actor_id, interval) @wait = 0 @pattern = 0 end def invoke(window, base_frame) terminate() @terminate_flag = false @pattern = 0 update(window, base_frame) end def update(window, base_frame) return false if @terminate_flag if @pattern.zero? @surface_id = window.get_surface() end surface, interval, method, args = @patterns[@pattern] @pattern += 1 if @pattern == @patterns.length @pattern = 0 end show_pattern(window, surface, method, args) window.append_actor(base_frame + interval, self) return false end end class RandomActor < Actor # sometimes, rarely, random, periodic def initialize(actor_id, interval, wait_min, wait_max) super(actor_id, interval) @wait_min = wait_min @wait_max = wait_max reset() end def reset() @wait = rand(@wait_min..@wait_max) @pattern = 0 end def invoke(window, base_frame) terminate() @terminate_flag = false reset() window.append_actor(base_frame + @wait, self) end def update(window, base_frame) return false if @terminate_flag if @pattern.zero? @surface_id = window.get_surface() end surface, interval, method, args = @patterns[@pattern] @pattern += 1 if @pattern < @patterns.length @wait = interval else reset() end show_pattern(window, surface, method, args) window.append_actor(base_frame + @wait, self) return false end end class OneTimeActor < Actor # runone def initialize(actor_id, interval) super(actor_id, interval) @wait = -1 @pattern = 0 end def invoke(window, base_frame) terminate() @terminate_flag = false @wait = 0 @pattern = 0 update(window, base_frame) end def update(window, base_frame) return false if @terminate_flag if @pattern.zero? @surface_id = window.get_surface() end surface, interval, method, args = @patterns[@pattern] @pattern += 1 if @pattern < @patterns.length @wait = interval else @wait = -1 # done terminate() end show_pattern(window, surface, method, args) if @wait >= 0 window.append_actor(base_frame + @wait, self) end return false end end class PassiveActor < Actor # never, yen-e, talk def initialize(actor_id, interval) super(actor_id, interval) @wait = -1 end def invoke(window, base_frame) terminate() @terminate_flag = false @wait = 0 @pattern = 0 update(window, base_frame) end def update(window, base_frame) return false if @terminate_flag if @pattern.zero? @surface_id = window.get_surface() end surface, interval, method, args = @patterns[@pattern] @pattern += 1 if @pattern < @patterns.length @wait = interval else @wait = -1 # done terminate() end show_pattern(window, surface, method, args) if @wait >= 0 window.append_actor(base_frame + @wait, self) end return false end end class Mayuna < Actor def set_exclusive() end def show_pattern(window, surface, method, args) end end def self.get_actors(config, version: 1) re_seriko_interval = Regexp.new('\A([0-9]+)interval\z') re_seriko_interval_value = Regexp.new('\A(sometimes|rarely|random,[0-9]+|always|runonce|yesn-e|talk,[0-9]+|never)\z') re_seriko_pattern = Regexp.new('\A([0-9]+|-[12])\s*,\s*([+-]?[0-9]+)\s*,\s*(overlay|overlayfast|base|move|start|alternativestart|)\s*,?\s*([+-]?[0-9]+)?\s*,?\s*([+-]?[0-9]+)?\s*,?\s*(\[[0-9]+(\.[0-9]+)*\])?\z') re_seriko2_interval = Regexp.new('\Aanimation([0-9]+)\.interval\z') re_seriko2_interval_value = Regexp.new('\A(sometimes|rarely|random,[0-9]+|periodic,[0-9]+|always|runonce|yesn-e|talk,[0-9]+|never)\z') re_seriko2_pattern = Regexp.new('\A(overlay|overlayfast|interpolate|reduce|replace|asis|base|move|start|alternativestart|stop|alternativestop)\s*,\s*([0-9]+|-[12])?\s*,?\s*([+-]?[0-9]+)?\s*,?\s*([+-]?[0-9]+)?\s*,?\s*([+-]?[0-9]+)?\s*,?\s*(\([0-9]+([\.\,][0-9]+)*\))?\z') buf = [] for key, value in config.each_entry if version == 1 match = re_seriko_interval.match(key) elsif version == 2 match = re_seriko2_interval.match(key) else return [] ## should not reach here end next if match.nil? next if version == 1 and not re_seriko_interval_value.match(value) next if version == 2 and not re_seriko2_interval_value.match(value) buf << [match[1].to_i, value] end actors = [] for actor_id, interval in buf if interval == 'always' actor = Seriko::ActiveActor.new(actor_id, interval) elsif interval == 'sometimes' actor = Seriko::RandomActor.new(actor_id, interval, 0, 10000) # 0 to 10 seconds elsif interval == 'rarely' actor = Seriko::RandomActor.new(actor_id, interval, 20000, 60000) elsif interval.start_with?('random') actor = Seriko::RandomActor.new(actor_id, interval, 0, 1000 * interval[7, interval.length - 1].to_i) elsif interval.start_with?('periodic') actor = Seriko::RandomActor.new(actor_id, interval, 1000 * interval[9, interval.length - 1].to_i, 1000 * interval[9, interval.length - 1].to_i) elsif interval == 'runonce' actor = Seriko::OneTimeActor.new(actor_id, interval) elsif interval == 'yen-e' actor = Seriko::PassiveActor.new(actor_id, interval) elsif interval.start_with?('talk') actor = Seriko::PassiveActor.new(actor_id, interval) elsif interval == 'never' actor = Seriko::PassiveActor.new(actor_id, interval) end if version == 1 key = (actor_id.to_s + 'option') else key = ('animation' + actor_id.to_s + '.option') end if config.include?(key) and config[key] == 'exclusive' actor.set_exclusive() end begin for n in 0..127 # up to 128 patterns (0 - 127) if version == 1 key = (actor_id.to_s + 'pattern' + n.to_s) else key = ('animation' + actor_id.to_s + '.pattern' + n.to_s) end unless config.include?(key) key = (actor_id.to_s + 'patturn' + n.to_s) # only for version 1 unless config.include?(key) key = (actor_id.to_s + 'putturn' + n.to_s) # only for version 1 next unless config.include?(key) # XXX end end pattern = config[key] if version == 1 match = re_seriko_pattern.match(pattern) else match = re_seriko2_pattern.match(pattern) end fail ("unsupported pattern: #{pattern}") if match.nil? if version == 1 surface = match[1].to_i.to_s interval = (match[2].to_i.abs * 10) method = match[3] else method = match[1] if match[2].nil? surface = 0 else surface = match[2].to_i.to_s end if match[3].nil? interval = 0 else interval = match[3].to_i.abs end end if method == '' method = 'base' end if ['start', 'stop'].include?(method) if version == 2 group = match[2] surface = -1 # XXX else group = match[4] end if group.nil? fail ('syntax error: ' + pattern) end args = [group.to_i] elsif ['alternativestart', 'alternativestop'].include?(method) args = match[6] if args.nil? fail ('syntax error: ' + pattern) end t = [] for x in args[1, args.length - 2].split('.', 0) for y in x.split(',', 0) t << y end end args = [] for s in t args << s.to_i end else if ['-1', '-2'].include?(surface) x = 0 y = 0 else if match[4].nil? x = 0 else x = match[4].to_i end if match[5].nil? y = 0 else y = match[5].to_i end end args = [x, y] end actor.add_pattern(surface, interval, method, args) end rescue => e Logging::Logging.error('seriko.rb: ' + e.message) next end if actor.get_patterns().empty? Logging::Logging.error( 'seriko.rb: animation group #' + actor_id.to_s + ' has no pattern (ignored)') next end actors << actor end temp = [] for actor in actors temp << [actor.get_id(), actor] end actors = temp actors.sort! temp = [] for actor_id, actor in actors temp << actor end actors = temp return actors end def self.get_mayuna(config) re_mayuna_interval = Regexp.new('\A([0-9]+)interval\z') re_mayuna_interval_value = Regexp.new('\A(bind)\z') re_mayuna_pattern = Regexp.new('\A([0-9]+|-[12])\s*,\s*([0-9]+)\s*,\s*(bind|add|reduce|insert)\s*,?\s*([+-]?[0-9]+)?\s*,?\s*([+-]?[0-9]+)?\s*,?\s*(\[[0-9]+(\.[0-9]+)*\])?\z') re_mayuna2_interval = Regexp.new('\Aanimation([0-9]+)\.interval\z') re_mayuna2_interval_value = Regexp.new('\A(bind)\z') re_mayuna2_pattern = Regexp.new('\A(bind|add|reduce|insert)\s*,\s*([0-9]+|-[12])\s*,\s*([0-9]+)\s*,?\s*([+-]?[0-9]+)?\s*,?\s*([+-]?[0-9]+)?\s*,?\s*(\([0-9]+(\.[0-9]+)*\))?\z') version = nil buf = [] for key, value in config.each_entry case version when 1 match = re_mayuna_interval.match(key) when 2 match = re_mayuna2_interval.match(key) else match1 = re_mayuna_interval.match(key) match2 = re_mayuna2_interval.match(key) if not match1.nil? version = 1 match = match1 elsif not match2.nil? version = 2 match = match2 else next end end next if match.nil? next if version == 1 and not re_mayuna_interval_value.match(value) next if version == 2 and not re_mayuna2_interval_value.match(value) buf << [match[1].to_i, value] end mayuna = [] for mayuna_id, interval in buf ##fail "assert" unless interval == 'bind' actor = Seriko::Mayuna.new(mayuna_id, interval) begin for n in 0..127 # up to 128 patterns (0 - 127) if version == 1 key = (mayuna_id.to_s + 'pattern' + n.to_s) else key = ('animation' + mayuna_id.to_s + '.pattern' + n.to_s) end if not config.include?(key) key = (mayuna_id.to_s + 'patturn' + n.to_s) # only for version 1 if not config.include?(key) next # XXX end end pattern = config[key] if version == 1 match = re_mayuna_pattern.match(pattern) else match = re_mayuna2_pattern.match(pattern) end if match.nil? fail ('unsupported pattern: ' + pattern) end if version == 1 surface = match[1].to_i.to_s interval = (match[2].to_i.abs * 10) method = match[3] else method = match[1] surface = match[2].to_i.to_s interval = match[3].to_i.abs end if not ['bind', 'add', 'reduce', 'insert'].include?(method) next else if ['-1', '-2'].include?(surface) x = 0 y = 0 else if match[4].nil? x = 0 else x = match[4].to_i end if match[5].nil? y = 0 else y = match[5].to_i end end args = [x, y] end actor.add_pattern(surface, interval, method, args) end rescue => e Logging::Logging.error('seriko.rb: ' + e.message) next end if actor.get_patterns().empty? Logging::Logging.error('seriko.rb: animation group #' + mayuna_id.to_s + ' has no pattern (ignored)') next end mayuna << actor end temp = [] for actor in mayuna temp << [actor.get_id(), actor] end mayuna = temp mayuna.sort! temp = [] for actor_id, actor in mayuna temp << actor end mayuna = temp return mayuna end end ninix-aya-5.0.9/lib/ninix/update.rb0000644000175000017500000003517513416507430015332 0ustar shyshy# -*- coding: utf-8 -*- # # Copyright (C) 2001, 2002 by Tamito KAJIYAMA # Copyright (C) 2002, 2003 by MATSUMURA Namihiko # Copyright (C) 2002-2019 by Shyouzou Sugitani # # This program is free software; you can redistribute it and/or modify it # under the terms of the GNU General Public License (version 2) as # published by the Free Software Foundation. It is distributed in the # hope that it will be useful, but WITHOUT ANY WARRANTY; without even the # implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR # PURPOSE. See the GNU General Public License for more details. # require "net/http" require "uri" require "fileutils" require "digest/md5" require_relative "home" require_relative "logging" module Update class NetworkUpdate BACKUPSUFFIX = '.BACKUP' def initialize @parent = nil @event_queue = [] @state = nil @backups = [] @newfiles = [] @newdirs = [] end def set_responsible(parent) @parent = parent end def state @state end def is_active return (not @state.nil?) end def enqueue_event(event, ref0: nil, ref1: nil, ref2: nil, ref3: nil, ref4: nil, ref5: nil, ref6: nil, ref7: nil) @event_queue << [event, ref0, ref1, ref2, ref3, ref4, ref5, ref6, ref7] end def get_event return nil if @event_queue.empty? return @event_queue.shift end def has_events return (not @event_queue.empty?) end def start(homeurl, ghostdir, timeout: 60) begin url = URI.parse(homeurl) rescue enqueue_event('OnUpdateFailure', :ref0 => 'bad home URL', :ref1 => '', :ref2 => '', :ref3 => 'ghost') # XXX @state = nil return end unless url.scheme == 'http' enqueue_event('OnUpdateFailure', :ref0 => 'bad home URL', :ref1 => '', :ref2 => '', :ref3 => 'ghost') # XXX @state = nil return end @host = url.host @port = url.port @path = url.path @ghostdir = ghostdir @timeout = timeout @redirect_limit = 5 @state = 0 end def interrupt @event_queue = [] @parent.handel_request( 'NOTIFY', 'enqueue_event', 'OnUpdateFailure', 'artificial', '', '', 'ghost') # XXX @state = nil stop(:revert => true) end def stop(revert: false) @buffer = [] if revert for path in @backups File.rename(path, path[0, path.length - BACKUPSUFFIX.length]) if File.file?(path) end for path in @newfiles File.delete(path) if File.file?(path) end for path in @newdirs FileUtils.remove_entry_secure(path) if File.directory?(path) end @backups = [] end @newfiles = [] @newdirs = [] end def clean_up for path in @backups File.delete(path) if File.file?(path) end @backups = [] end def reset_timeout @timestamp = Time.now.to_i end def check_timeout return (Time.now.to_i - @timestamp) > @timeout end def run len_state = 5 len_pre = 5 if @state.nil? or \ @parent.handle_request('GET', 'check_event_queue') return false elsif @state == 0 start_updates() elsif @state == 1 connect() elsif @state == 2 wait_response() elsif @state == 3 get_content() elsif @state == 4 @schedule = make_schedule() if @schedule.nil? return false end @final_state = (@schedule.length * len_state + len_pre) elsif @state == @final_state end_updates() elsif (@state - len_pre) % len_state == 0 filename, checksum = @schedule[0] Logging::Logging.info('UPDATE: ' + filename + ' ' + checksum) download(File.join(@path, URI.escape(filename)), :event => true) elsif (@state - len_pre) % len_state == 1 connect() elsif (@state - len_pre) % len_state == 2 wait_response() elsif (@state - len_pre) % len_state == 3 get_content() elsif (@state - len_pre) % len_state == 4 filename, checksum = @schedule.shift update_file(filename, checksum) end return true end def start_updates enqueue_event('OnUpdateBegin', :ref0 => @parent.handle_request('GET', 'get_name', :default => ''), :ref1 => @path, :ref2 => '', :ref3 => 'ghost') # XXX download(File.join(@path, 'updates2.dau')) end def download(locator, event: false) @locator = locator # don't use URI.escape here @http = Net::HTTP.new(@host, @port) if event enqueue_event('OnUpdate.OnDownloadBegin', :ref0 => File.basename(locator), :ref1 => @file_number, :ref2 => @num_files, :ref3 => 'ghost') # XXX end @state += 1 reset_timeout() end def connect @response = @http.get(@locator) @state += 1 reset_timeout() end def wait_response if check_timeout() enqueue_event('OnUpdateFailure', :ref0 => 'timeout', :ref1 => '', :ref2 => '', :ref3 => 'ghost') # XXX @state = nil stop(:revert => true) return end code = @response.code.to_i message = @response.message if code == 200 #pass elsif code == 302 if redirect() return else enqueue_event('OnUpdateFailure', :ref0 => 'http redirect error', :ref1 => '', :ref2 => '', :ref3 => 'ghost') # XXX @state = nil return end elsif @state == 2 # updates2.dau enqueue_event('OnUpdateFailure', :ref0 => code.to_s, :ref1 => 'updates2.dau', :ref2 => '', :ref3 => 'ghost') # XXX @state = nil return else filename, checksum = @schedule.shift Logging::Logging.error( "failed to download #{filename} (#{code} #{message})") @file_number += 1 @state += 3 return end @buffer = [] @size = @response.content_length @state += 1 reset_timeout() @redirect_limit = 5 # reset end def redirect @redirect_limit -= 1 return false if @redirect_limit < 0 location = @response['location'] return false if location.nil? begin url = URI.parse(location) rescue return false end return false if url.scheme != 'http' return false if url.path.empty? Logging::Logging.info("redirected to #{location}") @http.finish if @http.started? @host = url.host @port = url.port @path = url.path @state -= 2 download(@path) return true end def get_content data = @response.read_body if data.empty? if check_timeout() enqueue_event('OnUpdateFailure', :ref0 => 'timeout', :ref1 => '', :ref2 => '', :ref3 => 'ghost') # XXX @state = nil stop(:revert => true) return elsif data.nil? return end elsif @response.code != '200' enqueue_event('OnUpdateFailure', :ref0 => 'data retrieval failed', :ref1 => '', :ref2 => '', :ref3 => 'ghost') # XXX @state = nil stop(:revert => true) return end @buffer = data unless data.empty? if @size.nil? or data.length < @size enqueue_event('OnUpdateFailure', :ref0 => 'timeout', :ref1 => '', :ref2 => '', :ref3 => 'ghost') # XXX @state = nil stop(:revert => true) return end @state += 1 end def adjust_path(filename) filename = Home.get_normalized_path(filename) if ['install.txt', 'delete.txt', 'readme.txt', 'thumbnail.png'].include?(filename) or File.dirname(filename) != "." return filename end return File.join('ghost', 'master', filename) end def make_schedule schedule = parse_updates2_dau() unless schedule.nil? @num_files = (schedule.length - 1) @file_number = 0 list = [] for x, y in schedule list << x end update_list = list.join(',') if @num_files >= 0 enqueue_event('OnUpdateReady', :ref0 => @num_files, :ref1 => update_list, :ref2 => '', :ref3 => 'ghost') # XXX end @state += 1 end return schedule end def get_schedule @schedule end def parse_updates2_dau schedule = [] for line in @buffer.split("\n", 0) begin filename, checksum, newline = line.split("\001", 4) rescue enqueue_event('OnUpdateFailure', :ref0 => 'broken updates2.dau', :ref1 => 'updates2.dau', :ref2 => '', :ref3 => 'ghost') # XXX @state = nil return nil end next if filename == "" unless checksum.nil? checksum = checksum.encode('ascii', :invalid => :replace, :undef => :replace) # XXX end path = File.join(@ghostdir, adjust_path(filename)) begin f = open(path, 'rb') data = f.read() f.close() rescue # IOError # does not exist or broken data = nil end unless data.nil? next if checksum == Digest::MD5.hexdigest(data) end schedule << [filename, checksum] end @updated_files = [] return schedule end def update_file(filename, checksum) enqueue_event('OnUpdate.OnMD5CompareBegin', :ref0 => filename, :ref1 => '', :ref2 => '', :ref3 => 'ghost') # XXX data = @buffer digest = Digest::MD5.hexdigest(data) if digest == checksum path = File.join(@ghostdir, adjust_path(filename)) subdir = File.dirname(path) unless Dir.exists?(subdir) subroot = subdir while true head, tail = File.split(subroot) if Dir.exists?(head) break else subroot = head end end @newdirs << subroot begin FileUtils.mkdir_p(subdir) rescue SystemCallError enqueue_event('OnUpdateFailure', :ref0 => ["can't mkdir ", subdir].join(''), :ref1 => path, :ref2 => '', :ref3 => 'ghost') # XXX @state = nil stop(:revert => true) return end end if File.exists?(path) if File.file?(path) backup = [path, BACKUPSUFFIX].join('') File.rename(path, backup) @backups << backup end else @newfiles << path end begin f = open(path, 'wb') begin f.write(data) rescue # IOError, SystemCallError enqueue_event('OnUpdateFailure', :ref0 => ["can't write ", File.basename(path)].join(''), :ref1 => path, :ref2 => '', :ref3 => 'ghost') # XXX @state = nil stop(:revert => true) return end rescue # IOError enqueue_event('OnUpdateFailure', :ref0 => ["can't open ", File.basename(path)].join(''), :ref1 => path, :ref2 => '', :ref3 => 'ghost') # XXX @state = nil stop(:revert => true) return end @updated_files << filename event = 'OnUpdate.OnMD5CompareComplete' else event = 'OnUpdate.OnMD5CompareFailure' enqueue_event(event, :ref0 => filename, :ref1 => checksum, :ref2 => digest, :ref3 => 'ghost') # XXX @state = nil stop(:revert => true) return end enqueue_event(event, :ref0 => filename, :ref1 => checksum, :ref2 => digest) @file_number += 1 @state += 1 end def end_updates filelist = parse_delete_txt() unless filelist.empty? for filename in filelist path = File.join(@ghostdir, filename) if File.exists?(path) and File.file?(path) begin File.unlink(path) Logging::Logging.info('deleted ' + path) rescue SystemCallError => e Logging::Logging.error(e.message) end end end end list = [] for x in @updated_files list << x end update_list = list.join(',') if update_list.empty? enqueue_event('OnUpdateComplete', :ref0 => 'none', :ref1 => '', :ref2 => '', :ref3 => 'ghost') # XXX else enqueue_event('OnUpdateComplete', :ref0 => 'changed', :ref1 => update_list, :ref2 => '', :ref3 => 'ghost') # XXX end @state = nil stop() end def parse_delete_txt filelist = [] begin f = open(File.join(@ghostdir, 'delete.txt'), 'rb') for line in f line = line.strip() next if line.empty? filename = line filelist << Home.get_normalized_path(filename) end rescue # IOError return nil end return filelist end end end ninix-aya-5.0.9/lib/ninix/metamagic.rb0000644000175000017500000000616713416507430015776 0ustar shyshy# -*- coding: utf-8 -*- # # metamagic.rb - unknown unknowns # Copyright (C) 2011-2019 by Shyouzou Sugitani # # This program is free software; you can redistribute it and/or modify it # under the terms of the GNU General Public License (version 2) as # published by the Free Software Foundation. It is distributed in the # hope that it will be useful, but WITHOUT ANY WARRANTY; without even the # implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR # PURPOSE. See the GNU General Public License for more details. # module MetaMagic class Meme def initialize(key) @key = key @baseinfo = nil @menuitem = nil @parent = nil @handlers = {} end def set_responsible(parent) @parent = parent end def handle_request(event_type, event, *arglist) fail "assert" unless ['GET', 'NOTIFY'].include?(event_type) unless @handlers.include?(event) if self.class.method_defined?(event) result = method(event).call(*arglist) else result = @parent.handle_request( event_type, event, *arglist) end else result = method(@handlers[event]).call(*arglist) end return result if event_type == 'GET' end def create_menuitem(data) nil end def delete_by_myself end def key @key end def key=(data) # read only end def baseinfo @baseinfo end def baseinfo=(data) @baseinfo = data @menuitem = create_menuitem(data) delete_by_myself if menuitem.nil? end def menuitem @menuitem end def menuitem=(data) # read only end end class Holon attr_reader :baseinfo, :instance def initialize(key) @key = key @baseinfo = nil @menuitem = nil @instance = nil @parent = nil end def set_responsible(parent) @parent = parent end def handle_request(event_type, event, *arglist) fail "assert" unless ['GET', 'NOTIFY'].include?(event_type) unless @handlers.include?(event) if self.class.method_defined?(event) result = method(event).call(*arglist) else result = @parent.handle_request( event_type, event, *arglist) end else result = method(@handlers[event]).call(*arglist) end return result if event_type == 'GET' end def create_menuitem(data) nil end def delete_by_myself end def create_instance(data) nil end def key=(data) # read only end def key @key end def baseinfo # forbidden nil end def baseinfo=(data) @baseinfo = data @instance = create_instance(data) if @instance.nil? if @instance.nil? delete_by_myself() else @instance.new_(*data) # reset @menuitem = create_menuitem(data) delete_by_myself if menuitem.nil? end end def menuitem @menuitem end def menuitem=(data) # read only end def instance @instance end def instance=(data) # read only end end end ninix-aya-5.0.9/lib/ninix/kinoko.rb0000644000175000017500000003231713416507430015335 0ustar shyshy# -*- coding: utf-8 -*- # # Copyright (C) 2004-2019 by Shyouzou Sugitani # # This program is free software; you can redistribute it and/or modify it # under the terms of the GNU General Public License (version 2) as # published by the Free Software Foundation. It is distributed in the # hope that it will be useful, but WITHOUT ANY WARRANTY; without even the # implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR # PURPOSE. See the GNU General Public License for more details. # # TODO: # - きのことサーフェスの間に他のウインドウが入ることができてしまうのを直す. # - NAYUKI/2.0 # - 透明度の設定 require 'gettext' require "gtk3" require_relative "config" require_relative "seriko" require_relative "pix" require_relative "logging" module Kinoko class Menu include GetText bindtextdomain("ninix-aya") def initialize(accelgroup) @parent = nil @__menu_list = {} @__popup_menu = Gtk::Menu.new item = Gtk::MenuItem.new(:label => _('Settings...(_O)'), :use_underline => true) item.signal_connect('activate') do |a, b| @parent.handle_request('NOTIFY', 'edit_preferences') end @__popup_menu.add(item) @__menu_list['settings'] = item item = Gtk::MenuItem.new(:label => _('Skin(_K)'), :use_underline => true) @__popup_menu.add(item) @__menu_list['skin'] = item item = Gtk::MenuItem.new(:label => _('Exit(_Q)'), :use_underline => true) item.signal_connect('activate') do |a, b| @parent.handle_request('NOTIFY', 'close') end @__popup_menu.add(item) @__menu_list['exit'] = item @__popup_menu.show_all end def set_responsible(parent) @parent = parent end def popup() skin_list = @parent.handle_request('GET', 'get_skin_list') set_skin_menu(skin_list) @__popup_menu.popup_at_pointer(nil) end def set_skin_menu(list) key = 'skin' unless list.empty? menu = Gtk::Menu.new for skin in list item = Gtk::MenuItem.new(:label => skin['title']) item.signal_connect('activate', skin) do |a, k| @parent.handle_request('NOTIFY', 'select_skin', k) next true end menu.add(item) item.show() end @__menu_list[key].set_submenu(menu) menu.show() @__menu_list[key].show() else @__menu_list[key].hide() end end end class Nayuki def initialize() end end class Kinoko def initialize(skin_list) @skin_list = skin_list @skin = nil end def edit_preferences() end def finalize() @__running = false @target.detach_observer(self) @skin.destroy() unless @skin.nil? end def observer_update(event, args) return if @skin.nil? case event when 'set position', 'set surface' @skin.set_position() @skin.show() @skin.reset_z_order() when 'set scale' scale = @target.get_surface_scale() @skin.set_scale(scale) when 'hide' side = args[0] @skin.hide() if side.zero? # sakura side when 'iconified' @skin.hide() when 'deiconified' @skin.show() @skin.reset_z_order() when 'finalize' finalize() when 'move surface' side, xoffset, yoffset = args @skin.set_position(:xoffset => xoffset, :yoffset => yoffset) if side.zero? # sakura side @skin.reset_z_order() when 'raise' side = args[0] @skin.set_position() if side.zero? # sakura side @skin.reset_z_order() else Logging::Logging.debug('OBSERVER(kinoko): ignore - ' + event) end end def load_skin() scale = @target.get_surface_scale() @skin = Skin.new(@accelgroup) @skin.set_responsible(self) @skin.load(@data, scale) end def handle_request(event_type, event, *arglist) fail "assert" unless ['GET', 'NOTIFY'].include?(event_type) handlers = { 'get_target_window' => lambda { return @target.get_target_window }, # XXX 'get_kinoko_position' => lambda {|a| return @target.get_kinoko_position(a) } } if handlers.include?(event) result = handlers[event].call(*arglist) else if Kinoko.method_defined?(event) result = method(event).call(*arglist) else result = nil end end return result if event_type == 'GET' end def load(data, target) @data = data @target = target @target.attach_observer(self) @accelgroup = Gtk::AccelGroup.new() load_skin() return 0 if @skin.nil? send_event('OnKinokoObjectCreate') @__running = true GLib::Timeout.add(10) { do_idle_tasks } # 10[ms] return 1 end def do_idle_tasks() @__running end def close() finalize() send_event('OnKinokoObjectDestroy') end def send_event(event) return unless ['OnKinokoObjectCreate', 'OnKinokoObjectDestroy', 'OnKinokoObjectChanging', 'OnKinokoObjectChanged', 'OnKinokoObjectInstalled'].include?(event) ## 'OnBatteryLow', 'OnBatteryCritical', ## 'OnSysResourceLow', 'OnSysResourceCritical' args = [@data['title'], @data['ghost'], @data['category']] @target.notify_event(event, *args) end def get_skin_list() @skin_list end def select_skin(args) send_event('OnKinokoObjectChanging') @skin.destroy() @data = args load_skin() return 0 if @skin.nil? send_event('OnKinokoObjectChanged') return 1 end end class Skin def initialize(accelgroup) @frame_buffer = [] @accelgroup = accelgroup @parent = nil @__menu = Menu.new(@accelgroup) @__menu.set_responsible(self) end def get_seriko @seriko end def set_responsible(parent) @parent = parent end def handle_request(event_type, event, *arglist) fail "assert" unless ['GET', 'NOTIFY'].include?(event_type) handlers = { } if handlers.include?(event) result = handlers[event].call # no argument else if Skin.method_defined?(event) result = method(event).call(*arglist) else result = @parent.handle_request(event_type, event, *arglist) end end return result if event_type == 'GET' end def load(data, scale) @data = data @__scale = scale @__shown = false @surface_id = 0 # dummy @window = Pix::TransparentWindow.new() ##@window.set_title(['surface.', name].join('')) @window.set_skip_taskbar_hint(true) @window.signal_connect('delete_event') do |w, e| delete(w, e) next true end @window.add_accel_group(@accelgroup) unless @data['animation'].nil? path = File.join(@data['dir'], @data['animation']) actors = {'' => Seriko.get_actors(NConfig.create_from_file(path))} else base = File.basename(@data['base'], ".*") ext = File.extname(@data['base']) path = File.join(@data['dir'], base + 'a.txt') if File.exists?(path) actors = {'' => Seriko.get_actors(NConfig.create_from_file(path))} else actors = {'' => []} end end @seriko = Seriko::Controller.new(actors) @seriko.set_responsible(self) path = File.join(@data['dir'], @data['base']) begin @reshape = true @image_surface = Pix.create_surface_from_file(path) w = [8, (@image_surface.width * @__scale / 100).to_i].max h = [8, (@image_surface.height * @__scale / 100).to_i].max rescue ## FIXME @parent.handle_request('NOTIFY', 'close') return end @path = path @w, @h = w, h @darea = @window.darea @darea.set_events(Gdk::EventMask::EXPOSURE_MASK| Gdk::EventMask::BUTTON_PRESS_MASK| Gdk::EventMask::BUTTON_RELEASE_MASK| Gdk::EventMask::POINTER_MOTION_MASK| Gdk::EventMask::LEAVE_NOTIFY_MASK) @darea.signal_connect('button_press_event') do |w, e| next button_press(w, e) end @darea.signal_connect('button_release_event') do |w, e| button_release(w, e) next true end @darea.signal_connect('motion_notify_event') do |w, e| motion_notify(w, e) next true end @darea.signal_connect('leave_notify_event') do |w, e| leave_notify(w, e) next true end @darea.signal_connect('draw') do |w, cr| redraw(w, cr) next true end set_position() show() reset_z_order() @seriko.reset(self, '') # XXX @seriko.start(self) @seriko.invoke_kinoko(self) end def get_surface_id @surface_id end def get_preference(name) # dummy return 1.0 if name == 'animation_quality' return nil end def show() @window.show_all() unless @__shown @__shown = true end def hide() @window.hide_all() if @__shown @__shown = false end def reset_z_order return unless @__shown target_window = @parent.handle_request('GET', 'get_target_window') if @data['ontop'] @window.window.restack(target_window.window, true) else target_window.window.restack(@window.window, true) end end def append_actor(frame, actor) @seriko.append_actor(frame, actor) end def set_position(xoffset: 0, yoffset: 0) base_x, base_y = @parent.handle_request('GET', 'get_kinoko_position', @data['baseposition']) a, b = [[0.5, 1], [0.5, 0], [0, 0.5], [1, 0.5], [0, 1], [1, 1], [0, 0], [1, 0], [0.5, 0.5]][@data['baseadjust']] offsetx = (@data['offsetx'] * @__scale / 100).to_i offsety = (@data['offsety'] * @__scale / 100).to_i @x = (base_x - (@w * a).to_i + offsetx + xoffset) @y = (base_y - (@h * b).to_i + offsety + yoffset) @window.move(@x, @y) end def set_scale(scale) @__scale = scale reset_surface() set_position() end def get_surface() ## FIXME return nil end def redraw(widget, cr) @window.set_surface(cr, @image_surface, @__scale) @window.set_shape(cr, @reshape) @reshape = false end def get_image_surface(surface_id) path = File.join(@data['dir'], 'surface'+ surface_id.to_s + '.png') if File.exists?(path) surface = Pix.create_surface_from_file(path) else surface = nil end return surface end def create_image_surface(surface_id) unless surface_id.nil? or surface_id.empty? surface = get_image_surface(surface_id) else surface = Pix.create_surface_from_file(@path) end return surface end def update_frame_buffer() @reshape = true # FIXME: depends on Seriko new_surface = create_image_surface(@seriko.get_base_id) raise "assert" if new_surface.nil? # draw overlays for surface_id, x, y, method in @seriko.iter_overlays() begin overlay_surface = get_image_surface(surface_id) rescue next end # overlay surface cr = Cairo::Context.new(new_surface) cr.set_source(overlay_surface, x, y) cr.mask(overlay_surface, x, y) end #@darea.queue_draw_area(0, 0, w, h) @image_surface = new_surface @darea.queue_draw() end def terminate() @seriko.terminate(self) end def add_overlay(actor, surface_id, x, y, method) @seriko.add_overlay(self, actor, surface_id, x, y, method) end def remove_overlay(actor) @seriko.remove_overlay(actor) end def move_surface(xoffset, yoffset) @window.move(@x + xoffset, @y + yoffset) end def reset_surface() @seriko.reset(self, '') # XXX path = File.join(@data['dir'], @data['base']) w, h = Pix.get_png_size(path) w = [8, (w * @__scale / 100).to_i].max h = [8, (h * @__scale / 100).to_i].max @w, @h = w, h # XXX @seriko.start(self) @seriko.invoke_kinoko(self) end def set_surface(surface_id, restart: 1) path = File.join(@data['dir'], 'surface' + surface_id.to_s + '.png') if File.exists?(path) @path = path else #@path = nil @path = File.join(@data['dir'], @data['base']) end end def invoke(actor_id, update: 0) @seriko.invoke(self, actor_id, :update => update) end def delete() @parent.handle_request('NOTIFY', 'close') end def destroy() @seriko.destroy() @window.destroy() end def button_press(widget, event) @x_root = event.x_root @y_root = event.y_root if event.event_type == Gdk::EventType::BUTTON_PRESS click = 1 else click = 2 end button = event.button if button == 3 and click == 1 @__menu.popup() end return true end def button_release(widget, event) ## FIXME end def motion_notify(widget, event) ## FIXME end def leave_notify(widget, event) ## FIXME end end end ninix-aya-5.0.9/lib/ninix/lock.rb0000644000175000017500000000126613416507430014772 0ustar shyshy# -*- coding: utf-8 -*- # # Copyright (C) 2011-2019 by Shyouzou Sugitani # # This program is free software; you can redistribute it and/or modify it # under the terms of the GNU General Public License (version 2) as # published by the Free Software Foundation. It is distributed in the # hope that it will be useful, but WITHOUT ANY WARRANTY; without even the # implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR # PURPOSE. See the GNU General Public License for more details. # module Lock def self.lockfile(fileobj) fileobj.flock(File::LOCK_EX | File::LOCK_NB) end def self.unlockfile(fileobj) fileobj.flock(File::LOCK_UN) end end ninix-aya-5.0.9/bin/0000755000175000017500000000000013416507430012365 5ustar shyshyninix-aya-5.0.9/bin/ninix.in0000644000175000017500000000015213416507430014040 0ustar shyshy#!/bin/sh export LD_LIBRARY_PATH=@so_path:${LD_LIBRARY_PATH} exec @ruby @libdir/ninix_main.rb ${1+"$@"} ninix-aya-5.0.9/SAORI0000644000175000017500000000642513416507430012424 0ustar shyshy----------------------------- SAORI互換モジュール for ninix ----------------------------- これは何か ----------  ゴーストに機能を追加する汎用的な方法として、SAORI APIに準拠した モジュールが数多く公開されており、多くのゴーストで採用されています。 SAORIモジュールはWindowsのDLLとして提供されているため、ninix上で動作 させることはできません。そこで、ninix-ayaではSAORI互換モジュールを 搭載することにより、これらのゴーストの動作を再現しています。 設定 ---- 音声ファイル再生はGStreamerがそのファイル形式の再生に対応していれば 設定は必要ありません. それ以外のモジュールは特に設定の必要はありません.  現在、以下の互換モジュールが収録されています。 mciaudio.rb ----------- MIY氏が開発されている「MCIAUDIO」と互換のモジュールです。 音声ファイルの再生が可能になります. mciaudior.rb ------------ umeici氏が開発されている「MCIAUDIOR」と互換のモジュールです。 音声ファイルの再生が可能になります. bln.rb ------ umeici氏が開発されている「easyballoon」と互換のモジュールです。 ゴースト独自の追加バルーンを表示できます。 textcopy.rb ----------- 橋本孔明氏が開発されている「textcopy.dll」と互換のモジュールです。 文字列をクリップボードにコピーする機能を提供します。 hanayu.rb --------- りゅう氏が開発されている「花柚」と互換のモジュールです。 ゴーストの過去一週間の起動時間をグラフ表示します。 wmove.rb -------- tmizu氏が開発されている「wmove.dll」と互換のモジュールです。 サーフェスを移動させたり位置情報を取得する機能を提供します。 saori_cpuid.rb -------------- 七瀬いーうぃ氏が開発されている「saori_cpuid.dll」と互換のモジュールです。 OS情報を取得する機能を提供します。 ssu.rb ------ 櫛ケ浜やぎ氏が開発されている「ssu.dll」と互換のモジュールです。 osuwari.rb -------- ukiya氏が開発されている「osuwari.dll」と互換のモジュールです。 ゴーストをウィンドウに「お座り」させる機能を提供します。 httpc.rb -------- 櫛ヶ浜やぎ氏が開発されている「httpc.dll」と互換のモジュールです。 Web上の各種データをHTTPで取得する機能を提供します。 gomi.rb -------- ふるごむ氏が開発されている「gomi.dll」と互換のモジュールです。 ごみ箱を空にする機能を提供します。 連絡先 ------ この資料の著作権と文責は、 さくらのにえ 杉谷 が所有しています。ご連絡は、メールまたはwebサイト http://osdn.jp/projects/ninix-aya/ 上のフォーラムへお願いします。 リンク ------ SAORI collection http://umeici.onjn.jp/etcetera/saoricollect.html umeici氏提供の総合リンク集です。 ninix-aya開発プロジェクト http://ninix-aya.sourceforge.jp/ (当ソフトウェア配布サイト) http://osdn.jp/projects/ninix-aya/ 以上 ninix-aya-5.0.9/Makefile0000644000175000017500000000163413416507430013261 0ustar shyshy# # Makefile for ninix-aya # prefix = /opt/ninix-aya exec_libdir = $(prefix)/lib bindir = $(DESTDIR)$(prefix)/bin docdir = $(DESTDIR)$(prefix)/doc libdir = $(DESTDIR)$(exec_libdir) localedir = /usr/local/share/locale # system specific shiori_so_dir = $(DESTDIR)$(prefix)/lib/kawari8:$(DESTDIR)$(prefix)/lib/yaya ruby = ruby NINIX = ninix all: install: install-lib install-bin install-doc install-lib: mkdir -p $(libdir) cp -r lib/* $(libdir) mkdir -p $(localedir)/ja/LC_MESSAGES (cd po/ja ; msgfmt ninix-aya.po -o $(localedir)/ja/LC_MESSAGES/ninix-aya.mo) sed_dirs = sed -e "s,@ruby,$(ruby),g" -e "s,@libdir,$(libdir),g" -e "s,@so_path,$(shiori_so_dir),g" install-bin: mkdir -p $(bindir) $(sed_dirs) bin/ninix.in > bin/ninix install -m 755 bin/ninix $(bindir)/$(NINIX) install-doc: mkdir -p $(docdir) cp README README.ninix SAORI COPYING ChangeLog $(docdir) clean: $(RM) bin/ninix *~ ninix-aya-5.0.9/README.ninix0000644000175000017500000016762513416507430013642 0ustar shyshyninix 0.8 ========= ỳ̱ (2002ǯ712) Ϥ -------- ܥեȥϡ֤ʳβ with "Ǥ"(ʲֲ) ȸƤФ Windows ѥեȥ˻ƺ줿ǥȥåס ޥåȡץǤUnix OS (Linux, FreeBSD ʤ) X Window System Ķưޤħʲ˼ޤ o ֲѥ//Х롼б ڤƤֲѺؤǡ򤽤ΤޤѤǤޤ ޤƼﺹؤǡưڤؤ뤳ȤǤޤ o ɸ shiori.dll׸ߴ⥸塼 o shiori.dllֲ׸ߴ⥸塼 o shiori.dllΤ׸ߴ⥸塼 o shiori.dllֵ١׸ߴ⥸塼 o ȥ󥹥졼ֳĥ makoto.dll׸ߴ⥸塼 ֵ AIפȸƤФǽƼ⥸塼ˤ 饯٤ꤷޤ o Sakura Script Transfer Protocol (SSTP) б SSTP ꥯȤդƽ SSTP еǽ ƤޤSSTP ѤƥץȤդ뤳Ȥˤꡢ 饯ͳˤ٤餻뤳ȤǤޤǥǤ ʱѤǽǤ ܥեȥκǿǤϰʲξ꤫Ǥޤ http://www.geocities.co.jp/SiliconValley-Cupertino/7565/ ɬפʤ ---------- ܥեȥưˤϰʲΥեȥɬפǤ o Python (http://www.python.org/) ܥեȥγȯǤС 2.0 Ǥưǧ ƤޤС 1.5.2 2.1 ǤưȻפޤ o GTK+ (http://www.gtk.org/) X Window System Ѥ GUI 饤֥ǤС 1.2.8 Ǥ ưǧƤޤŤƤפȻפޤ o PyGTK (http://www.daa.com.au/~james/pygtk/) Python GTK+ Ѥ뤿Υ饤֥ǤС 0.6.6 ǤưǧƤޤŤСѤ ȥϳ򵯤Ȥ褦ǤǤǤ Ȥ o UnZip (Info-ZIP) o LHa for UNIX ̥եŸץǤ󥹥ȡ (ninix-install ޥ) ƤӽФƤޤUnZip 5.40 LHa 1.14i ưǧƤޤ ޤʲΥեȥѤޤ o pngtopnm, ppmquant, pnmtopng ץǤԤʤ -q (--quantize) ץ 󤬻ꤵ줿 ninix-install ޥɤǸƤӽФ ޤ-q ץȤʤפǤ 󥹥ȡˡ ---------------- ʲΤ褦 make ޥɤ¹ԤХ󥹥ȡϴλǤ # make # make install 󥹥ȡǥ쥯ȥ (prefix) /opt/ninix-0.8 ˤʤäƤ ޤɬפ˱ѤƲ: # make install prefix=/usr/local python ޥɤ̾ƱͤѹǤޤǻꤷ̾ ¹ԻˤȤޤ # make install python=/opt/bin/python2.0 󥹥ȡ뤵 ninix ޥɤ ninix-install ޥɤ̾ Ĥѿ NINIX, NINIX_INSTALL Ǥ줾ѹǤޤ: # make install NINIX=ninix-0.8 NINIX_INSTALL=ninix-install-0.8 ޤ$(prefix)/bin ǥ쥯ȥѥɲäƲ Ȥ ------ 󥹥ȡ (ninix-install ޥ) - - - - - - - - - - - - - - - - - - - ޤϹߤΥȤɤ (֥󥯽׻) ʲ Τǥ󥹥ȡ뤷ƤΤȤܥեȥˤɸ ΥȤޤޤƤʤᡢʤȤĤϥȤ򥤥 ȡ뤷ʤΥץबưޤ: # ninix-install mayura.zip ninix ȰդƤɸХ롼 ninix-balloon.lzh Ʊ ͤ˥󥹥ȡ뤷ƲɸХ롼ɬܤǤϤޤ󤬡 ʤȤĤϥХ롼򥤥󥹥ȡ뤷Ʋ: # ninix-install ninix-balloon.lzh ֥ե뤬 INSTALL/1.0 ʾбƤмưŪ˥ե (//Х롼/ץȤ4) ̤ ƽޤե̤̤ǤʤȤ顼Фϡ ʲΤ褦˥ץꤷƥե̤Ʋ: # ninix-install -g ghost1.zip ghost2.lzh (Ȥξ) # ninix-install -s shell1.zip shell2.lzh (ξ) # ninix-install -b balloon1.zip balloon2.lzh (Х롼ξ) ץ (ɲåǡ) ФƤä˥ץꤹɬ Ϥޤ󡣥ץȤɲоݤȤʤ르Ȥ򸡺Ƽư Ū˥󥹥ȡ뤷ޤоݤȤʤ르ȤʣĤ ϥ顼åϤƥ󥹥ȡߤޤ: # ninix-install kamo010902b.zip searching supplement target ... multiple candidates found ninix-20010802 sakura010925b kamo010902b.zip: try -S option with a target ghost name (skipped) ξϼΤ褦 -S (--supplement) ץꤷɲ оݤȤʤ르Ȥ򤷤Ʋ: # ninix-install -S sakura010925b kamo010902b.zip ⤷оݤȤʤ르ȤĤ⸫ĤʤСΥץ ϥȤƥ󥹥ȡ뤵ޤ ninix ѤΥץ饰 ninix-install ޥɤǥ󥹥ȡǤ ޤ: # ninix-install plugin.zip ֥եϰ٤ʣǤޤե̤ꤹ Ȥ٤Ʊե̤Ȥưޤ (̷⤬Ĥä Υ֥ե򥹥åפޤ) 㳰ȤơȤΥ֥եФ -s ץ ꤹƱΥեΤߤ򥷥Ȥƥ󥹥ȡ뤹뤳 Ǥޤֲפȡֵ١װʳε AI ѤƤ르 Υե򥷥ȤƻȤʤɤ˻ꤷƲ: # ninix-install -s mayura.zip PNG ϤΤޤޥ󥹥ȡ뤵ޤ-x (--xpm) ץ ꤹ XPM Ѵƥ󥹥ȡ뤷ޤPNG ǥ ȡ뤷ǡȤʤʤɤ˻ꤷƲ Ʊ̾Υǡ˥󥹥ȡ뤵ƤϺƿǡ 򥤥󥹥ȡ뤷ޤ-i (--interactive) ץꤹ Ⱥ˳ǧåɽޤ: # ninix-install -i mayura_v340.zip : ninix-install.py: remove "/home/kajiyama/.ninix/ghost/mayura_v315"? (yes/no) y Ϥȴ¸Υǡޤn Ϥȴ¸Υǡ ˿ǡ򥤥󥹥ȡ뤷ޤ (֥ե ̾ƱǤʤƱ̾Υǡ¸뤳ȤǤޤ) ֥ե̾ URL ꤹȼưŪ˥ե ɤƥ󥹥ȡ뤷ޤΤȤ-d (--download) ץꤹȥɤ֥ե򥢡 ֥ǥ쥯ȥ¸ޤ: # ninix-install -d http://localhost/mayura.zip ǥեȤΥ֥ǥ쥯ȥ ~/.ninix/archive ǤĶ ѿ NINIX_ARCHIVE ⤷ -A (--arcdir) ץꤹ뤳 Ȥˤꥢ֥ǥ쥯ȥѹǤޤ: # ninix-install -d -A . http://localhost/mayura.zip ޤ֥ե̾ޤ URL 1Ԥ˰ĤĵҤե (ꥹȥե) ꤹ뤳ȤǤޤʲΤ褦˥ե ̾ @ դƻꤷƲԤ # ǻϤޤ ( ) ̵뤷ޤ: # ninix-install @listfile.txt -r (--reload) ץꤹȥǡ򥤥󥹥ȡ뤷 ưΤ˼ưŪ˺ǿΥǡɤ߹ޤ뤳ȤǤޤ 1: # ninix-install -r mayura.zip 2: # ninix-install mayura.zip # ninix-install -r 󥹥ȡ¹Ԥ ~/.ninix ʲ fontrc gtkrc եưŪ˥󥹥ȡ뤷ޤƱ̾Υե뤬 ¸ߤϲ⤷ޤ󡣤ޤninix-install ޥɤ -R (--rcfiles) ץդǼ¹ԤȡƱ̾Υե뤬¸ߤ Ǥ񤭥󥹥ȡ뤷ޤˤꡢե 򸵤ξ֤᤹ȤǤޤ: # ninix-install -R ǡեեɸΥ󥹥ȡ ~/.ninix ʤäƤޤɸΥ󥹥ȡϡĶѿ NINIX_HOME -H (--homedir) ץꤹ뤳ȤˤѹǤޤ 1: # NINIX_HOME=/usr/local/share/ninix-0.8; export NINIX_HOME # ninix-install mayura.zip 2: # ninix-install -H /usr/local/share/ninix-0.8 mayura.zip ĿѤե֤ǥ쥯ȥ̾ϴĶѿ NINIX_USER -U (--userdir) ץǤǤޤλϴ ѿ NINIX_HOME -H ץλͥ褵ޤ NINIX_HOME NINIX_USER ĤδĶѿϸҤΥץ Ǥ⻲Ȥޤե (~/.bashrc, ~/.cshrc ʤ) ˴Ķѿɲä뤳Ȥ򤪴ᤷޤ ʤС 0.5 ~/.ninix ʲΥǥ쥯ȥ깽Ѥ ޤŤС򤪻Ȥäϡ~/.ninix Ȥäƥǡ򥤥󥹥ȡ뤷ľƲ ޤС 0.7.22 ǡե̾򤹤٤ƾʸѴ ƥ󥹥ȡ뤹褦ˤʤޤ¸ΥǡѤ ͽǡե̾ʸѴɬפޤ¸ ǡե̾Ѵˤ -L (--lower) ץդ ninix-install ޥɤ¹Ԥޤ: # ninix-install -L Υץ (ninix ޥ) - - - - - - - - - - - - - - - - ninix ޥɤ¹Ԥȡ󥹥ȡ뤵Ƥ르/ /Х롼ΰɤ߹Ǻǽ˸ĤäȤǵư ϰʲ̤Ǥ o ե (饯) ɥ 1å ɥʤ˻äƤ 2å ȥ˥塼򳫤 (̤) 1å ˥塼򳫤 o Х롼 (᤭Ф) ɥ 1å ɥʤ˻äƤ 2å ȥ˥塼򳫤 (̤) 1å Х롼󥦥ɥĤ o ˥塼 [ե] ͥåȥ ȤΥͥåȥԤʤ ǻؼ Ȥ˾Ǥؼ ɤ߹ ٤ƤΥǡɤľ 򳫤 Ψ ȤλΨɽޤ С ưɽСɽ λ ץλ [] Ȥڤؤ [] ڤؤ [Х롼] Х롼ڤؤ [ɽԡ] ץȤɽȤꤹ [ץ饰] ץ饰ư ninix ޥɤ -R (--raise) ץͿȤ٤ƤΥ ɥꤷԥ˰ưޤ̲˥С ФƤʤɤ˻ꤹǤ ޤtwm ʤɰΥɥޥ͡Ǥϥɥ֤ ʤ뤳Ȥޤξ -p ץդ ninix ޥɤ¹ԤƤߤƲΥץꤹ ϴϢΥ٥Ȥȯʤʤޤ (ä˳Ϥޤ) ޥɥ饤󥪥ץ --sstp-port ˤ9801֤11000ְʳ ݡȤ SSTP ꥯȤդ뤳ȤǤ褦ˤʤޤ ΥץʣǤޤޤĶѿ NINIX_SSTP_PORT ǤǽǤݡֹ򥳥ޤǶڤäƻꤷƲ 1: $ ninix --sstp-port 6809 --sstp-port 8086 2: $ NINIX_SSTP_PORT=6809,8086; export NINIX_SSTP_PORT $ ninix Ʊͤˡޥɥ饤󥪥ץ --iscp-port ˤ ISCP () ѤݡֹѹǤޤǥեͤ 9821 Ǥ ѿ NINIX_ISCP_PORT ǤǽǤ ISCP (Inter-Sakura Communication Protocol) ϡninix ͭ ץȥǤninix ʣưˡȴ֤βåǡ ꥴȸ SSTP ꥯȤޥ (ǽ˵ư ninix) ȥ졼 (2ܰʹߤ ninix) Ȥδ֤ǤȤꤹ뤿 Ѥޤ ͥåȥ - - - - - - - - - ΥȤͥåȥǽбƤ硢Τ ˥塼֥ͥåȥפ򤹤뤳Ȥˤ꼫ưŪ ȤΥǡǿǤ˹뤳ȤǤޤ˥塼 ̾ϥȤˤäѤ뤳Ȥޤ (ǥեȤϡ֥ͥ ȥפǤ) ͥåȥ򥳥ޥɥ饤ǹԤʤˤ ninix-update ɤѤޤ-l (--list) ץդƼ¹Ԥȥͥå бȤΰɽޤ: $ ninix-update -l ͥåȥԤʤˤϡȤΥۡ URL ǥ쥯ȥ̾ꤷ ninix-update ޥɤ¹Ԥޤ: $ ninix-update http://mayura.jp/update/ ~/.ninix/ghost/mayura ǻؼ - - - - - ΥȤǻؼбƤ硢Τ˥塼 ־ǻؼפ򤹤뤳ȤˤꥴȤ˾Ǥؼƥ 󥹥ȡ뤹뤳ȤǤޤ˥塼ι̾ϥȤ äѤ뤳Ȥޤ (ǥեȤϡ־ǻؼפǤ) ־ǻؼפ򤹤ȳǧΥɽޤ֤Ϥ 򲡤ȥ󥤥󥹥ȡ˿ʤߤޤȤǸΥդ ٤äƤ sakura ¦Υեɥ򺸥å ȾǻؼäơְαפȤǤޤ (unyuu ¦ Ǥϰα뤳ȤϤǤޤ)դ򤷤٤꽪 ǤƤޤޤΤդƲ - - - - - - - - å˥塼Ρפ򤹤ޤ Ǥϡninix γƼư򹥤ߤ˹碌Ƽͳꤹ 뤳ȤǤޤˤϰʲ () ٤ Ȥμȯȡ ٥ ٥Ȥȯ˴ؤ ե Х롼ѥեȤ ޥ ޥܥؤεǽγ ֥饦 ֥饦 إѡ ե̾ȳޥɤδϢդ o ֤٤ץ ֤٤פǤϡȤμȯȡΥȥˡ ꤷޤ֥¦椹פ򤹤ȼȯȡ ġΥȤȤ߹ޤƤץ˰Ǥޤ Ȥˤ뤪٤椬ޤưʤϡninix ¦ 椹פ򤷤Ʋˤϡˡ֤ ٤١פꤷޤ o ֥٥ȡץ ninix Ǥϡư佪λȤڤؤʤɤˡ֥ ٥ȡפȸƤФ뿮椬ȯޤȤ̾Υ ȤФƲ餫ȿ򼨤褦˥ץवƤޤȯ 륤٥ȡפϡ٥ȤФ르Ȥȿ ( ٤) ̵Ѥޤ㤨СOnBoot ˥å ninix εưľΥȤΤ٤뤳Ȥ Ǥޤ o ֥եȡץ ֥Х롼եȡפǤϿ᤭ФΥƥɽѤե ꤷޤեȥåȤʣΥեȤƹԤ˰ ĵҤޤե̾ %d ϼºݤΥեȥ֤ ޤǥեȤϰʲ̤Ǥ -*-helvetica-medium-r-normal--%d-*-*-*-*-*-iso8859-1 -*-fixed-medium-r-normal--%d-*-*-*-*-*-jisx0208.1983-0 o ֥ޥץ ֺåưפȡֱåưפǤϥޥκΥܥ ФƳƤ뵡ǽꤷޤ֥Х롼ξõפϥХ롼 󥦥ɥǥܥ򲡤ƥХ롼󥦥ɥĤưɽ ޤɥ̤ذưפϥեɥޤ Х롼󥦥ɥ򥯥åƤΥɥ־˰ư ưɽޤդˡɥ̤ذưפƤΥ ɥֲ˰ưưɽޤ o ֥֥饦ץ ֥֥饦פǤ ninix ƤӽФ֥饦ꤷޤ ޥ %s ϼºݤ URL ֤ޤǥեȤ ϰʲ̤Ǥ netscape -remote 'openURL(%s,new-window)' o ֥إѡץ ֥ץꥱפǤ Drag & Drop 줿եФƵư 볰ޥɤλ (եδϢդ) Ԥޤե δϢդϡե̾Υѥ (ɽ) ȥޥ̾Ȥ 롼ηɽޤ New ܥ򲡤ȿ롼ޤPattern ˤϥե̾ ΥѥCommand ˤϥѥ˥ޥåեФƵư ޥɤꤷޤ㤨СĥҤ .nar/.zip/.lzh Υե 뤬 Drag & Drop 줿 ninix-install ޥɤư ˤϰʲΥ롼ꤷޤޥ %s ϼºݤΥե̾ ֤ޤ Pattern: \.(nar|zip|lzh)$ Command: ninix-install %s ¸Υ롼Խˤ Edit ܥѤޤDelete ܥ פʥ롼ޤUp Down ϥ롼νѹ ΥܥǤ (˥ޥå롼뤬ͭˤʤޤ) ɥޥ͡ - - - - - - - - - - - - - - X Window System Ǥϥɥ˳ (ȥСꥵϥ ɥʤ) դꥭܡɥեͿꤹΤϥ ɥޥ͡λŻȤȤˤʤäƤޤ⤷ʤ¾ ץꥱƱͤ˳ȤɽȻפޤޤninix ˥ܡȥեȤʤꤦäȤȻפޤ ɥޥ͡ŬꤷơʲΥץꥱ ̾/ɥ̾ФƳȤɽˤꥭܡɥե Ϳʤ褦ˤ뤳Ȥ򤪴ᤷޤ (Τ褦ΤǤʤ ɥޥ͡Ǥ ninix λѤϿɤΤޤ) o ץΤꤹ̾ WM_NAME = "ninix" o ġΥɥꤹ̾ WM_CLASS = "ninix.surface.sakura", "Ninix" (¦ե) WM_CLASS = "ninix.surface.unyuu", "Ninix" (ˤ夦¦ե) WM_CLASS = "ninix.balloon.sakura", "Ninix" (¦Х롼) WM_CLASS = "ninix.balloon.unyuu", "Ninix" (ˤ夦¦Х롼) [] o WindowMaker ξ (С 0.61.1 dzǧ) ȥСDZåޤϥɥ Ctrl-Esc 򲡤 ơ°פ򤷡ʲιܤ˥åޤ - ȥСɽʤ - ꥵС̵ˤ - ɥ - եʤ ¸ץܥ򲡤ƥɥĤninix Ƶưޤ o qvwm ξ (С 1.1.11 dzǧ) ~/.qvwmrc [Applications] ˰ʲɲäޤ "Ninix" NO_TITLE, NO_BORDER, NO_FOCUS, NO_TBUTTON o fvwm2 ξ (С 2.2.4 dzǧ) ~/.fvwm2rc ˰ʲɲäޤ Style "Ninix" UsePPosition, ClickToFocus, NoTitle, WindowListSkip, NoHandle o twm ξ (XFree86 3.3.6 °ΥСdzǧ) ~/.twmrc ˰ʲɲäޤ UsePPosition "on" NoTitle { "ninix" } o sawfish ξ (С 1.0.1 dzǧ) ֥ɥץѥƥץǰʲɲäޤ - ץɥ: ̾ ^ninix$ - եʤ - ե졼ॿ: ʤ - ɥꥹȥå ϴϢΥ٥Ȥͭˤϡ嵭Ρ֥ե ʤפ򳰤ǡ֥եפΡ֥ɥϤ ɽȤ˥եפ̵ˤޤ 嵭꤬ͭˤʤʤϰʲμƤߤƲ $ rm ~/.sawfish/window-history $ sawfish-client -f restart ե - - - - - - - ~/.ninix/fontrc Խ뤳ȤˤХ롼ǻȤե ѹ뤳ȤǤޤǥեȤǤϰʲΤ褦Ƥˤʤä ޤ ------------------------------------------------------------ # fontrc for ninix -*-helvetica-medium-r-normal--%d-*-*-*-*-*-iso8859-1, -*-fixed-medium-r-normal--%d-*-*-*-*-*-jisx0208.1983-0 ------------------------------------------------------------ ʸեȤʸեȤ򥳥ޤǶڤäƵҤƲե ȥʬ嵭Τ褦 %d ˤƤŬڤʥΥե ȤȤޤ (ܤϰʤޤեȥŪ ꤹ뤳ȤǽǤ)1Ԥǽ񤯤Ȥ嵭Τ褦ޤ֤ƽ ȤǤޤޤƹԤιƬȹζʸ̵뤵ޤ # ǻϤޤԤϥȤǤ (̵뤵ޤ) ޤ~/.ninix/gtkrc 񤭴뤳Ȥˤ GUI θܤѹ 뤳ȤǤޤgtkrc εˡˤĤƤϳ䰦ޤܤ GTK+ Υޥ˥奢򻲾ȤƲ GTK+ 1.2 Tutorial (Chapter 23. GTK's rc Files) http://www.gtk.org/tutorial/ch-gtkrcfiles.html Ѿ (ǽμ) ------------------------------- o ǥեȤΥȤ̵ᡢ򤹤ȥե ڤؤꡢʬľ˻ѤƤΤ򤽤Τޤ ³ѤޤȤȥȤ߹碌ˤäƤϴ ʰݤ뤫Τޤ o ȥ˥塼 (ڤؤʤɤɽ) ȸͭΤդʤɤ̵ΤǡʤǵʤǤ o COMMUNICATE ɥ (ޤ?) ޤ o ¾Ƽﵡǽ̤ǤϤääơ֤Ǥ뤳ȡפ ʤǤܤǽ¤ΰ TODO եˤޤ ȤꥸʥΡֲפۤȤɸ˺äƤΤDz ̤ʤΤ褯ʬäƤޤ󡣡֤εǽ פȤ˾йθޤ o ɸϤˤȥǥХåѥåɽޤ /dev/null ˥쥯ȤʤɤƲ -------- o С 0.8 (2002ǯ712) - ǥ꡼ɤѹ̵ o С 0.7.33 (2002ǯ79) [󥹥ȡ] - ֤ makotob.dll Ȥե뤬ޤޤƤ Х [] - surfaces.txt Υȥ̾ȥեꥢ˥ ѥǥ󥰤줿եֹȤ褦ˤ o С 0.7.32 (2002ǯ73) [] - base ᥽åɤˤ륢˥᡼߻Х - overlay ᥽åɤɽ˥᡼󥰥롼ֹ ξΤ褹褦ˤ(Thanks to ë) - (ѥ󤬰Ĥʤ) ˥᡼󥰥롼פ - ؤľ \s[-1] DZեɥ ɽʤȤΤ - ¸ߤʤե뤬ꤵƤ [] - ׻νɲáƥؿ $calc - Ź沽 (ĥ .__1) б - ؿƤӽФΰβ (ӼιƬζ) ɤ 褦ˤ [Τ] - ̾˱黻ҤޤޤƤʸιʸϤʤ o С 0.7.31 (2002ǯ628) [] - ˥᡼ɤ߹߽ɡޤζ ɤФ褦ˤ - SERIKO/1.2 runonce Υ˥᡼󤬥ȵưǽ 1󤷤ưƤʤäΤ - եΥեֹФƥꥢʤ o С 0.7.30 (2002ǯ628) [ͥåȥ] - URL ~ 򥨥פ褦ˤ [] - SERIKO/1.2 overlay ᥽åɤʣΥ˥᡼ ưƤȤɽʤн褷 o С 0.7.29 (2002ǯ619) [󥹥ȡ] - եΥȤΥե̾˴ؤűѤ ǤդΥե̾򰷤褦ˤ - install.txt balloon.directory ǻꤵ줿ǥ쥯ȥ꤬ ¸ߤʤ [] - \![updatebymyself] νɲä - ̤ΤΥӥ᥿ʸ󤬤äƤʸˡ顼Ȥ˽ ³褦ˤ o С 0.7.28 (2002ǯ613) [󥹥ȡ] - ץȤΥ󥹥ȡ [ͥåȥ] - .pna եɤʤ褦ˤ - ܸե̾ Shift_JIS ǥ󥳡ɤƥꥯȤ 褦ˤ - 404 Not Found ʤɤΥ顼Ƥͥåȥ³ 褦ˤ(Thanks to ˤ㡼󤵤) o С 0.7.27 (2002ǯ66) [󥹥ȡ] - ꥢե͡ (ե̾̾) νѹ ե̾Τޤޥ󥹥ȡ뤹褦ˤ - ޥɼ¹ԻΰΥ׽& " ʤɤ ü쵭ޤե̾褦ˤ - ޤǥ쥯ȥ̾ʤХ [ͥåȥ] - ̵եɤ褦ˤ - delete.txt ˤեκб [Τ] - ץȤκŬɲáפڤؤľΰϢ \n 򼡤Ʊ쥹פƬ˥եȤ褦ˤ - ʸФ羮ӱ黻դȱդ˿ͤǤʤ ˸¤ʸĹӤ褦ˤ o С 0.7.26 (2002ǯ527) [󥹥ȡ] - ֥ǥ쥯ȥ˳Ǽ줿׼եʾ ˥󥹥ȡ뤷ƤޤХ [] - ɬܥե (0֤10) ̵ͭȽե Ǥʤեθ褦ˤ - ȹνelement0 ι᥽åɤλ ̵뤹 (overlay Ȥߤʤ) 褦ˤ - alias.txt ɤ߹߻Υ顼Ϥ˴ؤХ [] - SHIORI/1.0 getaistringrandom - misaka.ini propertyhandler ȥνɲä (ñ ɤФ) [Τ] - SHIORI/1.0 getaistringrandom - ʤȿѿ֡ʤǤȿפӡ֡ʤǤ ³֡פνɲä - ʸФ黻 (, , , , , , , ) - replace.txt replace_after.txt ˤʸִη ʸܸб string.replace() إɤѰդ o С 0.7.25 (2002ǯ523) [] - Ķѿ DISPLAY ̤ξ硢顼Ϥƽλ ˤ(Thanks to Ȥ) - ȹΥ顼ɡޤѥǥ󥰤줿 եֹĥȤʤ o С 0.7.24 (2002ǯ520) - Python 2.2 ʹߤ rfc822 ⥸塼βߴн褷 [󥹥ȡ] - Х롼Υ󥹥ȡǥ쥯ȥ̾ʸѴ˺ (Thanks to ë) [] - surfaces.txt νɤ (sakura|kero).surface.alias ɤ褦ˤޤ̵̣ʹԤ򥨥顼 ñɤФ褦ˤ o С 0.7.23 (2002ǯ516) - ͥåȥǰΥǡեΥɤ˼Ԥ Х o С 0.7.22 (2002ǯ515) [󥹥ȡ] - ǡե̾ʸѴƥ󥹥ȡ뤹褦ˤ - ¸Υǡե̾Ѵ -L (--lower) ץ դ (ФΡ֥󥹥ȡפι򻲾) - -g (--ghost) ץ -s (--shell) ץư ʤʤäƤΤ [] - \_a νɲä (ñɤФ) [] - ե̾ʸѴɤ߽񤭤褦ˤ [] - ե̾ʸѴɤ߽񤭤褦ˤ o С 0.7.21 (2002ǯ512) - Python ǽ񤫤줿٥⥸塼 (shiori.py) 򰷤褦ˤ (doc/extension.txt 򻲾) [] - ʲξ˥եŪ0/10֤ᤵʤ褦ˤ URL 򤷤Ȥ (󥯤éäȤ) OnChoiceSelect ٥Ȥȿ̤ΤȤ OnChoiceTimeout ٥Ȥȿ̤ΤȤ Х롼ĤƥץȤǤȤ å˥塼򳫤ƥץȤǤȤ - ǡեɤ߹߻Υ顼åɸ२顼˽ 褦ˤ o С 0.7.20 (2002ǯ58) - INSTALL/1.4 (ɲå󥹥ȡ) б - ninix-update ޥɤ -s (--script) ץ 줿ץȤμ¹Ի˥Ȥξɽ褦ˤ - Python 2.2 ǥ󥹥ȡǤʤ (config/Makefile.pre.in ̵) н衣Distutils ѤƳĥ⥸塼򥳥ѥ 뤹褦ˤ [] - \![change,ghost] νɲä - ѥǥ󥰤줿եֹб (: "0010" "10" ) - ȤĤ󥹥ȡ뤵Ƥʤϡ־ǻؼ ܥ򲡤ʤ褦ˤ [] - ֥륯ȤǰϤޤ줿ʸ \\ פ줿 ޡȤư [] - ɤ߹߻Υ顼åɽʤ褦ˤ - ؿΰ˳դΥäʸͿʸˡ顼 ˤʤ [Τ] - ɤ߹߻Υ顼åɽʤ褦ˤ - ʲΥ٥ȤФȿ̤ξ硢б֡ʸפ 쥯Ȥ褦ˤ OnFirstBoot OnBoot ư OnClose λ OnGhostChanging ¾ΥȤѹ OnGhostChanged ¾ΥȤѹ OnVanishSelecting ǻؼ OnVanishCancel ű OnVanishSelected Ƿ OnVanishButtonHold [] - ᥿ʸ %rand %ref θοȴƤ o С 0.7.19 (2002ǯ430) - ninix-update ޥɤפȡΤפбΤ˺ ƤΤǽ [Τ] - OnChoiceSelect ٥ȿ̤ξ硢Reference0 ǻ 줿֡ʸפƤӽФ褦ˤ(Thanks to 䤮) - ֡ñ췲פִƤӽФΡ֡ʸפȶͭ褦 (Thanks to 䤮) o С 0.7.18 (2002ǯ427) - shiori.dllΤ׸ߴ⥸塼ܤ [] - ͥåȥ DLL եɤʤ褦 - surfaces.txt alias.txt βϽ򶯲ȥ̾ '{' δ֤ζԤɤФѥڡ̵뤹褦ˤ - "-2" ˤ˥᡼νλ (SERIKO/1.4 ˤƱ) Х o С 0.7.17 (2002ǯ422) [󥹥ȡ] - ǥեȤΥ֥ǥ쥯ȥ (ɤե Ǽǥ쥯ȥ) ~/.ninix/archive ѹޤ Ķѿ NINIX_ARCHIVE -A (--arcdir) ץˤ ֥ǥ쥯ȥǤ褦ˤ [] - å˥塼ΰĤ˿ӤƲ̤Ϥ߽ФƤ ޤн衣 [] - źդѿȤб - ƥؿ $count - 顼㴳ɲá - #_Common ˴ؤ봪㤤Х(Thanks to ) - ƥؿ $copy ޥɤߴѿξ ̵뤹褦ˤ o С 0.7.16 (2002ǯ412) - README ե˾ǻؼɲä [] - եб - surfaces.txt ɤ߹ˡʥȥ꤬äƤ Ǹޤǥեɤ褦ˤޤ# // ǻϤޤԤ ȤȸʤɤФ褦ˤ [] - $_OnRandomTalk $_talkinterval ˤ뼫ưȡ o С 0.7.15 (2002ǯ411) [] - ͥåȥܥ̾򥴡ȤꤷΤѹ 褦ˤ - ե0ΤȤ OnUpdateReady ٥Ȥȯʤ 褦ˤ - \![open,teachbox] νɲáOnTeachStart ٥Ȥ ȯ褦ˤ - \![open,inputbox] 4 (Ԥ) άǽˤ [] - SHIORI/2.4 (ؽǽ) - ȤεưȽλˤ줾 system.OnLoad ȥ system.OnUnload ȥƤ֤褦ˤ - entry ޥɤߴץ2Ϥ 褦ˤ - save ޥɤνϥեΥȤ Shift_JIS ǤϤʤ EUC-JP ǽϤ [] - Ȥνλѿư¸褦ˤ - ͤʸѿư¸ʤ褦ˤ o С 0.7.14 (2002ǯ410) [] - ǻؼǽ(Thanks to ë) - ߥ˥󥦥ɥΥɥפ ꡣ-p ץ̵ͭȿǤʤ褦ˤ o С 0.7.13 (2002ǯ44) [] - \![open,inputbox] νɲáOnUserInput ٥Ȥȯ 褦ˤ(Thanks to ë) - Ťʤե饰Ƚˡ(Thanks to ë) - Ƶưȥޥܥ꤬ꥻåȤХ (Thanks to ߤʤ蘆) [󥹥ȡ] - ߥ˥󥦥ɥѤβե (balloonc?) 󥹥ȡ뤹褦ˤ [] - \ns_rt ӥ᥿ʸ %ver νɲä - ץΥ᥿ʸ %jpentry, %platform, %move, %mikire, %kasanari ŸʤХ o С 0.7.12 (2002ǯ327) [] - SERIKO/1.4 (˥᡼Ʊ) - 쥤 PNG б - ɽȤ꤬ ~/.ninix/preferences ¸ʤХ (Thanks to TOW ) - ȤڤؤƤ⸫ڤե饰ȽŤʤե饰ꥻåȤ ʤХ - surfaces.txt (sakura|kero).surface.alias ɤФ褦ˤ [] - ѿ¸/ƥؿ $backup νɲä - ƥؿ $adjustprefix - ϥ顼ȯȾˤäƤ - ̤Ф $pop $popmatchl ƤӽФ [] - UTF-8 μեб - \ns_tn νɲä [] - ɤ߹߻ʸѴ顼 o С 0.7.11 (2002ǯ320) [] - ڤե饰ȽŤʤե饰б(Thanks to ë) [] - ᥿ʸ %mikire %kasanari νɲä - ڤ/ŤʤϢ٥Ȥȯ褦ˤ o С 0.7.10 (2002ǯ318) - README եɲä - README ե twm sawfish 㡢 -p ץ ˴ؤɲä(Thanks to ë) [] - OnFileDropping, OnFileDropped, OnDiretoryDrop γƥ٥Ȥ ȯ褦ˤ(Thanks to ë) - Ρ֥إѡץ֤֥֥饦פ˲̾ ե̾ȥޥɤδϢդԤ֥إѡץ֤ߤ - \![raise] \![open,browser] νɲä [] - get ޥɤΥХ(Thanks to Ĥ) [] - ٤٤¸ (ȵư) 褦 [] - ƥؿ $substringw, $substringwl, $substringwr - ѿƱ̾Υ桼ѿ򻲾ȤǤʤ褦ˤ (ѿ ͥ褵) o С 0.7.9 (2002ǯ312) [] - OnMouseClick, OnMouseWheel, OnKeyPress γƥ٥Ȥȯ 褦ˤ(Thanks to ë) - Ρ֥٥ȡץ֤ OnFirstBoot ܥ OnBoot ܥξͤ褦ˤ [] - ʲΥ饤󥹥ץȥޥɤߴ ͤä硢0Ϳ줿ΤȤƼ¹Ԥ褦ˤ chr ޥɤ1 get ޥɤ2 inc ޥɤ dec ޥɤ13 rand ޥɤ1 test ޥɤα黻 -eq, -ne, -gt, -ge, -lt, -le ξ - ˤäƤͤʿͤȤߤʤȤХ - set ޥɤѿ̾ʣ票ȥ̾ (& ʣϢ뤷 ȥ̾) ǻꤹưʤ - ɤ߹߻Υ顼å򤹤ɡ顼ȯ (ֹȥե̾) ɽ褦ˤ o С 0.7.8 (2002ǯ311) [] - 򥫡뤬ܥ SSTP ޡ񤭤Ƥ (Thanks to ë) - ѥХ롼ĥȤѥХ롼ʤ ڤؤ硢ǸѤƤѥХ롼᤹褦ˤ - ܥ֥륯åƤ OnMouseDoubleClick ٥Ȥȯʤ褦ˤ - OnMouseDoubleClick ٥ȤФȿʤäΥǥե ȿ (ȥ˥塼̵ȤݤΥ) [] - ᥿ʸ %move νɲä [] - $getmousemovecount νĴʤȿδ٤夲 - ץ # ޤޤƤȼ¹Ի顼 o С 0.7.7 (2002ǯ38) [] - OnMouseMove ٥Ȥȯ褦ˤ (Thanks to ë) ܥ򲡤ޤޥޥ岼˿뤳ȤƱ٥Ȥ ȯ뤳ȤǤ롣ޤ˹碌ƥե ɥΰưܥѹ - OnFirstBoot ٥Ȥȯ褦ˤ (Thanks to ë) ΤȤƱȤǤ⥷뤬ۤʤƱ٥Ȥȯ 롣 [] - ƥؿ $getmousemovecount $resetmousemovecount - ӱ黻 != ʤ (˺Ƥ) o С 0.7.6 (2002ǯ37) [] - ʲΥƥؿ: $substringfirst, $substringlast, $hiraganacase, $isequallastandfirst, $index, $insentece [] - ९ƥ륻å \+ ޤ \_+ ǥ Ȥڤؤȱå˥塼Фʤʤ (Thanks to ë) o С 0.7.5 (2002ǯ35) - ninix-install ޥɤ Python 1.5.2 ưʤ֤ˤʤä Τ(Thanks to Ȥ) o С 0.7.4 (2002ǯ34) - overlay ᥽åɤ򤵤˽ϰϤɬ׺Ǿ¤ˤ Ĥޤ褦ˤ o С 0.7.3 (2002ǯ34) - SERIKO/1.2 overlay ᥽åɤʬŤ͹碌 ˥١Ȥʤ륵եƩʬ (ޥ) ޤƺ 褦ˤ(Thanks to ë) - ninix-install ޥɤɡƱ̾Υǡ˳ǧ åɽ -i (--interactive) ץɲä o С 0.7.2 (2002ǯ227) - ɸ shiori.dll׸ߴ⥸塼 - ʸޤǥ쥯ȥ̾ ninix-install ޥ ۾ェλ(Thanks to ë) o С 0.7.1 (2002ǯ222) - ˥᡼ǽ (SERIKO/1.2) move, base, overlay γƥ᥽åɤбoverlayfast overlay \i ɲá - \q ΰľ \n ɤ 褦ˤ o С 0.7 (2002ǯ218) - ʳ SSTP ꥯȤˤ \j[], \-, \+, \_+ γƥѤǤʤ褦ˤΥޤॹ Ȥ 400 Bad Request ֤ - OnMinuteChange ٥Ȥ OnSecondChange ٥Ȥ cantalk ե饰б - \q οʸˡ (\q[text,id] η) б ʸˡ (\q?[id][text] η) դ롣 - \! \_n νɲä (ñɤФ) - surfaces.txt б - եɥˡѹ֥ХåեѤ μ¹ԸΨ夲 (˥᡼ǽؤ) o С 0.6 (2002ǯ15) - ǥ꡼ɤѹ̵ o С 0.5.29 (2001ǯ1226) [] - ʲξﲼǤϱå˥塼Υ//Х롼 ѹͥåȥɤ߹ߡС󡢽λγ ǽѤǤʤ褦ˤ (¹Ԥ褦Ȥȷٹ𲻤Ĥ) ९ƥ륻å ͥåȥ Ԥ̤Υ٥ȤĤäƤ - ߵǽ - å˥塼ιѹ٤Ƥιܤ֥˥塼 äΤǥ˥塼򳫤äѤʤˤ뤳ȤǤ褦 ʤä - ٤νͤѹȤˤǥե Ȥˤޤ٤٤򱦥å˥塼 ˰ܤ o С 0.5.28 (2001ǯ1219) [󥹥ȡ] - եɻ˥ФΥƥȵǽѤƤ ڤ褦ˤContent-type: إåΥåѻߡ o С 0.5.27 (2001ǯ1214) - libpng ΥСˤäƤ modules/_image.c Υѥ ˼Ԥ(Thanks to shiena ) [] - ܸʸ2Хܤ礱ƤХ o С 0.5.26 (2001ǯ1211) [] - å˥塼ˡֻΨաפɲä [] - save ޥɤɤǤʤեƤޤ Ȥ o С 0.5.25 (2001ǯ129) [] - ư֥饦ѹǤ褦ˤ [] - AI ϤΥ᥿ʸ (%ms, %dms ʤ) ʸŸ (Thanks to TOW ) o С 0.5.24 (2001ǯ124) [] - νλ˥ץ饰 SIGHUP ʥ褦ˤ [] - date ޥɤν񼰻ʸ %n, %N, %r, %J б o С 0.5.23 (2001ǯ122) [] - ͥåȥǽΥץб˴ؤХ (Thanks to Τˤ) [] - ʣ票ȥ̾ (& ʣϢ뤷ȥ̾) б o С 0.5.22 (2001ǯ1126) [] - \n ΰ (\n[half] ʤ) ɤФ褦ˤ [] - С 0.5.3 ǤζΥơȥȤؤн褬ŬڤǤʤ Τ(Thanks to Ĺ䤵) o С 0.5.21 (2001ǯ1124) [] - ͥåȥǽץбˤ o С 0.5.20 (2001ǯ1115) [] - μ̿ŪʥХäΤǼޤ o С 0.5.19 (2001ǯ1115) [] - ͥåȥ updates2.dau ɤ߹ߤ˼Ԥ Х(Thanks to Τˤ) [] - - \set[] ޥɤȥ᥿ʸ %get[] νʲΤ褦˽: §黻ΥڥɤȤʸͿȱ黻̤ʸ ˤʤ (: %week+256 "sun+256") 0ǽ黻̤ʸˤʤ (: x/0 "x/0") ̤ѿ򻲾Ȥͤʸ "?" Ȥ롣 - ѿޥɤŸƤޤ (: %ms \ms Ÿ) Х o С 0.5.18 (2001ǯ1111) - ninix-install ޥɤdzĥ .nar Υ֥ե դ褦ˤ - ninix-update ޥɤǡ¤ѹˤưʤʤ ƤΤ(Thanks to Τˤ) [] - ư˥ƥפѤäƤⵯư֤ͤˤʤʤ ˤ [] - \set[] ޥɤӥ᥿ʸ %get[] - ᥿ʸ %year, %rand[], %surf0, %surf1 б - ñΥ (,) Υפб - .txt/.dtx ʳγĥҤΥե򼭽Ȥɤ߹⤦Ȥ Х - CΥȤΰ˴ؤХ o С 0.5.17 (2001ǯ1110) [] - ꥢ͡ (ե̾) б [󥹥ȡ] - ץ饰 URL ǥ󥹥ȡ뤹ȥ֥ե ˥󥹥ȡ뤵ƤޤХ [] - ʻʤʬդñϿʸˡ顼ȸʤƤ٤ ̵ˤʤäƤΤ o С 0.5.16 (2001ǯ1110) [] - SSTP EXECUTE/1.3 (Quiet/Restore) - ץȤμ¹Գ˥ե0֤10֤ᤵʤ褦 ˤ (ΥץȤνλΥեΤޤ޿ ץȤμ¹Ԥ򳫻Ϥ) - \q ɽˡ;פʲԤʤ褦ˤ - "..." Ǿά줿ʸ SSTP 饤Ȥ֤ Х [󥹥ȡ] - -R ץǧǤʤ o С 0.5.15 (2001ǯ116) [] - ץ饰ǽͤˤĤƤ doc/extension.txt 򻲾ȡ - \&, \_m, \_u γƥνɲä - OnSurfaceRestore ٥ȤȯޤǤλ֤2030ä˽̤᤿ [ͥåȥ] - OnUpdateReady ٥Ȥȯ褦ˤ - OnUpdate.OnDownloadBegin ٥Ȥȯߥ󥰤 updates2.dau եˤĤƤƱ٥Ȥȯʤ褦 ѹޤReference1 (ֹ̤) Reference2 (ե ) դ褦ˤ [󥹥ȡ] - ե̤μưȽ꤬ޤǽƤʤä o С 0.5.14 (2001ǯ115) [] - OnSurfaceChange ٥Ȥ OnSurfaceRestore ٥Ȥȯ 褦ˤ - å˥塼򳫤 SSTP 饤ȤΥץ break 饹ץȤɽߤ褦˽ - \q θʸιԤˤĤʤäƤޤХ [] - ȯȡΥߥ󥰤٥ȿĤä鼫ȯ ȡΥޡꥻåȤ褦ˤ - PѥڡȾѥڡȤߤʤ褦ˤ o С 0.5.13 (2001ǯ113) [] - SEND SSTP/1.3 ꥸʥλͤȤΰ㤤ˤĤƤ doc/extension.txt 򻲾ȡޤ\m[] νɲä - SSTP μѹ塼ǺΥץȤ ९ƥ륻åǤʤ硢ΥץȤ ǤƤ˼ΥץȤκϤ褦ˤ - ץȤκֳ֤0äˤ (3ô֤ΥȤ ֤Ƥ) - \h, \u, \0, \1 γƥνɽ֤ΥХ롼 ɽ̵̤ - ξΥեɥǤ٤ƤΥեɽǤ 褦ˤʬȥͥǽޤեΥ ޤޤäɽ֤η׻ˡ - ѻ %selfname %keroname ͤʤ o С 0.5.12 (2001ǯ112) [] - SSTP ĥȼꥯ EXECUTE/1.5 (Reload ޥ) ɲáSSTP 𤷤ƳΤ˥ǡκɤ߹ߤؼ Ǥ褦ˤ - ɤ߹ߤγȽλˤ줾 OnNinixReloading ٥ Ȥ OnNinixReloaded ٥Ȥȯ褦ˤ - å˥塼Ρֺɤ߹ߡפ̣ʤå˥塼 ƥˤʤäƤΤ [󥹥ȡ] - 󥹥ȡ˥ SSTP Ф˥ǡκɤ߹ߤ ؼ -r (--reload) ץɲä˹碌 --rcfiles ץû̷ -R ѹޤSSTP Υݡֹꤹ -p (--port) ץɲá o С 0.5.11 (2001ǯ1031) [] - \-, \+, \_+ γƥνɲä - å˥塼ˡֺɤ߹ߡפɲáΤư˥ ȡ뤷ǡͥåȥǿʤäǡ ưɤ߹褦ˤ o С 0.5.10 (2001ǯ1021) - Makefile lib/ninix/netlib.py Υޥ̾򥤥 ȡ˽񤭴褦ˤ(Thanks to shiena ) [] - SSTP NOTIFY/1.0 NOTIFY/1.1 б o С 0.5.9 (2001ǯ1019) [] - IfGhost: إåΤʤ SSTP SEND/1.4 ʥåȸ - λ˥ȤäƤ˺ƵưǤʤʤ (Thanks to shiena ) - ͥåȥǽۥ̾βΤǤޤ ʤ褦ˤޤupdates2.dau URL 쥯Ȥ 줿ϻĤΥեƱ쥯ۥȤ ɤ褦ˤ - λ˥եʤȤȤäƤ Ǥʤ (̤ΥȤư) [󥹥ȡ] - ֥ե̾˲ä URL ȥꥹȥեǤ 褦ˤ - 0.5.8 ǡֲ׸ߴ⥸塼˻ܤѹбΤ ˺ƤΤǽ(Thanks to shiena ) o С 0.5.8 (2001ǯ1017) [] - SSTP SEND/1.4 бΤȤȤ allowembryo (allowsakura) ̵ͤ뤹롣 - \s[-1] \b[-1] νɲä [] - kawari.ini set ȥ include ȥб - security ȥɤФ褦˽ - get, size, textload, tolower, toupper γƥ饤󥹥 ȥޥɤ [] - ¦Ǥ٤Ǥ褦ˤ - \ns_cr, \ns_st[], \ns_jp[] γƥ %jpentry, %plathome (%platform), %ns_st γƥ᥿ʸ/Ķѿνɲä - ٥ OnNSRandomTalk OnNSJumpEntry ȯ 褦ˤ o С 0.5.7 (2001ǯ1017) [] - Apply ܥդ Ƥ֤ϥݥåץåץ˥塼Фʤ褦ˤ (Thanks to shiena ) - 桼եǼǥ쥯ȥ򥳥ޥɥ饤 ץ -U (--userdir) ȴĶѿ NINIX_USER ǻǤ 褦ˤ󥹥ȡƱͤ˲ɡ(Thanks to ڤ) - ΰνredo ͤξΰб o С 0.5.6 (2001ǯ1015) - ninix-update ޥ (ޥɥ饤ǤΥͥåȥ ) Ѱդ [] - ɲä - ٤򥴡Ǥˤ륪ץɲä - ȯԤ륤٥ȤǤ褦ˤ - ޥܥ󲡲ο񤤤ѹǤ褦ˤ [] - pirocall, shift, unshift, push, pop, chr γƥ饤󥹥 ץȥޥɤ - 륹ץȥե ("[SAKURA]" ȤԤǻϤޤ뼭 ե) ɤ߹ߤб o С 0.5.5 (2001ǯ1012) [] - OnGhostChanging, OnShellChanging, OnClose γƥ٥Ȥȯ 褦ˤ֥쥤ǽʥץȤξϥХ롼 å뤳ȤˤǤ뤳ȤǤ롣 o С 0.5.4 (2001ǯ109) [] - ͥåȥǽupdates2.dau 󤵤Ƥե ̾Τ(install.txt thumbnail.png ʳ) ǥ쥯 ȥ̾ޤޤʤե ghost/master ǥ쥯ȥ۲ˤ Τȸʤ褦ˤ(Thanks to Τˤ) [󥹥ȡ] - ֥եŸ find ޥɤˤѡߥå ѹޤưƤʤäΤ [] - ᥿ʸ %ref0 %ref7 Ÿ o С 0.5.3 (2001ǯ108) [] - ͥåȥǽΤȤʲ : 줿եɤ߹ˤϺƵưɬס ǥ̵եϥɤʤ ե̾ΥꥢбƤʤ - OnGhostChanged ٥Ȥ OnShellChanged ٥Ȥȯ 褦ˤ - -H (--home) ץꤷƤ ~/.ninix/preferences ɤ߹ޤХ(Thanks to Τˤ) [] - ޥơȥȤ˶ΥơȥȤ Х(Thanks to Τˤ) [] - ޥ \id б o С 0.5.2 (2001ǯ105) - Makefile RPM Υѥåݤβ󥹥ȡ (DESTDIR) Ǥ褦ˤ [] - Ÿʸ ${} (ȥ̾λ) [MAKOTO] - 褬̵˥פ줿üʸΤޤŸ ˽ФƤޤХ o С 0.5.1 (2001ǯ103) [󥹥ȡ] - ץȤɲˡѹեå (surface.txt դ inverse ѥեǡ) ĥȤӥ ϥץȤɲоݤ鳰褦ˤ - ץȤɲоݤʣĤä˾ߤɲоݤ ꤹ뤿 -S (--supplement) ץɲä - ץȤФ -s (--shell) ץǤ褦 ˤ [] - kawari.ini Υ顼randomseed debug Ĥ ȥɤФ褦ˤ o С 0.5 (2001ǯ103) [] - redo ͤб\0 \1 νɲä - PNG ľɤ߹褦ˤ - Ȥӥտ魯Х롼̤ΥХ롼Ʊ 򤷤ѤǤ褦ˤ - \s[] ǥƥ֤ǤʤפΥեѹǤ ޤХ(Thanks to ڤ) - ̤ʤ \s \b ν㤨С"\s123" "\s[1]23" Ȳ᤹롣ޤ"\u\s1" "\u\s[11]" ᤹롣 [󥹥ȡ] - redo ͤбinverse ͤΥǡ⥤󥹥ȡǽ - PNG XPM Ѵ˥󥹥ȡ뤹褦ˤ - XPM Ѵƥ󥹥ȡ뤹뤿 -x (--xpm) ץ ɲá [pngtoxpm] - PNG νѤƤϥåơ֥ǼGLib ؤΰ¸̵ [] - `\' ǥפ줿ʸޤʸιʸϤɾ˴ؤ Х [] - \d, \k, ʬդñϿб - ³Ԥб - C/C++ ΥȤб o С 0.4 (2001ǯ925) - ǥ꡼ɤѹ̵ o С 0.3.6 (2001ǯ918) - 1023ְʲΥݡֹ򥪥ץǻǤʤ褦ˤ - εưΥåȴϢΥ顼åɡ - ֲ׸ߴ⥸塼ޥơȥȤ ʸǤϤʤʸǤĤʤ褦ľ o С 0.3.5 (2001ǯ93) - Makefile Զ(Thanks to shiena ) [] - ޥɥ饤󥪥ץ --sstp-port ɲáޤĶѿ NINIX_SSTP_PORT 򻲾Ȥ褦ˤ - ޥɥ饤󥪥ץ --iscp-port ɲáޤĶѿ NINIX_ISCP_PORT 򻲾Ȥ褦ˤ [󥹥ȡ] - ܸΥե̾ (ǥ쥯ȥ̾) ޤॢ֤򥤥 ȡǤ褦ˤ - Х롼դΥ/򤭤ȥݡȤ (ä бĤä¤ưƤʤä) o С 0.3.4 (2001ǯ828) - Х롼٤ɽƤʤ֤ SSTP å Х(Thanks to ڤ) - pngtoxpm 򤹤ѥ˷ٹ𤬽Фʤ褦ˤ ޤ󥫥ե饰 -lm ɲä - ưץ (ninix, ninix-install) δĶѿˡ ѹ o С 0.3.3 (2001ǯ821) - \b[] (Х롼󥵥ѹ) νɲá - SEND SSTP/1.2 ȡʹߥ˥塼褬ȿ ʤʤХ - ʤ %friendname 򥵥ݡȡ o С 0.3.2 (2001ǯ815) [] - OnMouseDoubleClick ٥Ȥ OnChoiceSelect ٥Ȥȯ 褦ˤեåȽ - ̿Ūʥ饤󥹥ץȥ顼ȯΤǡȤꤢ OnSecondChange ٥Ȥȯߤ뤳Ȥˤ - \a \_e \j[] νɲä - %exh %et ͤη׻ˡѹ%exh ˤñ̤դʤ ˤ - åˤɽǤǥ顼 - OnChoiceTimeout ٥ȤȿʤȤξ硢ǽΥ ॢȰʹ߲⤷٤ʤʤȤХ [] - 饤󥹥ץȥޥɤ䴰pirocall, urllist, help, ver, searchghost ʳ Phase 6.2 Τ٤ƤΥޥ - expr ޥɤĤ̤ȤɬפȤ (!) б - index ޥɤνƤ򴪰㤤ƤΤǽ - ʤŤΡֲ׼񥨥ȥ̾Ȥʤʤ Ƥ [] - դ٥Ȥν䴰ʸΤߤξб ( ǤΤ?) - \ns_ce \ns_st[?] Фɲáߤñ Ф o С 0.3.1 (2001ǯ813) [] - ֲ׸ߴ⥸塼˳ĥ饤󥹥ץȤ Ϥ - ֵ١׸ߴ⥸塼˾դ٥ȤνϤä - GUI θܤ ~/.ninix/gtkrc ѹǤ褦ˤ ݥåץåץ˥塼طʿԥ󥯤ˤƤߤꡣ - Х롼ǻȤեȤ ~/.ninix/fontrc ǻǤ 褦ˤ - ⤷ХȤȤ߹ޤ줿ưåɽ褦 ˤʤн̤Сɽ롣 - %exh %et ΰ̣㤨ƤΤǽ [󥹥ȡ] - ե (gtkrc, fontrc) ~/.ninix ˥󥹥ȡ뤹 褦ˤ˥ե뤬¸ߤ鲿⤷ʤ - -r (--rcfiles) ץɲáե뤬äƤ 񤭥󥹥ȡ뤹롣 - ʤȤ򥤥󥹥ȡǤ褦ˤ (ǥե Ȥؤ;-) o С 0.3 (2001ǯ731) - shiori.dllֵ١׸ߴ⥸塼Ȥꤢܡޤ ޤ̤εǽ¿沽ˡƤ ޤʡˤ˴ա - ֲפȡֵ١פξб褦˳ƥ⥸塼 o С 0.2 (2001ǯ717) - ǥ꡼ɤѹ̵ o С 0.1.13 (2001ǯ713) - 뤷ɽʤХ - SSTP ΥդƤΤ o С 0.1.12 (2001ǯ79) - üʸ '%' Фʸˡåδ¤ˡ˥Хä ΤǼޤ o С 0.1.11 (2001ǯ79) - 󥹥ȡ (ninix-install ޥ) ˥󥤥󥹥ȡ뵡ǽ ɲáƱ̾Υ//Х롼󤬴˥󥹥ȡ뤵 ƤϼưŪ˺褦ˤΥåϹԤ ʤСġ - Debian ѥå˹碌 Makefile lib/ninix/kawari.py [Sakura Script] - \URL ΥݡȤɲä - üʸ '%' Фʸˡå¤ǧǽʥ᥿ ʸ (%selfname, %keroname ʤ) ƬʳξǤϥХ å奨פʤǤ̾ʸȤư褦ˤ - ǿ Sakura Script λͤ˽򤹤褦˽ʲΥ ̤褦ˤ: \1, \2, \4, \5, \-, \+, \_+, \_l, \_v, \_V, \_b, \__t (顼ˤʤʤǼºݤν̤) - \s \b Υ֥饱å̵ΰ () б [Х롼] - Sender: إå (ʸ) ĹƤμ¤ IP 쥹ɽ褦ˤ [SSTP SEND/1.2] - Entry: إå̵ꥯȤб - ³ڤƤפʤ褦ˤ - 򤵤줿 ID URL ä˥饤Ȥ˥쥹 ݥ󥹤֤ʤХ o С 0.1.10 (2001ǯ75) - twm ʤɰΥɥޥ͡ǥɥΰֻ꤬ ʤninix ޥɤ -p ץɲä - Makefile GNU make γĥǽ (?) Ȥʤ褦ˤ - SSTP SEND/1.2 ΥץȤʤХ [Sakura Script] - \w 륦ȤĹ - \_w ΥݡȤɲä [Х롼] - SSTP ޡ IP ɥ쥹ɽ褦ˤ - SSTP SEND/1.x Option: nodescript б o С 0.1.9 (2001ǯ73) - ˥塼ꤷܤ ~/.ninix/preferences ¸ εưɤ߹褦ˤ - ninix ޥɤ˥ɥư -R (--raise) ɲä(Thanks to Ȥ) - Python 2.1 ȥ顼Ф ninix ưǤʤ (Thanks to Cub ) - ֲ׼ե뤬֥ǥ쥯ȥäƤ르 (̴פʤ) Υ󥹥ȡ˼Ԥ o С 0.1.8 (2001ǯ72) - ImageMagick ѥå convert ޥɤ֤볰 pngtoxpm 碌ƥ󥹥ȡ - 󥹥ȡ˸Ԥʤ -q (--quantize) ץɲ pngtopnm, ppmquant, pnmtopng ɬ (¹Ի˸Ĥ ʤñ˥顼ˤʤ) - եɥޥǥɥåư褦ˤ Х롼ξȸϥեΰưΰ֤˱ƼưŪ ˷ꤹ롣 - EXECUTE/1.0 ͤη(Thanks to ) - ɥ WM_CLASS ѹ (֥ɥޥ͡ פ򻲾ȤΤ) o С 0.1.7 (2001ǯ629) - \q ޤǤʤʤäƤΤ - \e ʹߤΥץȤͭˤʤ o С 0.1.6 (2001ǯ628) - ưΥå EXECUTE/1.2 ͤ (ӥֹ ޤ) ΤʥСֹФ褦ˤ - ˥塼ˡ֥Сפä [Sakura Script] - \z, \y ФΥñ̵뤹롣\q 줿⡼ɤ˰ܤ褦ˤ - %c %username Ʊ˰褦ˤ - user.defaultname Υǥե֥ͤ桼פѹ [Х롼] - maskmethod == 1 ΤȤʸη׻ˡѹǤ ʬʤɤĤΥХ롼¤ǤϤ - ĤǤ⥹СƤޤ (̤Ȥɽ ʤ) - SSTP ޡξä˺ - font.size (font.height), fontcolor.* (font.color.*) ʤɤε ͤ̾å褦ˤ - "--n" ͤб o С 0.1.5 (2001ǯ627) - SSTP ޡ - Х롼ʸΥȿ (Ǥ) ե ɤ褦ˤ - ۥ̾հǤʤ˼¹ԥ顼ä SSTP Ǥʤ - Զ礬褦ʤΤǥС 0.1.3 ǤΥ󥹥ȡ ѹ򸵤ᤷ o С 0.1.4 (2001ǯ627) - gtk.GtkPixmap() θƤӽФäΤŪŤ pygtk 0.6.3 Ǥư褦ˤʤäϥ(Thanks to Ȥ) o С 0.1.3 (2001ǯ626) - 󥹥ȡ (ǸƤӽФƤ convert ޥ) ط 򤦤ޤƩǤʤȤн衣Զ 뤫? o С 0.1.2 (2001ǯ626) - WM_NAME WM_ICON_NAME ꤹ褦ˤ - Python 2.0 ؤΰ¸ʬPython 1.5.2 Ǥ¹ԤǤ 褦ˤʤäƤϥ - 󥹥ȡ˻Ȥä python Υѥ̾ưޥ (ninix, ninix-install) ˽񤭹褦ˤ - ĥ makoto.dll ȤäƤ르Ȥʸ makoto.py ̤褦ˤʲʸ ~/.ninix/ghost ʲȤΥǥ쥯ȥƥ Ȥƥ󥹥ȡ뤷ƤߤƲ o С 0.1.1 (2001ǯ625) - ʸ顼ä˲ѡ̵¥롼פ˴٤ o С 0.1 (2001ǯ625) - ǽΥ꡼ 󥯽 -------- o դߤ (http://sakura.mikage.to/) ֲפܻǤֲ״ϢλͽϤǤޤ o MOON PHASE (http://www.moonphase.cc/) ֲ״ϢΥݡڡǤʥ//Х롼 Ϥ󥯤éǤޤ o SSTP Bottle (http://bottle.mikage.to/) SSTP Ѥå󥰥ӥǤֲץ饤դ256 ܳڤʤޤ o ز Wiz ޤ (http://members.tripod.co.jp/m_a_y_u_r_a/) ȡ֤ޤפΥڡǤ ռ ---- o ֲפФϱʶȹỪͤ o ֲ׳ȯԤ Meister o Τ׳ȯԤζͤ䤮 o ֵ١׳ȯԤΤޤʡˤ o ֳĥ makoto.dll׳ȯԤο򤵤 o SSTP Bottle ȯԤΤʤ뤵Ϥߤ󡢤ڤ ͥϤƤܥȥ顼Τߤʤ o //Х롼ԤΤߤʤ o Python ϤȤϢե꡼եȥγȯԤΤߤʤ ʤʤФΥեȥ¸ߤϤޤǤ ˤ꤬Ȥ 饤 ---------- ܥեȥϡGNU General Public License (С2ʹ) ˴Ťե꡼եȥǤʤϡƱ饤󥹤˽ä եȥͳѤդ뤳ȤǤޤ ܥեȥ̵ݾڤǤܥեȥκԤϡܥեȥ ѤȤˤäǡʤ»ФƤ⤽դ餤 ޤ ---- ỳ̱ ܥեȥФ륳ȡХ𡢥ѥåʤɤ򴿷ޤޤ 嵭Υ᡼륢ɥ쥹ޤǤڤˤ󤻲äˡ o Υ//Х롼ưʤ o ưϥꥸʥΡֲפȰ㤦Τľߤ o ꥸʥΡֲפˤ뤳εǽߤ ʤɤΥեɥХå紿ޤǤꤤޤ ninix-aya-5.0.9/po/0000755000175000017500000000000013416507430012233 5ustar shyshyninix-aya-5.0.9/po/ja/0000755000175000017500000000000013416507430012625 5ustar shyshyninix-aya-5.0.9/po/ja/ninix-aya.po0000644000175000017500000001762413416507430015074 0ustar shyshy# ja.po file for ninix-aya # Copyright (C) 2003-2016 Shyouzou Sugitani # msgid "" msgstr "" "Project-Id-Version: ninix-aya 5.0\n" "POT-Creation-Date: 2015-06-02 22:51+0900\n" "PO-Revision-Date: 2016-08-06 22:35+0900\n" "Last-Translator: Shyouzou Sugitani \n" "Language-Team: LANGUAGE \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" #: ../lib/ninix/nekodorif.rb:77 msgid "Settings...(_O)" msgstr "設定...(_O)" #: ../lib/ninix/kinoko.rb:50 msgid "Skin(_K)" msgstr "スキン(_K)" #: ../lib/ninix/nekodorif.rb:82 msgid "Exit(_Q)" msgstr "終了(_Q)" #: ../lib/ninix/menu.rb:74 msgid "Portal sites(_P)" msgstr "ポータル(_P)" #: ../lib/ninix/menu.rb:77 msgid "Recommend sites(_R)" msgstr "おすすめ(_R)" #: ../lib/ninix/menu.rb:80 msgid "Options(_F)" msgstr "設定(_F)" #: ../lib/ninix/menu.rb:83 msgid "Network Update(_U)" msgstr "ネットワーク更新(_U)" #: ../lib/ninix/menu.rb:87 msgid "Vanish(_F)" msgstr "消滅指示(_F)" #: ../lib/ninix/menu.rb:91 msgid "Preferences...(_O)" msgstr "設定(_O)" #: ../lib/ninix/menu.rb:95 msgid "Console(_C)" msgstr "コンソール(_C)" #: ../lib/ninix/menu.rb:99 msgid "Ghost Manager(_M)" msgstr "ゴーストマネージャ(_M)" #: ../lib/ninix/menu.rb:103 msgid "Information(_I)" msgstr "情報(_I)" #: ../lib/ninix/menu.rb:106 msgid "Usage graph(_A)" msgstr "使用率グラフ(_A)" #: ../lib/ninix/menu.rb:110 msgid "Version(_V)" msgstr "バージョン情報(_V)" #: ../lib/ninix/menu.rb:114 msgid "Close(_W)" msgstr "終了(_W)" #: ../lib/ninix/menu.rb:118 msgid "Quit(_Q)" msgstr "全て終了(_Q)" #: ../lib/ninix/menu.rb:122 msgid "Change(_G)" msgstr "交代(_G)" #: ../lib/ninix/menu.rb:125 msgid "Summon(_X)" msgstr "召喚(_X)" #: ../lib/ninix/menu.rb:128 msgid "Shell(_S)" msgstr "シェル(_S)" #: ../lib/ninix/menu.rb:131 msgid "Balloon(_B)" msgstr "バルーン(_B)" #: ../lib/ninix/menu.rb:134 msgid "Costume(_C)" msgstr "着せ替え(_C)" #: ../lib/ninix/menu.rb:137 msgid "Stick(_Y)" msgstr "デスクトップに居座る(_Y)" #: ../lib/ninix/menu.rb:142 msgid "Nekodorif(_N)" msgstr "猫どりふ(_N)" #: ../lib/ninix/menu.rb:145 msgid "Kinoko(_K)" msgstr "きのこ(_K)" #: ../lib/ninix/nekodorif.rb:80 msgid "Katochan(_K)" msgstr "落下物(_K)" #: ../lib/ninix/ngm.rb:315 msgid "Search for" msgstr "検索文字を入力して下さい" #: ../lib/ninix/ngm.rb:407 msgid "_File" msgstr "ファイル(_F)" #: ../lib/ninix/ngm.rb:408 msgid "_View" msgstr "表示(_V)" #: ../lib/ninix/ngm.rb:409 msgid "_Archive" msgstr "アーカイブ(_A)" #: ../lib/ninix/ngm.rb:410 msgid "_Help" msgstr "ヘルプ(_H)" #: ../lib/ninix/ngm.rb:412 msgid "Search(_F)" msgstr "検索(_F)" #: ../lib/ninix/ngm.rb:416 msgid "Search Forward(_S)" msgstr "次を検索(_S)" #: ../lib/ninix/ngm.rb:420 msgid "Settings(_O)" msgstr "設定(_O)" #: ../lib/ninix/ngm.rb:425 msgid "DB Network Update(_N)" msgstr "DB ネットワーク更新(_N)" #: ../lib/ninix/ngm.rb:429 msgid "Close(_X)" msgstr "終了(_X)" #: ../lib/ninix/ngm.rb:433 msgid "Mask(_M)" msgstr "表示マスク(_M)" #: ../lib/ninix/ngm.rb:438 msgid "Reset to Default(_Y)" msgstr "デフォルトに戻す(_Y)" #: ../lib/ninix/ngm.rb:443 msgid "Show All(_Z)" msgstr "すべて表示(_Z)" #: ../lib/ninix/ngm.rb:486 msgid "Ghost Manager" msgstr "ゴーストマネージャ" #: ../lib/ninix/ngm.rb:523 msgid "Previous" msgstr "前ヘ" #: ../lib/ninix/ngm.rb:531 msgid "Next" msgstr "次ヘ" #: ../lib/ninix/ngm.rb:656 msgid "Install" msgstr "Install" #: ../lib/ninix/ngm.rb:665 msgid "Update" msgstr "Update" #: ../lib/ninix/ngm.rb:711 msgid "Author:" msgstr "作者:" #: ../lib/ninix/ngm.rb:712 msgid "ArchiveTime:" msgstr "最終更新時刻:" #: ../lib/ninix/ngm.rb:713 msgid "ArchiveSize:" msgstr "アーカイブサイズ:" #: ../lib/ninix/ngm.rb:714 msgid "NetworkUpdateTime:" msgstr "ネットワーク更新時刻:" #: ../lib/ninix/ngm.rb:715 msgid "Version:" msgstr "バージョン:" #: ../lib/ninix/ngm.rb:716 msgid "AIName:" msgstr "使用偽AI:" #: ../lib/ninix/ngm.rb:725 ../lib/ninix/ngm.rb:730 msgid "SurfaceList:" msgstr "使用サーフェス:" #: ../lib/ninix/ngm.rb:742 msgid " Web Page" msgstr "公開ページ" #: ../lib/ninix_main.rb:47 msgid "A ninix-aya error has been detected." msgstr "母ちゃん…勘弁!!!!" #: ../lib/ninix_main.rb:48 msgid "Bug Detected" msgstr "だからいったじゃないカーッ!!!" #: ../lib/ninix_main.rb:51 msgid "Show Details" msgstr "詳細" #: ../lib/ninix_main.rb:1522 msgid "Console" msgstr "コンソール" #: ../lib/ninix_main.rb:1526 msgid "Nanntokashitekudasai." msgstr "何とかしてください" #: ../lib/ninix/prefs.rb:136 msgid "Surface&Balloon" msgstr "サーフェス&バルーン" #: ../lib/ninix/prefs.rb:138 msgid "Misc" msgstr "色々" #: ../lib/ninix/prefs.rb:140 msgid "Debug" msgstr "デバッグ" #: ../lib/ninix/prefs.rb:297 msgid "Surface Scaling" msgstr "サーフェス倍率" #: ../lib/ninix/prefs.rb:307 ../lib/ninix/prefs.rb:407 msgid "Default Setting" msgstr "デフォルト設定" #: ../lib/ninix/prefs.rb:316 msgid "Scale Balloon" msgstr "バルーンもいっしょ" #: ../lib/ninix/prefs.rb:320 msgid "Default Balloon" msgstr "デフォルトのバルーン" #: ../lib/ninix/prefs.rb:334 msgid "Balloon Name" msgstr "バルーン名" #: ../lib/ninix/prefs.rb:342 msgid "Always Use This Balloon" msgstr "常にこのバルーンを使う" #: ../lib/ninix/prefs.rb:346 msgid "Font(s) for balloons" msgstr "バルーンのフォント" #: ../lib/ninix/prefs.rb:357 msgid "Translucency" msgstr "透過処理" #: ../lib/ninix/prefs.rb:364 msgid "Use PNA file" msgstr "PNAファイルを使用する" #: ../lib/ninix/prefs.rb:368 msgid "Animation" msgstr "アニメーション" #: ../lib/ninix/prefs.rb:378 msgid "Quality" msgstr "品質" #: ../lib/ninix/prefs.rb:394 msgid "SSTP Setting" msgstr "SSTP 設定" #: ../lib/ninix/prefs.rb:397 msgid "Allowembryo" msgstr "IfGhostに一致するゴーストがいない場合に他のゴーストで再生(SEND/1.4)" #: ../lib/ninix/prefs.rb:401 msgid "Script Wait" msgstr "表示ウェイト" #: ../lib/ninix/prefs.rb:413 msgid "None" msgstr "なし" #: ../lib/ninix/prefs.rb:415 msgid "Fast" msgstr "速い" #: ../lib/ninix/prefs.rb:417 msgid "Slow" msgstr "遅い" #: ../lib/ninix/prefs.rb:425 msgid "Raise & Lower" msgstr "Raise & Lower" #: ../lib/ninix/prefs.rb:432 msgid "Sink after Talk" msgstr "喋り終わると裏へ沈む" #: ../lib/ninix/prefs.rb:436 msgid "Raise before Talk" msgstr "喋る時手前に出てくる" #: ../lib/ninix/prefs.rb:447 msgid "Surface Debugging" msgstr "サーフェスのデバッグ" #: ../lib/ninix/prefs.rb:454 msgid "Display Collision Area" msgstr "当たり判定領域を表示する" #: ../lib/ninix/prefs.rb:458 msgid "Display Collision Area Name" msgstr "当たり判定領域を表示する" #: ../lib/ninix/sakura.rb:533 msgid "Network Update has begun." msgstr "ネットワーク更新を開始しました。" #: ../lib/ninix/sakura.rb:537 msgid "Network Update completed successfully." msgstr "ネットワーク更新に成功しました。" #: ../lib/ninix/sakura.rb:541 msgid "Network Update failed." msgstr "ネットワーク更新に失敗しました。" #: ../lib/ninix/sakura.rb:722 msgid "Sakura&Unyuu" msgstr "さくら&うにゅう" #: ../lib/ninix/sakura.rb:732 msgid "User" msgstr "ユーザーさん" #: ../lib/ninix/sakura.rb:737 ../lib/ninix/sakura.rb:748 msgid "Sakura" msgstr "さくら" #: ../lib/ninix/sakura.rb:756 msgid "Unyuu" msgstr "うにゅう" #: ../lib/ninix/sakura.rb:764 msgid "Tomoyo" msgstr "知世" #: ../lib/ninix/sakura.rb:3000 msgid "I'm afraid I don't have Network Update yet." msgstr "ネットワーク更新、\\w4まだ無いの。" #: ../lib/ninix/sakura.rb:3036 msgid "Vanish" msgstr "消滅指示" #: ../lib/ninix/version.rb:34 msgid "Are igai No Nanika with \"Nin'i\" for X" msgstr "あれ以外の何か with \"任意\" for X" ninix-aya-5.0.9/README.en0000644000175000017500000001161513416507430013102 0ustar shyshy-------------------------- ninix-aya(Function extended version of ninix) -------------------------- What is this? ---------- Ninix-aya ,once known as 'AYA compatible modules and so forth for ninix', is a functionally extended fork of desktop accessory application 'ninix' that runs on Unix OSes. It also includes improvements and bug fixes not included in the original ninix. Now it runs not only on Unix OSes but also on Windows. After version 4.500.x (i.e. 5.x series) it switched to use Ruby. What are necessary to ruy ninix-aya? ---------- Following softwares are necessary to run ninix-aya. It may not work properly if you use older version than confirmed one. - Ruby (http://www.ruby-lang.org/) Language used to develope and run this software. We have confirmed the operation with version 2.5.1 and 2.3.3. * Please install Ruby Installer and Devkit on Windows. - NArray(http://masa16.github.io/narray/) Ruby class library that makes it easy and fast to compute large-scale multidimensional numerical arrays. We have confirmed the operation with version 0.6.1.2 and 0.6.1.1. - Ruby/GTK3 (http://ruby-gnome2.osdn.jp/) Set of Ruby language bindings for the GNOME development environment. We have confirmed the operation with version 3.2.7 and 3.1.0. - GTK+ (http://www.gtk.org/) Multi-platform toolkit for creating graphical user interfaces. We have confirmed the operation with version 3.22.30 and 3.22.11. Version 3.22 or later is mandatory. - Ruby/GStreamer (http://ruby-gnome2.osdn.jp/) This software is not mandatory for ninix-aya to run. Used to play back audio files. We have confirmed the operation with version 3.2.7 and 3.1.0. - GStreamer (http://gstreamer.freedesktop.org/) This software is not mandatory for ninix-aya to run. Used to play back audio files. We have confirmed the operation with version 1.14.1 and 1.10.4. - Rubyzip(https://github.com/rubyzip/rubyzip) Ruby library for reading and writing zip files. We have confirmed the operation with version 1.2.1 and 1.2.0. - Ruby gettext(http://ruby-gettext.github.io/) Ruby library and tools for localization. We have confirmed the operation with version 3.2.9 and 3.2.2. - CharlockHolmes (http://github.com/brianmario/charlock_holmes) This software is not mandatory for ninix-aya to run. Necessary if you would like to run 'ghost' which uses 'MISAKA' library and whose encoding is not Shift-JIS. We have confirmed the operation with version 0.7.5 and 0.7.3. Installation ------------ For Linux distributions such as Debian, we recommend to use offical package. In following sentences we explain how to install from the source archive. And we assume Unix OS. Download source archive of latest version and extract it. There are items in Makefile that specifies following settings. * Global installation destination directory (prefix) * Installation destination directory of files for localization (localedir) * Path for SHIORI .so files such as KAWARI8 and YAYA (shiori_so_dir) * And so on Modify them according to your environment. Then installation will be completed if you execute 'make install' as following. # make install Script to execute ninix-aya is installed. So execute it. $ ninix On Windows environment extract source archive and execute lib/ninix_main.rb with Ruby interpreter. * Steps to install required library by using 'gem'. (You need to install DevKit in advance) gem install narray gem install gtk3 gem install gstreamer gem install gettext gem install rubyzip License ---------- Copyright (C) 2001, 2002 by Tamito KAJIYAMA Copyright (C) 2002-2007 by MATSUMURA Namihiko Copyright (C) 2002-2019 by Shyouzou Sugitani Copyright (C) 2002, 2003 by ABE Hideaki Copyright (C) 2003-2005 by Shun-ichi TAHARA This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License (version 2) as published by the Free Software Foundation. It is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. Contact Information. ------ This software is developed and distruted by Shyouzou Sugitani and MATSUMURA Namihiko (a.k.a. Sakura No Nie). Mail addresses are Shyouzou Sugitani MATSUMURA Namihiko Or access forums of http://osdn.jp/projects/ninix-aya/ (There are part of old developement informations at http://nie.counterghost.net/) Links ------ Home page of Are Igai No Nanika with 'Nin-i' (Something other than that with 'ANY') - Ninix distribution site http://www.geocities.co.jp/SiliconValley-Cupertino/7565/ Home page of ninix-aya developement projcet http://ninix-aya.osdn.jp/ Ninix-aya distribution site http://osdn.jp/projects/ninix-aya/ Concluded. ninix-aya-5.0.9/gen_pot.sh0000644000175000017500000000314213416507430013604 0ustar shyshy#!/bin/sh # mkdir -p pot rxgettext lib/ninix_main.rb -o pot/ninix_main.pot rxgettext lib/ninix/alias.rb -o pot/alias.pot rxgettext lib/ninix/balloon.rb -o pot/balloon.pot rxgettext lib/ninix/communicate.rb -o pot/communicate.pot rxgettext lib/ninix/config.rb -o pot/config.pot rxgettext lib/ninix/dll.rb -o pot/dll.pot rxgettext lib/ninix/entry_db.rb -o pot/entry_db.pot rxgettext lib/ninix/home.rb -o pot/home.pot rxgettext lib/ninix/install.rb -o pot/install.pot rxgettext lib/ninix/keymap.rb -o pot/keymap.pot rxgettext lib/ninix/kinoko.rb -o pot/kinoko.pot rxgettext lib/ninix/lock.rb -o pot/lock.pot rxgettext lib/ninix/logging.rb -o pot/logging.pot rxgettext lib/ninix/makoto.rb -o pot/makoto.pot rxgettext lib/ninix/menu.rb -o pot/menu.pot rxgettext lib/ninix/metamagic.rb -o pot/metamagic.pot rxgettext lib/ninix/nekodorif.rb -o pot/nekodorif.pot rxgettext lib/ninix/ngm.rb -o pot/ngm.pot rxgettext lib/ninix/pix.rb -o pot/pix.pot rxgettext lib/ninix/prefs.rb -o pot/prefs.pot rxgettext lib/ninix/sakura.rb -o pot/sakura.pot rxgettext lib/ninix/script.rb -o pot/script.pot rxgettext lib/ninix/seriko.rb -o pot/seriko.pot rxgettext lib/ninix/sstp.rb -o pot/sstp.pot rxgettext lib/ninix/sstplib.rb -o pot/sstplib.pot rxgettext lib/ninix/surface.rb -o pot/surface.pot rxgettext lib/ninix/update.rb -o pot/update.pot rxgettext lib/ninix/version.rb -o pot/version.pot rmsgcat pot/*.pot -o ninix-aya.pot # cp ninix-aya.pot po/ja/ninix-aya.po and edit ninix-aya.pot # msgfmt po/ja/ninix-aya.po -o locale/ja/LC_MESSAGES/ninix-aya.mo # msgunfmt -o po/ja/ninix-aya.po locale/ja/LC_MESSAGES/ninix-aya.mo ninix-aya-5.0.9/ChangeLog0000644000175000017500000102601313416507430013372 0ustar shyshySun January 13 2019 Shyouzou Sugitani * バージョン5.0.9リリース. Sun January 6 2019 Shyouzou Sugitani * Gtk::ApplicationWindowを生成し、画面サイズ算出に利用するようにした. * Gtk::Applicationを使用するようにした. * AYA, AYA5互換モジュールにおいてFixnum(deprecated)を 使用しないよう修正した. * ウインドウ形状算出処理の負荷対策を実装した. Fri January 4 2019 Shyouzou Sugitani * バージョン5.0.8リリース. * Ruby移植時のミスなどを修正した. * 描画処理の負荷低減を「猫どりふ」、「きのこ」、"easyballoon" まで拡大した. Thu January 3 2019 Shyouzou Sugitani * 5.0.7のバルーンに対する変更から着想を得て 描画処理の負荷低減を更に進め、 適用対象もサーフェスまで拡大した. (「猫どりふ」、「きのこ」、"easyballoon"は対象外.) Wed January 2 2019 Shyouzou Sugitani * バージョン5.0.7リリース. * バルーンの描画処理の負荷を低減した。 * Copyrightを2019年に更新した. Sun July 8 2018 Shyouzou Sugitani * バージョン5.0.6リリース. * gemspecファイルを更新した. Sat July 7 2018 Shyouzou Sugitani * できるだけ広範囲のバージョンのRuby/Gtk3で動作するように調整した. * easyballoon互換モジュールが旧描画方法のままになっていたのを修正した. * CommunicateWindowが旧描画方法のままになっていたのを修正した. * README.enを追加した.(Thanks to Yasuhiro KIMURAさん) Sun April 29 2018 Shyouzou Sugitani * MCIAudio(R)互換モジュールのメッセージ処理を修正した. Sun January 7 2018 Shyouzou Sugitani * バージョン5.0.5リリース. * ウインドウの描画方法刷新によりWayland環境に対応. * Copyrightを2018年に更新した. Wed August 16 2017 Shyouzou Sugitani * deprecatedになっているメソッドを使用しないようにした. (Gtk::Menu.popup) * 「きのこ」互換機能ウインドウの重なり状態制御方法を変更した. * 「猫どりふ」互換機能のマウスボタンイベント処理を修正した. * バルーンとサーフェスの重なり状態制御方法を変更した. * SSTPControlerクラスがHolonクラスを継承するようにした. Sat August 12 2017 Shyouzou Sugitani * バージョン5.0.4リリース. * Ruby-GNOME2 3.1.2対応. Mon June 26 2017 Shyouzou Sugitani * gemspecを追加. Sun April 9 2017 Shyouzou Sugitani * バージョン5.0.3リリース. Sat April 8 2017 Shyouzou Sugitani * Windows環境ではInputShapeを使用しないように修正. * ワークエリアの取得方法を改善.(Gtk 3.22以降が必須となった.) * HolonとMemeクラス拡張時の変更漏れを修正. Sat March 25 2017 Shyouzou Sugitani * Copyrightを2017年に更新した. * Rubyらしいコーディングスタイルへの修正. * HolonとMemeクラスを拡張. Sun December 11 2016 Shyouzou Sugitani * Rubyらしいコーディングスタイルへの修正. * Makotoクラスのテストをtest-makoto.rbに移動. Thu October 20 2016 Shyouzou Sugitani * deprecatedになっているGtkのWidgetを使用しないようにした. (Gtk::ActionGroupとGtk::UIManagerの2つ.) Mon October 17 2016 Shyouzou Sugitani * Pix.create_pixbuf_from_fileの引数:is_pnrのデフォルト値を falseに変更した. * MAYUNAのメニュー生成で落ちる問題を修正した. * deprecatedになっているGtkのWidgetを使用しないようにした. (GtkTearoffMenuItemとGtkImageMenuItemの2つ.) Sun October 16 2016 Shyouzou Sugitani * バージョン5.0.2リリース. * Ruby-GNOME2 3.0対応の漏れを修正. * kinoko.rb, ngm.rbのRuby移行時の変更漏れと変更ミスを修正. * PNR, PNA対応方法を変更.(暫定措置) (pixelsではなくpixel-bytesプロパティを使用するようにした.) Sat October 15 2016 Shyouzou Sugitani * バージョン5.0.1リリース. * Ruby-GNOME2 3.0対応. Tue August 23 2016 Shyouzou Sugitani * Rubyらしいコーディングスタイルへの修正.(7) Fri August 19 2016 Shyouzou Sugitani * Rubyらしいコーディングスタイルへの修正.(6) Thu August 18 2016 Shyouzou Sugitani * Rubyらしいコーディングスタイルへの修正.(5) Wed August 17 2016 Shyouzou Sugitani * Rubyらしいコーディングスタイルへの修正.(4) Sun August 7 2016 Shyouzou Sugitani * バージョン5.0(power cycle)リリース. * Makefileを追加した. * locale/ja.poを更新した. * poファイルの追加・更新用としてgen_pot.shを追加した. * bin/ninix.inを追加した. (Python版との違いは栞の.soファイルのパス設定があること.) Sat August 6 2016 Shyouzou Sugitani * 文5互換モジュールを修正した. * surface.rbのcollision area読み込みを修正した. * ssu.dll互換モジュールの文字コード変換を修正した. * コミュニケートのバグを修正した.(Ruby移行時のregression.) * Rubyらしいコーディングスタイルへの修正.(3) Thu August 4 2016 Shyouzou Sugitani * Rubyらしいコーディングスタイルへの修正.(2) Thu May 12 2016 Shyouzou Sugitani * Rubyらしいコーディングスタイルへの修正.(1) Thu May 5 2016 Shyouzou Sugitani * ネットワーク更新のリダイレクト処理を修正した. * ネットワーク更新のダウンロード処理を修正した. * ssu.dll互換モジュールを修正した. * ゴーストの本体と相方以外のサーフェス処理が動作しなくなっていたのを 修正した. Sun May 1 2016 Shyouzou Sugitani * バージョン4.999.2リリース. * 文4互換モジュールを修正した. * 代入文などが演算子の優先順位に依存しないように括弧を付与した. (実際に一部の式が意図と違う動作になっていたのを修正.) Fri April 29 2016 Shyouzou Sugitani * SakuraScriptの処理を修正した. Mon April 25 2016 Shyouzou Sugitani * バルーンのSSTPメッセージ表示位置が正しく読み込まれていなかったのを 修正した. Sun April 24 2016 Shyouzou Sugitani * ネットワーク更新のイベント処理が動作しなくなっていたのを修正した. * メニューの項目表示/非表示の切り替えが正しく動作していなかったのを 修正した.(ネットワーク更新など) Sat April 23 2016 Shyouzou Sugitani * ssu.dll互換モジュールのtypoを修正した. Wed April 20 2016 Shyouzou Sugitani * メニューのCSS設定をCSS nodes(Gtk+3.20以降の形式)に変更した. Tue April 19 2016 Shyouzou Sugitani * surfaces*.txtの読み込みを修正した. * sakura.rbのループ処理を一部修正した. Mon April 18 2016 Shyouzou Sugitani * メニューのテーマにおいて非推奨のシンボルを使用しないようにした. Sun April 17 2016 Shyouzou Sugitani * 文5互換モジュールを更新した.(栞として動作するようにした.) * 一部の互換SAORIについてモジュール名を修正した. * MCIAudioR互換モジュールの絶対パスの扱いを修正した. Sat April 16 2016 Shyouzou Sugitani * ポップアップメニューのツールチップが表示される度に イベントハンドラ内でGdkPixbufを生成して使用していたのを、 あらかじめ生成してイベントハンドラに結びつけておいたものを 使用するように修正した. * 文5互換モジュールを更新した. Wed April 13 2016 Shyouzou Sugitani * surface定義の例外指定"!"に対応した. Tue April 12 2016 Shyouzou Sugitani * 複数のsurfaces***.txt(***は任意の文字列)の読み込みに対応した. * 追加定義用surface定義に対応した. * READMEファイルを更新した. Sun April 10 2016 Shyouzou Sugitani * 複数サーフェス定義の省略記法に対応した. Sun April 3 2016 Shyouzou Sugitani * Gtk::Windowのinput_shape_combine_regionが使えない環境では shape_combine_regionを使うように修正した. Sat April 2 2016 Shyouzou Sugitani * Gstreamerが動作に必須になってしまっていたのを修正した. * コーディングスタイル修正. (delete_atの使い方を修正した.) Mon March 21 2016 Shyouzou Sugitani * コーディングスタイル修正. (再度例外を起こす場合以外はraiseではなくfailを使うようにした.) Sun February 7 2016 Shyouzou Sugitani * バージョン4.999.1リリース. * Ruby移行以降の修正で漏れがあったもの対応. (コンソールへのドラッグ&ドロップ, 文4互換モジュールの動作改善) Sat February 6 2016 Shyouzou Sugitani * Ruby移行以降の修正で漏れがあったもの対応. (アニメーション処理, バルーン, インストーラ, NGM, 文4互換モジュール, 華和梨7互換モジュールの動作改善) Fri February 5 2016 Shyouzou Sugitani * 華和梨8ローダーを更新した. * Ruby版のYAYAローダーを追加した. Thu February 4 2016 Shyouzou Sugitani * 華和梨8ローダーを複数の華和梨8ゴースト同時起動に対応させた. Wed February 3 2016 Shyouzou Sugitani * Ruby版の華和梨8ローダーを追加した. 動作させるには libshiori.so のある場所を環境変数 LD_LIBRARY_PATH で指定する必要がある. (複数の華和梨8ゴースト同時起動には未対応.) Mon January 11 2016 Shyouzou Sugitani * バージョン4.999.0(shotgun debugging)リリース. Sun January 10 2016 Shyouzou Sugitani * Pixモジュールのget_workareaメソッドを削除した. 代替としてBaseTransparentWindowクラスにインスタンス変数workareaを、 Surface, Sakuraクラスのメソッドとしてget_workareaを追加した. 上記等を使用してPixモジュールのget_workarea使用箇所を変更した. * MOTION_NOTIFYイベントの処理を修正した. (API仕様書に従い、Gdk::Window.get_device_positionではなく Gdk::Event.request_motionsを使うようにした.) * インストールの処理で落ちる場合があるのを修正した. * 高速化のためPixモジュールの画素値演算にはNArrayを使用するようにした. 対象とした処理はPNR, PNA, InputShape(ウインドウ形状)の3つである. (READMEの「必要なもの」にもNArrayを追加した.) * ポップアップメニューが表示される度に各アイテムに対して イベントハンドラ内でGtk::CssProviderを生成して使用していたのを、 あらかじめ生成してイベントハンドラに結びつけておいたものを 使用するように修正した. Tue January 5 2016 Shyouzou Sugitani * バージョン4.500.9リリース. * 使用率グラフ表示で落ちる場合がある問題を修正した. Mon January 4 2016 Shyouzou Sugitani * 美坂互換モジュールのLexerクラスの動作速度を改善した. * シグナル処理のコールバックブロックの戻り値を一部修正した. * メニューの「きのこ」呼び出し部分を修正した. * スクリプト'\5'の処理を修正した. * 里々互換モジュールを修正した. * Surfaceクラスの最大サーフェスサイズ取得メソッドが返す値を 画面サイズで制限するようにした. Sun January 3 2016 Shyouzou Sugitani * シグナル処理のコールバックブロックの戻り値を明示的に与えるようにした. * otherghostnameイベントの引数が間違っていたのを修正した. * アルファチャンネル付きpngに対するpnrの処理を修正した. * 文4互換モジュールを修正した. * 華和梨7互換モジュールを修正した. * 美坂互換モジュールを修正した. Fri January 1 2016 Shyouzou Sugitani * バージョン4.500.8リリース. * Copyrightを2016年に更新した. Thu December 31 2015 Shyouzou Sugitani * 文4互換モジュールを更新した. Wed December 30 2015 Shyouzou Sugitani * コメントを整理した. Tue December 29 2015 Shyouzou Sugitani * 里々互換モジュールを更新した. Mon December 28 2015 Shyouzou Sugitani * 美坂互換モジュールを更新した. Fri December 25 2015 Shyouzou Sugitani * 華和梨7互換モジュールを更新した. * 偽栞互換モジュールを更新した. Mon December 14 2015 Shyouzou Sugitani * バージョン4.500.7リリース. Sun December 13 2015 Shyouzou Sugitani * メニューのツールチップ表示のコードを修正. (メニューを開いていると落ちる場合があったのを修正.) * 5.0リリースに向けてのコード整理を開始. Fri December 11 2015 Shyouzou Sugitani * 美坂互換モジュールにCharlockHolmesによる文字コードの自動判定を実装. Sun December 6 2015 Shyouzou Sugitani * Ruby移行以降の修正で漏れがあったもの対応. (さくらスクリプト処理, 美坂互換モジュールの動作改善) Sun November 29 2015 Shyouzou Sugitani * Ruby移行以降の修正で漏れがあったもの対応. * httpc互換モジュールを修正. Tue November 24 2015 Shyouzou Sugitani * バルーンの選択肢処理を修正. * バルーンのフォントカラー処理を修正. Thu November 19 2015 Shyouzou Sugitani * Ruby移行以降の修正で漏れがあったもの対応. Mon November 16 2015 Shyouzou Sugitani * バージョン4.500.6リリース. * Ruby移行以降の修正で漏れがあったもの対応. Thu November 12 2015 Shyouzou Sugitani * Ruby移行以降の修正で漏れがあったもの対応. Sun November 8 2015 Shyouzou Sugitani * Ruby移行以降の修正で漏れがあったもの対応. Tue November 3 2015 Shyouzou Sugitani * Ruby移行以降の修正で漏れがあったもの対応. Sun November 1 2015 Shyouzou Sugitani * Ruby移行以降の修正で漏れがあったもの対応. * 一部の互換SAORIが動作しない問題を修正した. Tue October 20 2015 Shyouzou Sugitani * to_i, to_fメソッドは例外を起こさないのでbegin~rescueの中では Integer(), Float()を使うようにした.(追加修正) Mon October 19 2015 Shyouzou Sugitani * バージョン4.500.5リリース. * MasterList.xmlが無いとNGMが落ちる問題を修正した.(同様の修正多数.) (Thanks to PaulLiuさん) * Ruby移行時にクラスのインスタンス生成をnewメソッドに 置き換え忘れていた箇所を修正. (Thanks to PaulLiuさん) * to_i, to_fメソッドは例外を起こさないのでbegin~rescueの中では Integer(), Float()を使うようにした. Sat October 17 2015 Shyouzou Sugitani * 文字列のsplitメソッドについて引数limitを明示的に渡すようにした。 * easyballoon互換モジュールの改行を修正した。 Sun September 6 2015 Shyouzou Sugitani * バージョン4.500.4リリース. * 里々互換モジュールを更新した. Sat September 5 2015 Shyouzou Sugitani * 里々互換モジュールを更新した. Sun August 23 2015 Shyouzou Sugitani * 里々互換モジュールを追加した. Sat August 22 2015 Shyouzou Sugitani * バージョン4.500.3リリース. Sun August 16 2015 Shyouzou Sugitani * 美坂互換モジュールを更新した. * 文4互換モジュールを更新した. * 華和梨7互換モジュールを更新した. * sakura.rbの変更漏れを修正した. Sat August 15 2015 Shyouzou Sugitani * 美坂互換モジュールを追加した. * sakura.rbのtypoを修正した. Fri August 14 2015 Shyouzou Sugitani * バージョン4.500.2リリース. * 華和梨7互換モジュールを更新した. Thu August 13 2015 Shyouzou Sugitani * 非推奨のGtk+シンボルを使用しないようにした. * 華和梨7互換モジュールを更新した. Wed August 12 2015 Shyouzou Sugitani * 華和梨7互換モジュールを追加した. Tue August 11 2015 Shyouzou Sugitani * 文4互換モジュールを更新した. * バルーンの"\n[half]"タグの処理を修正した. * 文5互換モジュールを更新した. Fri August 7 2015 Shyouzou Sugitani * バージョン4.500.1リリース. * 移植が完了していない文5互換モジュールが動作しないようにした. Wed August 5 2015 Shyouzou Sugitani * 偽栞互換モジュールを更新した. Mon August 3 2015 Shyouzou Sugitani * 偽栞互換モジュールを更新した. Sun August 2 2015 Shyouzou Sugitani * 偽栞互換モジュールを更新した. * コミュニケートのバグを修正した.(Ruby移行時のregression.) * \qタグのバグを修正した.(Ruby移行時のregression.) Sat August 1 2015 Shyouzou Sugitani * 偽栞互換モジュールを追加した. Mon July 27 2015 Shyouzou Sugitani * 文5互換モジュールを追加した. Sat July 25 2015 Shyouzou Sugitani * 非推奨のGtk+シンボルを使用しないようにした. Tue July 14 2015 Shyouzou Sugitani * バージョン4.500.0(recompile the world)リリース. * Ruby 2.x & Gtk+3 に移行した. (互換栞はAYA Ver.4のみ移行完了. 他は5.0リリースまでに移行予定.) Mon March 24 2014 Shyouzou Sugitani * アルファチャンネルがサポートされていない環境でも Shaped Window が使えるようにgtkhackを修正した. (Thanks to PaulLiuさん) * NGMが文字コードがEUC-JPのMasterList.xmlを読めない問題を修正した. (Thanks to PaulLiuさん) * NGMからのゴーストのインストールが実行されない問題を修正した. Mon November 11 2013 Shyouzou Sugitani * バージョン4.99.18リリース. Sun November 10 2013 Shyouzou Sugitani * 以下のインストールイベントを実装した. OnInstallBegin OnInstallComplete OnInstallFailure OnKinokoObjectInstalled Thu November 7 2013 Shyouzou Sugitani * リフレッシュインストールの場合には, そうであることが分かる様に ファイル削除の警告メッセージを変更した. Tue November 5 2013 Shyouzou Sugitani * プラグインのアンインストール機能を追加した. Mon November 4 2013 Shyouzou Sugitani * \![lock/unlock,repaint]が機能するように修正した. (SSP同様に実行中のスクリプト内でのみ有効とした.) Sun November 3 2013 Shyouzou Sugitani * loggingモジュールへの出力をコンソールにも出すようにした. Wed August 28 2013 Shyouzou Sugitani * バージョン4.99.17リリース. Sun August 25 2013 Shyouzou Sugitani * ユーザーがバルーンを動かしていない場合にも, サーフェスが持つ バルーンのオフセットを無視してしまっていたのを修正した. * misaka.pyのchardetによる文字コード判定で, 結果がasciiと出た場合は そこで終了せずに他のファイルで判定を続行するようにした. * サーフェス切り替え後にはサーフェス位置をリセットするようにした. * passivemode中であってもinputbox, teachbox, communicateboxを 開けるように修正した. Wed August 21 2013 Shyouzou Sugitani * バージョン4.99.16リリース. * アルファチャンネル付きpngに対するpnrの処理が正しく動作して いなかったのを修正した.(_gtkhack導入時のregression.) * surfaces.txtの記述ミス対策を追加した. * 一部のゴーストで最大サーフェスサイズが取得出来ていなかったのを 修正した. Mon August 19 2013 Shyouzou Sugitani * gomi.py: ごみ箱内にリンクがあると落ちる問題を修正した. * aya5.py: ループ条件に整数値も使えるよう修正した. * aya5.py: 条件式の評価が正しく行なわれないケースがあったのを修正した. * SERIKOのbaseメソッドが複数指定されていた場合にサーフェスが 正しく表示されない問題を修正した. Sat August 17 2013 Shyouzou Sugitani * satori.pyでセーブデータの文字コード変換が間違っていたのを修正した. * Python3.3でkawari8.pyがSAORI互換モジュールをアンロードする際に エラーで落ちるのを修正した. * メニューのサイドバー画像が無い場合にエラーで落ちるのを修正した. Thu August 15 2013 Shyouzou Sugitani * 再度メニューのサイドバー画像を使用するようにした. Fri August 9 2013 Shyouzou Sugitani * バージョン4.99.15リリース. Wed August 7 2013 Shyouzou Sugitani * ninix_main.py: Python3.3でプラグインが動作しない問題を修正した. * プラグイン用ダイアログをPython3に対応させた. * 上記修正に合わせてプラグインのstandard versionを2.6に上げた. Tue August 6 2013 Shyouzou Sugitani * dll.py: Python3.3で動作しない問題を修正した. (併せて推奨されないimpパッケージからimportlibに移行した.) Tue July 2 2013 Shyouzou Sugitani * バージョン4.99.14リリース. Sun June 30 2013 Shyouzou Sugitani * 複数ゴースト起動時にメニュー画像が正しく更新されない問題を修正した. * メニューのGtkStyleContextの設定を実行するタイミングを 見直すことで負荷を低減した. * お気に入り/ポータルメニューのバナーやゴースト等のサムネイルが pnrとして処理されていたのを修正した. * メニューのGtkStyleContextの設定でmenu.background.alignmentと menu.foreground.alignmentが反映されなくなっていたのを修正した. * menu.sidebar.alignmentのデフォルト値が間違っていたのを修正した. Sat June 29 2013 Shyouzou Sugitani * メニューのGtkStyleContextの設定方法を変更した. この修正で再びメニューのフォアグラウンド画像を使用するようになった. (サイドバー画像は非対応のまま.) Mon June 24 2013 Shyouzou Sugitani * _gtkhackモジュールを復活させた. (新しいpycairoがリリースされるまでの暫定処置.) gdkpixbuf_get_pixels_array, gtkwindow_set_input_shapeの2つの メソッドを実装している. このメッソドを使うことで現在リリースされているpycairoに パッチを当てなくても動作するように出来る. * lib/ninix/pix.pyで_gtkhackを使用するようにした. * GdkWindowのメソッドraiseの呼び出し方法をraise_()に戻した. (pygobjectが修正された.) Tue February 26 2013 Shyouzou Sugitani * ウインドウ移動後の再描画処理要求をTransparentWindowの方で 生成するようにした. Mon February 25 2013 Shyouzou Sugitani * Gtk.DrawingAreaを持たないBaseTransparentWindowクラスを追加し, TransparentWindowはこのクラスを継承するようにした. * CommunicateWindowはGtk.Windowではなく, BaseTransparentWindowの インスタンスを作成して使うようにした. * SurfaceWindow等で共通して行なっていた処理の幾つかを TransparentWindowに移した. * NGM内でのサーフェス画像描画処理を修正した. Sun February 24 2013 Shyouzou Sugitani * 描画位置の補正処理はTransparentWindowクラス内で全て行ない SurfaceWindow等のクラスでは意識しなくて良いようにした. * Gtk.DrawingAreaの生成とウインドウの背景色を透明に設定する処理を TransparentWindowクラスに移した. * バルーンが見切れた際に描画位置の補正が2重に掛って、文字の位置が ずれる問題を修正した. Fri February 22 2013 Shyouzou Sugitani * バージョン4.99.13リリース. * TransparentWindowクラスをさらに改修し, それに合わせてサーフェスや バルーンなどの描画方法を変更した. (5.x系の描画システムの基本形が完成.) * 「きのこ」などObserverクラスのインスタンスが残っている状態で ゴーストを終了すると落ちる問題を修正した. * サーフェスの大きさの基準としてデフォルトサーフェスではなく 最大のサーフェス画像のサイズを使用するようにした. surfaces.txtのdescriptスコープのmaxwidthの方が大きい場合には そちらを優先する. Sat February 16 2013 Shyouzou Sugitani * TransparentWindowクラスの描画方法を変更した際に, サイズ変更後の ウインドウの移動処理を削ってしまっていたのを戻した. (リビジョン114f4bf202008297488b64a52ea571054f3c914dのコミットで 発生したregressionの修正.) Fri February 15 2013 Shyouzou Sugitani * surfaces.txtのdescriptスコープからSERIKOのバージョン情報を 読み取って使用するようにした. * SERIKO/2.0でstart/stopが動作するように再度修正した. Thu February 14 2013 Shyouzou Sugitani * バージョン4.99.12リリース. * SERIKOのstart/stopメソッドで落ちる問題を修正した. (リビジョンa37a6184e5a8f346a0357a141c9d4393497ff616のコミットに 問題があった.) * バルーンの位置をマウスドラッグで調整できるようにした. 初期化するにはサーフェス上でCtrl-Shift-F10を入力する. Wed February 13 2013 Shyouzou Sugitani * TransparentWindowクラスの描画方法を変更した. - ウインドウが見切れる際のちらつきを低減した. - 特定の条件下でウインドウが再描画されなくなる問題への対策をした. (再現条件はGtk.Window.hide()が呼ばれる時にそのウインドウ上で マウスのボタンを押したままにしていること. 問題の原因は Gtk+とウインドウマネージャのレースコンディション?) * ウインドウの背景色を明示的に指定するようにした. (Gtk.Window.override_background_color()を使用.) * typoを修正した.(scalling -> scaling) * Pythonのビルトイン関数名と同じ変数名を変更した.(dir -> direction) Sat February 9 2013 Shyouzou Sugitani * SERIKO/2.0のstop, alternativestopの両メソッドを実装した. * SERIKO/2.0のパターン処理用の正規表現変更でstartメソッドが 動かなくなっていたのを修正した. * SERIKOのasisメソッドを実装した. * SERIKO/2.0のパターン処理用の正規表現に間違いがあったのを修正した. * SERIKO/2.0のメソッドの一部でパラメータの取得に問題があったのを 修正した. Fri February 8 2013 Shyouzou Sugitani * SERIKO/2.0のalternativestartのID区切り文字として ピリオドとカンマの両方を受け付けるようにした. * SERIKO/2.0のパターン処理用の正規表現を修正した. Thu February 7 2013 Shyouzou Sugitani * SERIKO/MAYUNA/elementのメソッド対応を拡張した. - interpolateとreplaceメソッドを実装した. - reduceをMAYUNA以外でも使えるようにした. - overlayとoverlayfastの動作が同じになっていたのを修正した. Wed February 6 2013 Shyouzou Sugitani * SakuraScriptの\![set,balloonoffset]を実装した. Tue February 5 2013 Shyouzou Sugitani * SSPでSERIKOのintervalに追加されたperiodicを実装した. Mon February 4 2013 Shyouzou Sugitani * バージョン4.99.11リリース. Sun February 3 2013 Shyouzou Sugitani * SakuraScriptの\![bind]を実装した. * \![*], \![sound], \![quicksession]が動かない問題を修正した. (リビジョン06dbd307ba1e0f1652e035cc0bb7e7233d38ad3cのコミットに 問題があった.) * おすすめ/ポータルで名前だけのエントリ(URL以降省略)があった場合に メニューアイテム無効で表示するようにした.(SSP互換) Sat February 2 2013 Shyouzou Sugitani * OnCloseイベントのReference0がshutdownの場合にはスクリプトの 再生終了時点で強制的に終了するようにした. * OnCloseAllイベントを実装した. Fri February 1 2013 Shyouzou Sugitani * マウスボタンイベントの処理を改修した. - OnMouseClock, OnMouseDoubleClickに加えて OnMouseDown, OnMouseDownEx, OnMouseUp, OnMouseUpEx, OnMouseClickEx, OnMouseDoubleClickEx を実装した. - Reference0からReference6まで全てに対応した. (ただし, 現状ではReference6は常に"mouse"が来る.) * OnDressupChangedのReference1が正しくなかったのを修正して, Reference3を追加した. Tue January 22 2013 Shyouzou Sugitani * バージョン4.99.10リリース. Mon January 21 2013 Shyouzou Sugitani * SakuraScriptの\![close,inputbox,ID]を実装した. Sun January 20 2013 Shyouzou Sugitani * SakuraScriptの\![set,autoscroll,enable/disable]を実装した. * SakuraScriptの\x[noclear]を実装した. Wed January 16 2013 Shyouzou Sugitani * サーフェスエイリアス用の正規表現を修正した. Tue January 15 2013 Shyouzou Sugitani * alias.txtおよびsurfaces.txtでchar?.surface.aliasによる \p[2]以降のサーフィスエイリアス定義を可能にした. Mon January 14 2013 Shyouzou Sugitani * installedghostname, installedshellname, installedballoonname イベントを実装した. Sun January 13 2013 Shyouzou Sugitani * バルーンのdescript.txtでSSTPメッセージフォント色を指定する sstpmessage.font.color.[rgb]をサポートした. * OnInitializeおよびOnDestroyイベントを実装した. * シェルのdescript.txtでchar?.bindgroup/char?.menuitemによる \p[2]以降の着せ替え定義を可能にした. * バルーンが本来表示すべき位置とは別の場所に一旦出てから正しい位置に 移動してくることがある問題を修正した. (4.99.6で修正したのとは別の箇所に同じ問題が残っていた.) * ゴーストのdescript.txtでバルーンの標準画像IDを指定する balloon.defaultsurfaceをサポートした. Fri January 11 2013 Shyouzou Sugitani * バージョン4.99.9リリース. Thu January 10 2013 Shyouzou Sugitani * READMEダイアログを閉じる動作がninix-ayaの終了と重なると落ちる 問題を修正した. Wed January 9 2013 Shyouzou Sugitani * バルーン同梱ゴーストのインストールを行なった際には再起動しなくても メニューにそのゴーストだけでなくバルーンも追加されるよう修正した. * READMEダイアログを複数開けるようにした. Tue January 8 2013 Shyouzou Sugitani * SakuraScriptの\![open,readme]を実装した. READMEダイアログはゴースト/バルーンのインストール時にも開く. (シェルやサプリメント等には未対応.) * インストーラがファイル削除の途中で停止する問題を修正した. * バルーン同梱ゴーストの上書きインストールが正しく実行されない 問題を修正した. Mon January 7 2013 Shyouzou Sugitani * お気に入り/ポータルメニューでバナーを表示するようにした. (表示方法はゴースト等のサムネイルと同様.) ローカル(ghost/master/banner/)にファイルがある場合のみ表示する. (URLが渡された場合の画像取得は行なっていない.) Sun January 6 2013 Shyouzou Sugitani * ゴースト/バルーン/サーフェス/着せ替えそれぞれにあるサムネイルを メニューのゴースト召喚/交代, バルーン切り替え, シェル切り替えの 各項目にカーソルを合わせた時に表示するようにした. Sat January 5 2013 Shyouzou Sugitani * バージョン4.99.8リリース. * Copyrightを2013年に更新した. * SakuraScriptの\CをSSTPで実行出来ないタグ (lib/ninix/sstp.py内のPROHIBITED_TAGS)に追加した. 理由はゴーストのトークに追記することが出来てしまうため. * SakuraScriptの\Cを実装した. Fri January 4 2013 Shyouzou Sugitani * ゴーストのdescript.txtの(sakura|kero).seriko.defaultsurface指定を サポートした.(char2以降の指定は以前からサポートしていた.) 指定があるときにはSSPと同様に「0番10番がない」シェルを許容する. * SakuraScriptの\![set,trayicon]を実装した. * タイムクリティカルセッション中はOnMouse系イベントが起きないように Sakuraクラスのnotify_eventメソッドを修正した. Thu January 3 2013 Shyouzou Sugitani * 当たり判定の表示機能に文字列表示を追加した. (\![enter,collisionmode,rect]にも対応.) * SakuraScriptの\![enter/leave,collisionmode]を実装した. Wed January 2 2013 Shyouzou Sugitani * 異常終了後の起動でOnBootイベントのReference6, 7を送信するようにした. ~/.ninix/.lock ファイルにタイマ割り込みを処理中のゴースト名を 書き込み, それが残っているかどうかを見ている. 正常終了時には このゴースト名は消去される. ゴーストを複数起動しても機能するが, 不正確な場合がある. (SERIKO, SAORI, 猫どりふ等のタイマ割り込みが別にあるため, そちらで落ちた場合はこの情報はあてにならない.) Sun December 9 2012 Shyouzou Sugitani * バージョン4.99.7リリース. Fri December 7 2012 Shyouzou Sugitani * menu.background.font.color.[rgb]および menu.foreground.font.color.[rgb]の値が0から255の範囲を出ないよう 制限を加えた.(一部ゴーストでこの範囲外の値が指定されていたため 落ちることがあった.) * 「里々」互換モジュールの暗号化辞書の処理が動かなくなっていたのを 修正した. Thu December 6 2012 Shyouzou Sugitani * ssu.dll互換モジュールが正常に動かなくなっていたのを修正した. Wed December 5 2012 Shyouzou Sugitani * OnBalloonCloseイベントが間違ったタイミングで送られていたのを修正し, OnBalloonBreakおよびOnBalloonTimeoutイベントを実装した. * Sakuraクラスのis_talkingメソッドで落ちる問題を修正した. Mon December 3 2012 Shyouzou Sugitani * SakuraScriptの\![set,*]の処理において引数の数のチェック漏れが あったのを修正した. * SakuraScriptの\![set,alignmenttodesktop,default]を実装した. * メニュー制御のためのShioriResouceの一部に対応した. (「裏子」のメニュー画像が出るようになった.) Sun December 2 2012 Shyouzou Sugitani * バージョン4.99.6リリース. Sat December 1 2012 Shyouzou Sugitani * バルーンが本来表示すべき位置とは別の場所に一旦出てから正しい位置に 移動してくることがある問題を修正した. (移動処理を表示の有無にかかわらず行なうよう修正した.) * メニューが画像よりも大きい場合には画像の右下の端の色で 画像が切れた部分を塗り潰すようにした. * pix.pyにpng画像の右下の端の色情報を取り出すための関数 get_png_lastpixを追加した. * shellのdescript.txtにあるmenu.background.alignment, menu.foreground.alignment, menu.sidebar.alignmentの値を 使用するようにした. * 最後に再生したスクリプトを保持するメンバlast_scriptと 現在のシェル名を取得するメソッドget_current_shell_nameを Sakuraクラスに追加した. * ゴースト間コミュニケーションの機能をOnOther*イベントの処理と 統合した. これにより1つのイベントでコミュニケート対象のゴーストに OnCommunicateとOnOther*の両方が来る問題が解消した. (Communicateクラスのsend_messageメソッドは廃止し, notify_otherに 一本化した.) * OnGhostChangedイベントのReference1, 2, 7の送信を実装した. * 上記変更に併せてOnOtherGhostChangedイベントのReferenceを修正した. * OnVanishedイベントにスクリプトを返さなかった場合に, OnBootへフォールバックする前にOnGhostChangedを試すよう修正した. Fri November 30 2012 Shyouzou Sugitani * dll/aya.py: Python3対応漏れを修正. Wed November 28 2012 Shyouzou Sugitani * Sakuraクラスにスクリプト再生後に処理する内容を入れておくメンバ script_finallyを追加した. 既にあったscript_post_procとは異なり スクリプトがブレークされた場合にも実行される. * Bootイベント後にサーフェスが出ていない場合にデフォルトサーフェスを 出す処理は, Sakuraクラスのscript_post_procではなくscript_finallyに 入れるようにした.(Bootイベントがブレークされた場合にも対応するため.) * OnOther*イベントの送信に必要な機能をCommunicateクラスに実装した. * SakuraScriptの\![set,otherghosttalk]と\![set,othersurfacechange] を実装した. * OnOtherGhostBooted, OnOtherGhostClosed, OnOtherGhostChanged, OnOtherSurfaceChange, OnOtherGhostVanished, OnOtherGhostTalk イベントを実装した. ただし, OnOtherGhostChangedのReferenceは不完全である. (OnGhostChangedのReferenceの追加が完了次第修正予定.) また, OnSurfaceChangeのReference3に「旧サーフェスID」を, Reference4に「新しいサーフェスの大きさ(座標:左,上,右,下)」を 独自に追加した. (上記2点はninix-ayaのOnOther*イベントの生成方法に関係している. 元となるイベントの終了を受けて生成するので, そのイベントの時点で OnOther*の生成に必要な情報が渡されている必要があるため.) Tue November 20 2012 Shyouzou Sugitani * SERIKO baseに設定されているサーフェスを当たり判定領域の基準に するように変更した. Mon November 19 2012 Shyouzou Sugitani * SakuraScriptの\![change,balloon,バルーン名]を実装した. Sun November 18 2012 Shyouzou Sugitani * OnUpdate系イベントのReference情報追加に対応した. * ネットワーク更新イベントのOnUpdate.OnMD5CompareBeginを実装した. * SakuraScriptの\![call,ghost,ゴースト名]を実装した. * OnDisplayChangeイベントが複数回送信される問題を修正した. * OnDisplayChangeイベントの送信の際にキャラウィンドウの位置を 再調整するようにした. * SakuraScriptの\![vanishbymyself]が動かなくなっていたのを修正した. * SakuraScriptの\![vanishbymyself,ゴースト名]に対応した. Sat November 17 2012 Shyouzou Sugitani * SakuraScriptの\![quicksession,true/false]を実装した. Fri November 16 2012 Shyouzou Sugitani * バージョン4.99.5リリース. * SakuraScriptの\![sound]および\_Vを実装した. 再生にはGstreamerを使用する. (\![sound]はplay, cdplay, loop, stop, wait, pause, resume全て対応.) * lib/ninix/dll/mciaudior.py, mciaudio.py: 再生状態を独自に管理するのではなく, 情報をGstreamerから 取得するように修正した. Thu November 15 2012 Shyouzou Sugitani * OnGhostChangingイベントのReferenceの内容を修正した. (Reference0からReference3まで全てに対応した.) * OnGhostCalling(ゴースト呼び出し中)イベントを実装した. Wed November 14 2012 Shyouzou Sugitani * OnDressupChanged(着せ替え変更通知)イベントを実装した. * TransparentWindowクラスのinput_shape_combine_regionの処理を 再度修正した. self.__childの位置のずれをオフセットとして GdkWindowに対してinput_shape_combine_regionを呼ぶようにした. Tue November 13 2012 Shyouzou Sugitani * SakuraScriptの\8(WAV再生)を実装した. * SakuraScriptの\8および\_vをSSTPで実行出来ないタグ (lib/ninix/sstp.py内のPROHIBITED_TAGS)に追加した. Mon November 12 2012 Shyouzou Sugitani * SakuraScriptEnvの%*を実装した. Fri November 9 2012 Shyouzou Sugitani * マウスイベントのOnMouseDragStart(マウスドラッグ開始)および OnMouseDragEnd(マウスドラッグ終了)を実装した. Thu November 8 2012 Shyouzou Sugitani * 終了イベントを補足してゴーストを終了させるようにした. (POSIX環境ではSIGTERMのみ補足し, SIGKILLは即終了になります.) * OnCloseイベントのReference0に終了理由(user|shutdown)を 入れる様にした. Wed November 7 2012 Shyouzou Sugitani * SakuraScriptEnvの%wronghour(正しくない現在時間)を実装した. Tue November 6 2012 Shyouzou Sugitani * OnSurfaceChangeイベントの送信処理をSakuraクラスから SurfaceWindowクラスに移し, SSP同様にReference2を追加した. Mon November 5 2012 Shyouzou Sugitani * 起動時以外にも画面のサイズが変化した際にはその都度 OnDisplayChangeイベントを送る様にした. Sun November 4 2012 Shyouzou Sugitani * SakuraScriptの\z, \yを実装した.(挙動は\eと同じ.) * OnKeyPressイベントのReference2にキーを押したままのときの リピート回数を入れるようにした. * TransparentWindowクラスではinput_shape_combine_regionメソッドは 自分(self)ではなくself.__childに対して呼び出すように変更した. (ウインドウが見切れている場合に, self.__childの位置をselfとは ずらしていることがあるため.) Sun November 4 2012 Shyouzou Sugitani * バージョン4.99.4リリース. Sat November 3 2012 Shyouzou Sugitani * サーフェスのdrag_data_receivedメソッドの戻り値を修正した. * インプットボックス等(CommunicateWindowクラスのウインドウ)に DnDでも入力出来るようにした. * EscキーでTeachBoxを閉じても内部の状態管理がオープンのまま 解除されず, ゴーストの終了等が出来なくなるバグを修正した. Fri November 2 2012 Shyouzou Sugitani * 選択したシェルの情報が正しく記録されないバグを修正した. Thu November 1 2012 Shyouzou Sugitani * dll/mciaudio.py: typoを修正した. * SAORIクラスを抽象化した. * CommunicateWindowクラスを抽象化した. Tue October 30 2012 Shyouzou Sugitani * 入力文字を隠す入力ボックスを開くためのSakuraScript \[open,passwordinput] を実装した. * 入力ボックスなどのコミュニケートウインドウのテキスト入力領域に マウスカーソルが入ると入力フォーカスが外れてしまう問題を修正した. * OnCommunicateInputCancel, OnTeachInputCancel, OnUserInputCancel イベントを実装した. Sun October 28 2012 Shyouzou Sugitani * コンソールへhttp, ftpのURIをDnDすることでもゴーストなどを インストール出来るようにした. Wed October 10 2012 Shyouzou Sugitani * バージョン4.99.3リリース. * dll/gomi.py: ゴミ箱がマウントされているボリュームのトップディレクトリに ある場合にも対応した. Mon October 8 2012 Shyouzou Sugitani * dll/gomi.py: ユーザーのホームディレクトリにあるゴミ箱のパス指定方法を "The FreeDesktop.org Trash specification"に合わせた. (http://standards.freedesktop.org/trash-spec/trashspec-0.8.html) * dll/gomi.py: -f, --force オプションに対応した. Thu September 27 2012 Shyouzou Sugitani * バージョン4.99.2リリース. * READMEのGStreamerに関する記述を更新した. * lib/ninix/dll/mciaudior.py: GStreamer 1.0でループ再生が機能しない問題を修正した. (4.99.xではGStreamer 0.10でのループ再生はサポートしない.) Sun August 19 2012 Shyouzou Sugitani * バージョン4.99.1リリース. Sat August 18 2012 Shyouzou Sugitani * dll/gomi.py: dbus-pythonからGDBusに移行した. * README: dbus-pythonに関する記述を削除した. Thu August 16 2012 Shyouzou Sugitani * バージョン4.99.0(bulletproof)リリース. Wed August 15 2012 Shyouzou Sugitani * README: gomi.pyが使用するdbus-pythonについての記述を追加した. Sat August 12 2012 Shyouzou Sugitani * pix.py: TransparentWindowクラスにupdate_shapeメソッドを追加し, drawイベントの際にはこのメソッドを呼び出すようにした. * pycairoおよびpygobjectのcairo_region_tサポートが無くても エラーが出ないようにした. (cairo_region_tサポートが無い場合にはウインドウ形状が矩形になる.) Fri August 11 2012 Shyouzou Sugitani * READMEを更新した. * doc/saori.txtにgomi.pyに関する記述を追加した. * locale/ja.poを更新した. * Python3移行作業をした. + 今回更新したファイル: setup.py, ninix_win32_postinst.py Fri August 10 2012 Shyouzou Sugitani * プラグインもPython3環境に移行が必要なため, サポートする プラグインのstandard versionを2.5以上にすることで古い プラグインが起動出来ないようにした. * ninix_main.py: optparseからargparseモジュールに移行した. Fri August 10 2012 Shyouzou Sugitani * dll/gomi.py: gomi.dll互換モジュールを追加した. 現状ではGNOME環境のみのサポートで, 動作にdbus-pythonが必要. Wed August 8 2012 Shyouzou Sugitani * ウインドウの形状を指定するタイミングをdrawイベントの処理時点に 変更した. * pix.py: ウインドウサイズの変更時にそれに合わせてGtk.DrawingAreaのサイズも 明示的に変更するようにした. Mon August 6 2012 Shyouzou Sugitani * Python3移行作業をした. + 今回更新したファイル: dll/aya5.py * dll/aya.py, dll/aya5.py: SAORIのロード時に渡すパス名がSHIORIのトップディレクトリだったのを SAORIの置かれているディレクトリ名も含めるよう修正した. Tue July 31 2012 Shyouzou Sugitani * Python3移行作業をした. + 今回更新したファイル: dll/aya5.py * balloon.py: 上下方向についてバルーンが画面外に見切れることがないようにした. Sat July 28 2012 Shyouzou Sugitani * Python3移行作業をした. + 今回更新したファイル: sakura.py Sat July 28 2012 Shyouzou Sugitani * Python3移行作業をした. + 今回更新したファイル: dll/aya.py * dll/aya.py: データベースファイルの文字コードをUTF-8に変更し, フォーマットの バージョンを1.1から2.1に上げた. (EUC-JPを使用しているバージョン1.0および1.1のファイルも読み込める.) Fri July 27 2012 Shyouzou Sugitani * Python3移行作業をした. + 今回更新したファイル: dll/aya.py, dll/httpc.py, dll/kawari.py, dll/misaka.py, dll/satori.py, dll/ssu.py, dll/wmove.py * dll/bln.py: クラス変数timeout_idの初期化タイミングを変更した. * menu.py: Menuクラスの__imagepathクラス変数の内容をファイルのパスから 背景画像の指定するCSSの文字列(bytes)に変更した. Mon July 23 2012 Shyouzou Sugitani * Python3移行作業をした. + 今回更新したファイル: menu.py * menu.py: メニューの背景画像の指定にgtk-widget CSSを利用するようにした. ただし, sidebarは無しでprelightの指定は問題があるため使用せず. メニューが画像より大きい場合の余白の処理も未実装. Sun July 22 2012 Shyouzou Sugitani * Python3移行作業をした. + 今回更新したファイル: install.py, sakura.py, ninix_main.py Sat July 21 2012 Shyouzou Sugitani * Python3移行作業をした. + 今回更新したファイル: alias.py, home.py, plugin.py, prefs.py, sakura.py, sstp.py, sstplib.py, update.py, ninix_main.py * prefs.py: mimetoolsからemailモジュールに移行した. * sakura.py: PyGIを通してGStreameを利用するように変更した. (gst-pythonは不要になった.) * ninix_main.py: バージョン4.2.10で導入したstderrのチェックはPython3では不要なので 削除した. Thu July 19 2012 Shyouzou Sugitani * Python3移行作業をした. + 今回更新したファイル: balloon.py, dll/mciaudio.py, dll/mciaudior.py, dll/niseshiori.py, home.py, pix.py, sstplib.py, surface.py * balloon.py: CommunicateWindowクラスもサーフェスなどと同様に Gtk.Window.input_shape_combine_region()を使用するように変更した. * balloon.py: CommunicateWindowクラスの持つGtk.Entryについてフォントの指定を 追加した. * balloon.py: CommunicateWindowクラスでGtk.FixedではなくGtk.Overlayを 使用するように変更した. * dll/mciaudio.py, dll/mciaudior.py: PyGIを通してGStreameを利用するように変更した. * sstplib.py: mimetoolsからemailモジュールに移行した. Sat July 14 2012 Shyouzou Sugitani * doc/satori.txtの記述を修正した. * Python3移行作業をした. + 今回更新したファイル: config.py, dll.py, dll/bln.py, dll/hanayu.py, dll/kawari8.py, dll/saori_cpuid.py, dll/yaya.py, kinoko.py, nekodorif.py, ngm.py * cairo_region_tを利用してウインドウの形状を指定するようにした. (ただし, pycairoとpygobjectにパッチが必要.) * dll/saori_cpuid.py: cpu.nameの処理が抜けていたのを追加した. * ngm.py: NGMクラスのnextメソッドの名前をgo_nextに変更した. Thu July 12 2012 Shyouzou Sugitani * Python3移行を開始した.(以下は全ファイルで行なう共通の修正.) - 2to3で修正される部分については省略する. - 旧式classは無くなったので親クラスとしてobjectを指定する必要は 無くなった. - ファイルパスとしてはstrではなくbytesクラスのオブジェクトを 使用するようにした. - ファイルパスの文字コード処理にはos.fsencode(), osfsdecode()を 使用するようにした. - 文字列についてstr(Unicode文字列)とbytesの区別を行なった. - 必要に応じて文字コード指定をShift-JISからCP932に変更した. - 結果に整数を必要とする除算は/ではなく//演算子を使うか明示的に intに変換するようにした. + 今回更新したファイル: communicate.py, dll/kawari8.py, dll/textcopy.py, entry_db.py, keymap.py, makoto.py, metamagic.py, script.py, seriko.py, version.py * dll/osuwari.py: 動作しなくなっていたのを修正した. * seriko.py: GLib.get_current_time()ではなく GLib.get_monotonic_time()を使用するようにした. * version.py: バージョンを4.99.0(bulletproof)に上げた. Fri January 19 2012 Shyouzou Sugitani * pix.py: 変数名を修正した. Thu January 18 2012 Shyouzou Sugitani * 一部の画像でpnrの処理が正しく動かない問題を修正した. (処理にはNumpyを使うようにした.) Wed January 18 2012 Shyouzou Sugitani * pnrの処理を高速化した. Wed February 22 2012 Shyouzou Sugitani * バージョン4.3.9リリース. Tue February 21 2012 Shyouzou Sugitani * プラグインの保存していたデータが消える場合があるのを修正した. * プラグインのstandard versionを2.3から2.4に上げた. * プラグインがユーザーの入力を受け取るためのダイアログを追加した. BasePluginにダイアログを開くためのopen_dialogメソッドを追加した. Thu January 19 2012 Shyouzou Sugitani * アルファチャンネル付きpngファイルに対するpnrの処理を高速化した. Mon January 16 2012 Shyouzou Sugitani * バージョン4.3.8リリース. Sun January 15 2012 Shyouzou Sugitani * kawari8.soがSegmentation faultで落ちる場合があったのを修正した. (詳細は http://shy.b.sourceforge.jp/2012/01/14/ninix-aya-with-kawari8%e3%81%ab%e3%83%90%e3%82%b0%e7%99%ba%e8%a6%8b/ を参照.) Sat January 14 2012 Shyouzou Sugitani * SERIKOによるサーフェスの書き換えを抑制するユーザー設定を削除した. * SERIKOアニメーションの品質に関するユーザー設定の下限を 0.1(3fpsに相当)から0.4(12fpsに相当)に引き上げた. Fri January 13 2012 Shyouzou Sugitani * サーフェスとバルーンの透過率をユーザー設定から削除した. (これまでの実装では, 1.0以外の値を設定するとPNAファイルで 設定されたアルファチャンネルが無視されるため. PNAファイルと折り合いの付く形の実装であれば再度追加する 可能性はある.) Thu January 12 2012 Shyouzou Sugitani * Cairoによる描画処理のコードを整理した. Wed January 11 2012 Shyouzou Sugitani * Application.find_ghost_by_name()の引数を2箇所直し忘れていたのを 修正した. Tue January 10 2012 Shyouzou Sugitani * GNOMEデスクトップ環境でSakuraScriptの\![set,wallpaper]を 処理する際にGConfではなくGioを使用するようにした. (python-gconfのインストールは不要になった.) Mon January 9 2012 Shyouzou Sugitani * PyGTK2からPyGI GTK3へ移行した.(gtk3ブランチ) 残っている作業はcairo_region_t, cairo_pattern_tを使用する部分. InputShape(ShapedWindow)とメニューの背景画像が該当する. Mon January 9 2012 Shyouzou Sugitani * easyballoonのマウスドラッグによる移動が表示倍率の影響を 受けないように修正した. Sun January 8 2012 Shyouzou Sugitani * satori.pyの文字コード指定を修正した.(追加) * 猫どりふ互換機能で無駄な描画があったのを修正した. * ngm.pyの文字コード指定を修正した. Sun January 1 2012 Shyouzou Sugitani * Copyrightを2012年に更新した. * PyGTK All-in-one Installer for Windows 2.24.1がリリースされたので それに合わせてKNOWN_ISSUESの内容を更新した. Wed December 28 2011 Shyouzou Sugitani * kawari.pyの文字コード指定を修正した. * niseshiori.pyの文字コード指定を修正した. * satori.pyの文字コード指定を修正した. * aya5.pyの文字コード指定を修正した. * aya5.pyにAYA Ver.4のシステム関数のコードが残っていたのを削除した. Sat December 24 2011 Shyouzou Sugitani * バージョン4.3.7リリース. Fri December 23 2011 Shyouzou Sugitani * SERIKOで位置が変化している状態のサーフェスをマウスドラッグで 移動すると落ちる問題を修正した. Wed December 21 2011 Shyouzou Sugitani * balloon.pyのtypoを修正した. * easyballoon互換モジュールでeasyballoonの全消去('clear')が 機能しなくなっていたのを修正した. Tue December 20 2011 Shyouzou Sugitani * ゴースト起動時のバルーン検索で落ちる場合があるバグを修正した. * aya.pyファイルの文字コードをutf-8に変更した. (内部の処理には従来通りEUC-JPを使用している.) * bln.pyの文字コード指定を修正した. Mon December 19 2011 Shyouzou Sugitani * BOM付きUTF-8で記述された設定ファイル(descript.txt, install.txt, surfaces.txt, plugin.txt)の読み込みに対応した. * デフォルトの文字コードがutf-8であることに依存しないように, 文字コードの指定を厳密にした. (現状では"import gtk"の結果としてutf-8にセットされているが, PyGTK2からPyGI GTK3に移行した場合にはasciiになるため.) ただし, dll/以下のファイルについてはまだ修正していない. Fri December 16 2011 Shyouzou Sugitani * easyballoon互換モジュールの設定ファイルにfont.colorの指定が無い エントリがある場合に落ちる問題を修正した. * nar(zip)アーカイブ内にディレクトリのみの項目がある場合に インストール出来ない問題を修正した. (Thanks to Donさん) * 使用率グラフの表示で落ちる問題を修正した. Sat December 10 2011 Shyouzou Sugitani * バージョン4.3.6リリース. * install.pyに残っていたninix-installコマンドのためのコードを削除した. * Sakuraクラスのifghostメソッドが落ちる問題(typo)を修正した. * Installerを日本語(cp932)のファイル名を含むZIPアーカイブに対応させた. (文字コードを決め打ちしているので他の言語には非対応.) Wed December 7 2011 Shyouzou Sugitani * コメントの単位は[]で括るようにした. * satori.pyが落ちる問題を修正した. (Unicodeに変換した文字列とShift_JISのままの文字列を連結しようとして 落ちていた.) この修正でゴースト「マイマイトーカ」が起動するようになった. * ゴーストと同様にバルーンについてもデータ読み込みを指定した ディレクトリに対してのみ行なえるようにした. (バルーンがインストールされた際にその情報のみを読み込むため.) * HolonとMemeクラスをlib/ninix/metamagic.pyファイルに分離した. それぞれを抽象ベースクラス(ABC)とし, Holonを継承してGhostクラスを Memeを継承してShellMemeとBalloonMemeクラスを作成した. (MemeとHolonの間の継承関係は敢えて作らなかった.) * シェルとバルーンの管理にMemeクラスを使用するようにした. * バルーンのメニューの項目をgtk.RadioMenuItemからシェルと同じ gtk.MenuItemに変更した. Sun December 4 2011 Shyouzou Sugitani * バージョン4.3.5リリース. * Windows環境でもGtk+に渡す場合にはファイル名の文字コードを utf-8に変換するようにした. * 画像ファイルの読み込みが全てpix.py経由になるように修正した. (menu.pyに直接Gtk+を呼んでいる部分があった.) Sat December 3 2011 Shyouzou Sugitani * ゴースト等のインストール先ディレクトリ名の文字コードに Windows環境の場合にはmbcsを, それ以外の環境ではutf-8を 使用するようにした.(これまでは環境によらずutf-8を使用していた.) (Thanks to Donさん) * ユーザーが選択したシェルをSETTINGSファイルに記録しておき, 次回の起動の際にはそのシェルで起動するようにした. そのためメニューの召喚/交代からはシェルの指定を削除した. * シェルとバルーンの情報を管理するためのMemeクラスを追加した. (まだ構想段階で実際には使用していない.) シェル/バルーンを構築するのに必要な情報(baseinfo)とユーザーが シェル/バルーンにアクセスするための情報(menuitem)を保持するが, Holonクラスと異なりシェル/バルーンのinstanceへの参照は保持しない. * Holonクラスのデータ構造を一部変更した. Wed November 30 2011 Shyouzou Sugitani * 既に起動しているゴーストを上書きインストールした場合には ゴーストを再起動するようにした. * Sakuraクラスにデフォルトのシェルを取得するためのメソッドを追加した. * SakuraクラスにIfGhostを処理するためのメソッドを追加した. * ゴーストを管理するデータ構造としてHolonクラスを追加した. ゴーストを構築するのに必要な情報(baseinfo), ゴーストの本体 (Sakuraクラスのinstanceへの参照)とユーザーがゴーストに アクセスするための情報(menuitem)を保持している. Mon November 28 2011 Shyouzou Sugitani * シェル選択のメニュー項目も各ゴーストを管理しているデータ構造に 一緒に格納するようにした. * ゴーストが消滅した場合にも当該ゴーストのメニュー項目が有効になる (選択した場合には落ちる)バグを修正した. Sun November 27 2011 Shyouzou Sugitani * ゴーストの交代/召喚のメニュー項目は各ゴーストを管理している データ構造内に格納し, ゴーストの起動/終了の際に無効/有効を 設定するようにした. ゴースト(A)と(B)が起動した状態で(A)に終了の指示を出してから (A)が終了する前に(B)の上でメニューを開くと(A)のメニュー項目は 無効になっているが, (A)が終了した時点で有効に切り替わるようになった. (これまではメニューを閉じて再度開かないと有効に切り替わらなかった.) Sat November 26 2011 Shyouzou Sugitani * メニューを管理するデータ構造をリストから辞書に変更した. * これまでゴースト毎(正確にはSurfaceクラス毎)に持っていたメニューを Applicationクラスが管理する1つのメニューに統合した. * ゴーストを管理するデータ構造(辞書とリストの2つ)を順序付き辞書1つに 統合した. * ゴーストの交代/召喚メニューのアイコンが表示されなくなっていたのを 修正した. Sat November 19 2011 Shyouzou Sugitani * バージョン4.3.4リリース. Fri November 18 2011 Shyouzou Sugitani * SSTPで送信されたスクリプトの再生中にSHIORIイベントが発生した場合に イベントが正しく処理されない問題を修正した. * バルーンのダブルクリックでSSTP BREAKが発生した場合にはバルーンを 閉じるようにした. Wed November 16 2011 Shyouzou Sugitani * プラグインのstandard versionを2.2から2.3に上げた. * BasePluginにプラグイン終了後も保存されるデータを扱うための set_variable, set_variables, get_variableの3つのメソッドを追加した. * 同じプラグインは複数起動出来ないようにした. Sun November 13 2011 Shyouzou Sugitani * バージョン4.3.3リリース. Sat November 12 2011 Shyouzou Sugitani * BasePlugin.send_sstpメソッドでのSSTPリクエストのバージョン判定を 修正した. * BasePlugin.send_scriptメソッドはsend_sstpメソッドを使うようにした. * sstp.pyに残っていたEXECUTE SSTP/1.5(ninix拡張)のコードを削除した. * EXECUTE SSTPのレスポンスにはリクエストで指定されていた文字コードを 使用するようにした. * EXECUTE SSTPのレスポンスで追加データがある場合に最後のCR+LFが 不足していたのを修正した. Fri November 11 2011 Shyouzou Sugitani * プラグインを実行する際に既にプラグインがimportされていた場合には もう一度読み込み直す(reloadする)ように修正した. Thu November 10 2011 Shyouzou Sugitani * READMEのPythonに関する記述を更新した. Python 2.7が必須になったので動作確認範囲からバージョン2.6を削除した. * SSTPリクエストを受けた時ではなく, SSTPキューからリクエストを 取り出して処理する際にゴーストの存在確認(IfGhost)を行なうように 変更した. * SEND SSTPリクエストとNOTIFY SSTPリクエストは全て1つのキューに入れて 順番に処理していく様にした. (これまではSEND 1.4のみキューに入れて処理していた.) * SEND SSTP/1.4で選択肢インターフェースとIfGhostを同時に使うことが 出来ないバグを修正した. * NOTIFY SSTPリクエストを受けるゴーストの選択の際に保険スクリプトの IfGhostを考慮するようにした. IfGhostのエントリに入っているゴーストが「起動していれば」 そのゴーストに送る.(NOTIFY SSTPはSHIORIイベントの送信なので, SHIORIが動作していない一時起動では駄目.) * プラグインを起動したゴーストのIfGhost名をプラグインに渡すようにした. (BasePlugin.caller['ifghost']に値が入っている.) Mon November 7 2011 Shyouzou Sugitani * BasePluginにSEND SSTPリクエストを送信するためのsend_sstpメソッドを 追加した. * プラグインのstandard versionを2.1から2.2に上げた. * BasePluginのnotify_sstpメソッドのevent引数を省略不可にした. * sstp.pyのNOTIFY SSTP/1.1リクエストの処理でリクエストヘッダーに Eventの指定が無い場合には"400 Bad Request"を返すように戻した. Sun November 6 2011 Shyouzou Sugitani * バージョン4.3.2リリース. Sat November 5 2011 Shyouzou Sugitani * BasePluginクラスのnotify_eventメソッドの名前をnotify_sstpに変更し, NOTIFY SSTP/1.1の仕様を満たす事が出来る様に修正した. * SSTPリクエストで選択肢インターフェースを使用した場合にエラーになる 問題を修正した. (SSTPRequestHandler.handleメソッドの終了時点でソケットが 閉じられてしまうために, レスポンスがhandleメソッドの外側で 行なわれる場合に問題が発生していた.) * BasePluginクラスにninix_homeとcaller変数を追加した. * プラグインのstandard versionを2.0から2.1に上げた. Tue November 1 2011 Shyouzou Sugitani * プラグインのstandard versionチェックが機能していなかったのを修正した. * BasePluginクラス(plugin.py)にNOTIFY SSTP/1.1を送信するための notify_eventメソッドを追加した.(Thanks to Donさん) * sstp.pyのNOTIFY SSTP/1.1に関するバグを2つ修正した.(Thanks to Donさん) (イベントのレスポンスが得られない. 保険反応の指定が無い場合にエラーになる.) Tue October 25 2011 Shyouzou Sugitani * バージョン4.3.1リリース. Sun October 23 2011 Shyouzou Sugitani * Windows環境でプラグインが動作しない問題を修正した. * Windows用インストーラパッケージがメニュー内のショートカットの作成に 失敗する問題を修正した.(typoによるエラー) * Windows用インストーラが作成するメニュー内のショートカットに ターミナルを開いてninix-ayaを実行するショートカットを追加した. Thu October 20 2011 Shyouzou Sugitani * バージョン4.3(juggling eggs)リリース. Wed October 19 2011 Shyouzou Sugitani * メニューから「バージョン情報」を選択してバージョン情報を表示した際に sstpmessage領域をリセットしていなかったのを修正した. * Windows用インストーラパッケージでインストールした際にショートカットを 作成するスクリプトninix_win32_postinst.pyを追加した. Mon October 17 2011 Shyouzou Sugitani * ファイルのpermissionを修正した.(Thanks to PaulLiuさん) lib/ninix_main.py, lib/ninix/dll/wmove.py, lib/ninix/dll/osuwari.py * Windows環境でもgettext(多言語対応)が機能するようにした. 各言語のメッセージファイル(ninix.mo)がインストールされていないと 機能しない. 現在はja, zh_TWの2つの言語のファイルがあり, インストーラパッケージを使用すれば本体と一緒にインストールされる. Sun October 16 2011 Shyouzou Sugitani * locale/ja.poを更新した. * setup.pyを更新した. Sat October 15 2011 Shyouzou Sugitani * ninix-installを削除した. * main.pyの名前をninix_main.pyに変更した. Fri October 14 2011 Shyouzou Sugitani * ninix/dll/以下のファイルをインポートする際のパスの取得方法を変更した. これまではmain.pyの場所(sys.path[0])から求めていたが, dll.pyに追加したget_path関数はninixパッケージの場所 (ninix.__path__[0])から求める. この変更で, main.pyとninixパッケージ(ninix/以下のファイル)を 別の場所にインストールしても動作するようになった. * sstplib.pyをninix/以下に移した. Thu October 13 2011 Shyouzou Sugitani * Windows環境ではゴーストの持つDLLを優先して使用するようにした. (現状では互換SHIORIモジュールは一切使用しない. 将来はDLLと互換モジュールをユーザーが切り替えられるようにする予定.) Wed October 12 2011 Shyouzou Sugitani * READMEを更新した. * doc/extension.txtを更新した. * プラグインをPOSIX/Windows両環境で動作する形で実装し直した. (Pythonのmultiprocessingモジュールを使用している.) * 「猫どりふ」「きのこ」のinstallメソッドが戻り値として インストール先のディレクトリ名を返していなかったのを修正した. Sun October 9 2011 Shyouzou Sugitani * バージョン4.2.10リリース. * READMEのPythonに関する記述を更新した. (動作確認範囲にバージョン2.7.2を追加.) * \pタグを[]無しでも使えるようにした. (非推奨のはずだが, Emilyも使っているので. というのは冗談で, 使えるように実装していたはずが, 正規表現の方で\pタグを 間違った所に入れていたため使えなかっただけ.) Sat October 8 2011 Shyouzou Sugitani * Windows環境でpythonw.exeを使用した場合に, 動作が不安定になって 落ちる問題への対策をした.(Thanks to Donさん) (起動時にloggingのデフォルト出力先であるstderrをチェックし, 使用出来ないと判断した場合にはログをstderrに送らないようにした.) * win_dll.pyで実行環境が32bitか64bitかをチェックするようにした. 64bitのPythonではDLLを利用出来ないと判断する. (現状ゴーストに入っているSHIORI DLLは32bitのはずなので.) 注意: 64bit Windows上の32bit Pythonは実行環境としては32bitなので DLLを利用出来る.(今回使用した判定も32bitを返す.) * yaya.pyからWindows環境でDLLを利用するためのコードを削除した. (Windows環境にはwin_dll.pyの方で対応する.) * Windows環境でwin_dll.pyがデフォルトで動作するようにした. SHIORI互換モジュールとどちらが優先されるかは, 場合によって異なる. (この点についてはさらに調整が必要である.) Fri October 7 2011 Shyouzou Sugitani * KNOWN_ISSUESにIDLEから起動した場合の問題に関する記述を追加した. * READMEからhttplib2に関する記述を削除した. * httplib2の使用をやめ, 以前のコードと同様の処理に戻した. Sun October 2 2011 Shyouzou Sugitani * バージョン4.2.9リリース. * win_dll.pyを追加した. このモジュールはWindows環境でゴーストの持つ任意のSHIORI DLLを 使用することが出来るようにする. ただし, 動作チェックが不十分なため現在は動作しないようにしてある. (使用するにはwin_dll.pyのDEFAULT_SCOREの値を変更する必要がある. 互換モジュールとどちらが優先されるかは, このスコアの値次第である.) * SHIORI互換モジュールのレスポンスにCharsetエントリを追加した. * 本体とSHIORIのやりとりでの文字コードの処理方法を変更した. (各リクエスト毎のCharsetエントリを使用する. ninix-aya独自のSHIORIロード時の文字コードの問い合わせは削除した.) * ネットワーク更新でSHIORI DLLが更新された場合でもエラーにならない ようにするために, バックアップファイルを消去するタイミングを ゴーストの再起動のためにSHIORIをアンロードした後に変更した. (Thanks to Donさん) Sat October 1 2011 Shyouzou Sugitani * ゴースト with バルーンのバルーンインストール先ディレクトリ名が 常に"balloon"になっていたのを修正した.(Thanks to Donさん) * YAYAゴーストを複数起動した状態から1体でも終了すると, 残ったゴーストのYAYAが動作しなくなる問題を修正した. (Windows環境とPOSIX環境で起きる現象は同じだがその理由が異なるため, 修正内容は環境によって違っている.)(Thanks to Donさん) Fri September 30 2011 Shyouzou Sugitani * バージョン4.2.8リリース. * sakura.pyのtypoを修正した.(Thanks to Donさん) * surfaces.txtのサーフェススコープ名の列記と省略形に対応した. * surfaces.txtの同じIDのsurfaceエントリの分割に対応した. * yaya.py: Windows環境ではゴーストの持つyaya.dllをロードするようにした. (DLLはリネームされていてもOK.) POSIX環境ではこれまで通りlibaya5.soをロードする. Tue September 27 2011 Shyouzou Sugitani * バージョン4.2.7aリリース. * 「何とかしてください」ウインドウが開くと落ちる問題を修正した. (4.2.7でのregression.)(Thanks to Donさん) * シェルを認識出来ないゴーストについてはとりあえず無視するようにした. (Thanks to Donさん) * Installerクラスのinstallメソッドが常にインストール先の ディレクトリ名を返すようにした. Sun September 25 2011 Shyouzou Sugitani * バージョン4.2.7リリース. Sat September 24 2011 Shyouzou Sugitani * Installerクラスのinstallメソッドの戻り値にインストールした アーカイブの種別を追加した. ゴーストやバルーン等を管理しているリストの更新は, 起動時と インストール/ネットワーク更新があった場合にだけ行なうようにした. * dll.py: ゴーストのディレクトリ内でSHIORIを探す処理が起動中のゴーストの 動作に影響を与えないように修正を加えた. (これまでは起動時のみサーチをしていたので問題無かったが, 本体でゴーストをインストール出来るようになったので, 起動後にもゴーストのインストールの度にサーチが発生する.) * 「きのこ」のアーカイブがインストール出来なくなっていたのを修正した. Sun September 18 2011 Shyouzou Sugitani * NARアーカイブ内のinstall.txtでtype,supplementが指定されている場合に インストール出来ない問題を修正した. * Python3への移行の準備として以下の変更を行なった. (現在の動作環境はまだPython2.6もしくは2.7である.) ただし, 廃止予定のninix-installについては変更を加えていない. Python2での最適なコーディングからは外れる部分があるので, その部分についてはパフォーマンスが4.2.6よりも低下した可能性が ある.(下の項目の中で文末に(*)を付記したもの.) - exceptの変数指定には","ではなく"as"を使用するようにした. - %による文字列フォーマットはやめて文字列のformatメソッドを 使用するようにした. - dict.keys()等のPython3でlistではなくviewを返すようになる メソッドに対してはlistのsortメソッドではなくsorted関数を 使用するようにした. - dictのiterkeys, itervalues, iteritemsメソッドを使用していた所は それぞれkeys, values, itemsメソッドに変更した.(*) - dictのkeys, values, itemsメソッドを使用している箇所の中で listを必要としている所では明示的にlist()を使用するようにした. - filterとmap関数をリストの内包表記で置き換えた. Fri September 9 2011 Shyouzou Sugitani * バージョン4.2.6リリース. * kinoko.pyの変更漏れを修正した. * コーディングスタイルの微調整をした. Thu September 8 2011 Shyouzou Sugitani * 全てのクラスをNew-style classにした. * 下位のクラスから上位のクラスのメソッドを呼び出す方法を変更した. (現状ではこれまでの実装よりもオブジェクト間の結合が強くなったが, 必要なら各クラスのhandle_requestメソッド内で調整可能.) * update.pyでhttplib2.Httpにキャッシュの保存ディレクトリとして ".cache"を指定していたのを削除した. (これだとninix-ayaを実行したディレクトリにキャッシュを作成して しまうので.) Tue September 6 2011 Shyouzou Sugitani * prefs.py: corner caseの処理を追加した. Mon September 5 2011 Shyouzou Sugitani * prefs.py: 値の変更を記録しておいて後で破棄/確定するための機能を Preferencesクラスに追加した. * 前回最後に起動していたゴーストの記録にはゴーストの名前ではなく インストール先ディレクトリ名を使用するようにした. (互換性のため, ディレクトリ名での記録が無く名前での記録がある場合には 名前で探すようになっている.) また, シェルの選択が記録に反映されなくなっていたのを修正した. * lib/ninix/install.py: ファイルのpermissionを修正した. Sun September 4 2011 Shyouzou Sugitani * Observer関連のメソッドを修正した. (SakuraクラスでのObserverの管理データはlist型からdict型に変更.) * アイコン化解除イベントが間違って発行される場合があったのを修正した. Sat September 3 2011 Shyouzou Sugitani * コード全体をリファクタリングした. - New-style classの使用を拡大.(property使用を増やした.) - リストの内包表記を積極的に使用. * setup.pyのためにMANIFEST.inを追加した. *lib/main.py: 変数名の変更忘れがあったのを修正した. *install.py: コンソールからバルーンをインストールすると落ちる問題を修正した. * \n[half]を使用するとバルーンの表示がおかしくなることがあったのを 修正した. (高さが半分に変更された行がスクロールで表示範囲外に出た後に 表示領域内の行の位置を修正する処理が抜けていた.) * easyballoon互換モジュールで表示されるバルーンもサーフェスの 倍率変更に即追従するようにした. Sun August 28 2011 Shyouzou Sugitani * バージョン4.2.5リリース. Sat August 27 2011 Shyouzou Sugitani * バルーンとサーフェスを隠す場合にはgtk.DrawingAreaを隠すのではなく, gtk.Windowを隠すように戻した. (良く見ると分かるが, gtk.DrawingArea.hide()しても1ピクセル分の ウインドウが画面に残っていた.) Fri August 26 2011 Shyouzou Sugitani * TransparentWindowにSurfaceWindowの機能の一部を移し, 改良を行なった. (ウインドウをワークエリア外に出せない仕様のウインドウマネージャでも 画面の端を越えるウインドウの移動と同様の表現が出来るようにする機能が 中心. バグ修正も含む.) TransparentWindowを使用しているバルーン, 猫どりふ, きのこ, easyballoonも変更の影響を受けており, 倍率変更等の際のウインドウの 位置計算が改善された. * バルーンの位置計算を簡素化した. * バルーンと猫どりふもサーフェス同様に画面から見切れることが出来るよう 変更した. * サーフェスと猫どりふに位置を初期化する機能(Ctrl-Shift-F12)を実装した. それぞれキーボードフォーカスがある状態で上記のキーを入力すると 初期位置に戻る. * easyballoonのウインドウをgtk.WINDOW_POPUPからgtk.WINDOW_TOPLEVELに 変更した. Sat August 20 2011 Shyouzou Sugitani * バージョン4.2.4リリース. Fri August 19 2011 Shyouzou Sugitani * 見切れ/重なり判定はサーフェスの位置に変化があった時にだけ 計算するようにした. Thu August 18 2011 Shyouzou Sugitani * コンソール/「何とかしてください」ウインドウのInstallボタンを押して 出てくるファイル選択ダイアログで, nar(zip)以外のファイルを指定すると 落ちる問題を修正した. * install.pyでファイルのダウンロード時に落ちる問題を修正した. * NGMのゴーストインストール機能をinstall.pyを使って再実装した. * BalloonDescriptのwindowposition.x, windowposition.yに対応した. (「ねこことショータRX」同梱のバルーン「ねこのきもち」の \1側の表示位置が修正された.) Wed August 17 2011 Shyouzou Sugitani * 栞互換モジュールのエラー等のログの出力全てにloggingモジュールを 使うように変更した. * --debugオプションが値を取らないように変更した. オプションを指定するとloggingモジュールを使用したログ出力のレベルが logging.DEBUGになる. Mon August 15 2011 Shyouzou Sugitani * Windows環境用にsetup.pyを追加した. ("python.exe setup.py bdist_wininst"でWindows用にパッケージ化される. py2exeについては未テスト.) Sun August 14 2011 Shyouzou Sugitani * バージョン4.2.3リリース. Sat August 13 2011 Shyouzou Sugitani * ネットワーク更新完了後のゴーストリロードで落ちる問題を修正. (4.2.2でのregression.)(Thanks to Donさん) * サプリメントのインストール先ゴースト(acceptに対応)が複数見付かった 場合にはユーザーに選択を求めるようにした. それに伴ないninix-installの-S(--supplement)オプションは廃止した. * エラー等のログの出力全てにloggingモジュールを使うように変更した. (栞互換モジュールは未対応.) --logfileオプションで指定したファイルに出力することも可能. Wed August 10 2011 Shyouzou Sugitani * バージョン4.2.2リリース. Tue August 9 2011 Shyouzou Sugitani * メニューの「設定」からコンソールを開けるようにした. * install.pyをゴーストwithバルーンのinstall.txt内での balloon.source.directory指定に対応させた. Mon August 8 2011 Shyouzou Sugitani * lib/ninix-install.pyを基にlib/ninix/install.pyを作成した. (Installerクラスを作成し, ninix-installもこれを使うようにした.) * ninix-installの-L(--lower)オプションを削除した. * コンソール/「何とかしてください」ウインドウからのインストールは ninix-installを呼ぶのではなく, Installerクラスを使用するようにした. * コンソール/「何とかしてください」ウインドウにInstallボタンを追加した. このボタンを押すとファイル選択ダイアログが開き, 選択したファイルが インストールされる.(ファイルのDnDもこれまで通りサポートしている.) * KNOWN_ISSUESファイルを追加した. (問題を抱えているソフトウエアの情報を記述している.) * READMEのpygtk(GTK+)に関する記述を更新した. * ninix-aya内部でのゴースト, バルーン, シェルの識別にはそれぞれの インストール先ディレクトリ名を使用するようにした. (ファイルシステムにより一意性が保証される.) * ネットワーク更新が落ちる場合があったのを修正した. (4.1.12でのregression.) * satori.pyが落ちる場合があったのを修正した. (φエスケープを処理するためのバッファの初期化に問題があった.) Wed August 3 2011 Shyouzou Sugitani * バージョン4.2.1リリース. * ninix-installでバルーンがインストール出来なくなっていたのを修正した. * Windows環境でninix-install, ネットワーク更新が落ちる場合があったのを 修正した. * READMEの「必要なもの」にpywin32の記述を追加した. Wed July 27 2011 Shyouzou Sugitani * バージョン4.2(voodoo programming)リリース. 必要なソフトウエアが全て揃っていれば, Windows環境でも 動作することを確認した. Tue July 26 2011 Shyouzou Sugitani * GStreamerがインストールされていなくてもninix-ayaが動くようにした. Gstreamerが無ければ音声ファイルの再生は機能しない. (READMEの「必要なもの」のGStreamerの記述を変更した.) Mon July 25 2011 Shyouzou Sugitani * ファイルのロックに関するコードをlib/ninix/lock.pyとして分離した. (現在のところmain.pyとaya.pyが使用している.) * Windows環境でのファイルのロックがエラーで落ちる問題を修正. * 長い間ほとんど使用されることのなかったプラグインシステムを削除した. それに伴いdoc/extension.txtを更新した. (将来的には違う形でのプラグインの実装を考える. ethos等のライブラリの使用も検討する.) * main.pyとninix-install.pyのシェバン行(#!で始まる行)を削除した. Sat July 23 2011 Shyouzou Sugitani * main.pyをlib/ninix/からlib/に移した. * デフォルトのSSTPポートから11000を削除した. (デフォルトでは9801のみとした.) * 環境変数NINIX_SSTP_PORTをを削除した. * 環境変数NINIX_ARCHIVEとninix-installのコマンドラインオプション -A(--arcdir)を削除した. * 環境変数NINIX_HOMEおよびninixとninix-installのコマンドライン オプション-H(--homedir)を削除した. ninix-ayaのホームディレクトリはこれまでのデフォルト~/.ninixで 固定とした.(要望があればオプションで変更可能にします.) * 環境変数NINIXを削除した. * pygst.require()はsys.pathを改変してしまうため, 実行しないようにした. * 環境変数PYTHONPATHにninixのインストール先を指定していたのを削除した. dll/以下のファイルについてはmain.pyの場所(sys.path[0])を基にして 探すように変更した. * 標準出力(sys.stdout)と標準エラー出力(sys.stderr)へのメッセージ出力を やめて, loggingモジュールを使用するようにした. (printを使用している部分の中にもloggingを使うべき所があるが, printについては今回はそのままにした.) * pix.py: Windows環境で画面サイズの取得(get_workarea)の際に落ちる問題を修正. * pix.py: 画像ファイルをopen()する際のモード指定にb(バイナリ)を追加した. (Windows環境でも動作するようにするため.) Sat July 16 2011 Shyouzou Sugitani * バージョン4.1.12リリース. Wed July 13 2011 Shyouzou Sugitani * satori.py: ランダムトークの参照"()"が正しく処理されていなかったのを修正した. (4.1.5でのregression.) (「シズクと冷しゃぶ」のメニュー「話して」が動かなくなっていた.) Sun July 10 2011 Shyouzou Sugitani * main.py: 使用率グラフの表示で落ちる問題を修正.(4.1.4でのregression.) * aya5.py: AyaFunction.evaluate_token()で配列の処理が一部抜けていたのと, 関数の引数に配列が来た場合の処理を修正. (「橘花」(taromati2)で音楽ファイルの再生が機能するようになった.) * sakura.py, mciaudio.py, mciaudior.py: GStreamerに渡すファイル名にマルチバイト文字が入っている場合に ファイルが認識されない問題を修正した. * update.pyがhttplibではなくhttplib2を使用するように変更した. この変更に伴いhttplib2を必須ライブラリにした. (READMEの「必要なもの」のhttplib2の記述を変更した.) * main.py, aya.py: ファイルのロック方法を環境によって変えるようにした. POSIX系OSではこれまで通りfcntlモジュール, Windows環境では win32fileモジュールを使用するようにした. (ninix-ayaがWindows環境で動くようになるには, まだ変更が必要.) * GTK+等を使用する部分でPythonモジュールのインポートの前に 環境変数DISPLAYをチェックしていたのを削除. * dll.py, sakura.py: 文字列処理でstripメソッドを呼ぶ前にUnicodeへの文字コード変換を するように処理の順番を変更した. * StringIOを使用しなくても文字列のsplitlinesメソッドで処理出来る 部分についてはStringIOを使用しないように変更した. * 全てのファイルについてコーディングスタイルの調整を行なった. * 一部の例外を除いてファイルのopen()にはwithステートメントを 使用するように変更した. Mon July 4 2011 Shyouzou Sugitani * バージョン4.1.11リリース. * httpc.py: 致命的なバグ(引数3つの場合に無限ループに陥ることがあったの)を修正. * balloon.py: PNAファイルを使用する等して半透明になった部分でマウス/キーボードの 入力イベントが発生しなくなっていたのを修正. (インプットマスクの設定ミス.) Sun July 3 2011 Shyouzou Sugitani * バージョン4.1.10リリース. * yaya.py: 使用する文字コードをyaya.txtから取得する部分を修正. Sat July 2 2011 Shyouzou Sugitani * aya.py, aya5.py: 栞にYAYAを使用しているゴーストに対する栞判定のスコアとして 0(動作しない)を返すようにした. * yaya.py: Henryさんが作製したバージョンで置き換えた. (ctypesモジュールを使用してlibaya5.soをロードする. ninix-ayaの持つSAORI互換モジュールの呼び出しには未対応.) * ninix-install.py: readme.txtの入っていないゴーストアーカイブのインストールで 落ちる問題を修正. * doc/saori.txt: typoを修正. Tue June 28 2011 Shyouzou Sugitani * バージョン4.1.9リリース. * satori.py: 4.1.5で削除したParser.split()の呼び出しが残っていたのを修正. (Thanks to Paul Liuさん) * satori.py: Satori.get_reserved()の戻り値の文字コードが1箇所だけ utf-8になっていたのを修正. * saori_cpuid.py: OS情報の取得にsys, osではなくplatformを使用するように変更. Sun June 26 2011 Shyouzou Sugitani * バージョン4.1.8リリース. * satori.py: 4.1.5で入ったregressionを修正.(Thanks to Paul Liuさん) Sun June 5 2011 Shyouzou Sugitani * バージョン4.1.7リリース. Tue May 31 2011 Shyouzou Sugitani * ninix-install.py: ファイル操作にos.system()を使って外部コマンドを呼び出していたのを Pythonの標準ライブラリで処理するよう変更. Sat May 28 2011 Shyouzou Sugitani * ninix-install.py: ファイルを展開する一時ディレクトリをユーザーが指定するオプション (--tempdir)を削除.(必要なら環境変数で設定可能.) * 環境変数NINIX_USERと関連するオプションを削除. 設定ファイルの置き場所もNINIX_HOMEに統一. (元々デフォルトではNINIX_HOMEと同じだった.) Tue May 17 2011 Shyouzou Sugitani * README, NEWSを更新した. * locale/ja.poを更新した. Mon May 16 2011 Shyouzou Sugitani * バージョン4.1.6リリース. * 「何とかしてください」ウインドウ(仮)を追加. (ゴーストもしくはバルーンが存在しない場合にのみこの名前で現われる.) ウインドウに対してアーカイブをDnDすることでインストールが出来る. (現在はninix-installを呼び出してインストールを行なう. ファイルダイアログでアーカイブを選んでのインストールにも対応予定.) 将来はインストール機能の他に起動メッセージ等の表示に使う予定. Sat May 7 2011 Shyouzou Sugitani * バージョン4.1.5リリース. Fri May 6 2011 Shyouzou Sugitani * satori.py: ファイルの文字コードをeuc-jpからutf-8に変更. * satori.py: 内部処理で使用する文字コードをEUC-JPからUnicodeに変更. * satori.py: φエスケープの実装を開始. Fri March 25 2011 Shyouzou Sugitani * satori.py: 引数区切りの追加と削除に対応. * satori.py: SAORIの返り値は通常の変数とは別に管理するようにした. * satori.py: 変数を評価する際のサーチ順序を変更. 本体からのリクエスト情報(R0やS0とか)を通常の変数より先にした. Thu March 24 2011 Shyouzou Sugitani * READMEの「必要なもの」を更新. * httpc.py: SAORI/1.0でValue[0]の様に余分な括弧が付いていたのを修正. * httpc.py: httplibとurlparseではなくhttplib2を使用するように変更. * httpc.py: chardetの使用方法を変更.(処理時間を短かくするため.) * satori.py: 内部関数nopで引数が評価されていなかったのを修正. * satori.py: SAORIの結果について文字コード変換が抜けている部分があったのを修正. Wed March 23 2011 Shyouzou Sugitani * satori.py: Filterクラスの内部で使用する文字コードをUnicodeにすることで 処理を簡素化. * satori.py: 変数への代入で右辺が常に計算式として処理されていたのを修正. ("="を使用した場合のみ計算式として処理するように修正.) * satori.py: 関数/SAORI呼び出しの引数を区切る処理で引数内に()がある場合も 正しく処理するよう修正. * satori.py: SAORI呼び出しの引数計算で+-の符号のみの場合には文字列として 処理するように修正. * satori.py: 計算式内に小数点を含む数値がある場合に落ちる問題を修正. (計算自体は整数で行なう.) Tue March 22 2011 Shyouzou Sugitani * httpc.py: 単純なミスを2つ修正. (forループの範囲指定と文字列のjoinの引数の括弧の付け忘れ.) * mciaudio.py, mciaudior.py: 再生の前にファイルのパスが設定されているかをチェックするように修正. Fri March 4 2011 Shyouzou Sugitani * satori.py: 内部関数nop, whenを追加. * satori.py: 条件式の結果は全角数字0/1で返すよう修正. * satori.py: 引数区切り文字は"," "," "、" "、" バイト値1の5つの中で 最初に出てきたものだけを使うよう修正. Wed February 23 2011 Shyouzou Sugitani * バージョン4.1.4リリース. * READMEの「必要なもの」を更新.(UnZip, Numerical Pythonを削除.) Tue February 22 2011 Shyouzou Sugitani * シェルの変更の場合にはゴーストの再起動をしないようにした. Mon February 21 2011 Shyouzou Sugitani * ninix-installに-r(--rebuild)オプションを追加. 4.1.3以前のninix-installでインストールしたバルーン付属ゴーストの バルーンをballoon/以下に移動する. 4.1.4を起動する前に実行しておくことを推奨.(一回だけ実行すればOK.) * シェルの変更はメニューの「交代」ではなく「シェル」から 行なうようにした. * デフォルトでPNAファイルを使用するように設定を変更. (最初にninix-ayaを起動した時に設定されるデフォルトです. 既にninix-ayaを起動している場合には, PNAファイルを 「使用しない」ようにデフォルトで設定されています. 値の変更はメニューの「設定」から出来ます.) Sun February 20 2011 Shyouzou Sugitani * ninix-installでゴーストのreadme.txtとthumbnail.png(pnr)も インストールするようにした. * ゴースト個別の設定ファイルSETTINGSを追加. 現在記録しているのは使用するバルーンの値のみ. バールン付属のゴーストはインストール時に付属バルーンが 使用するバルーンとしてこのファイルに記録される. メニューからバルーンを変更すると値は上書きされる. * ゴースト, バルーン, プラグイン等のファイルを起動時に 読み込んでいたのを, 必要になるまで読み込まないようにした. * メニューからバルーンを変更した際にはすぐにバルーンの位置を 再計算するようにした. * OnBalloonChangeイベントのReference1にバルーンの絶対パスを 入れるようにした. Fri February 18 2011 Shyouzou Sugitani * ninix-installの@ファイルリストのサポートを削除. (名前が@で始まるファイルにアーカイブを列記しておくと 全てインストールされるというもの.) * ninix-installでinstall.txtをインストールしないように修正. * ネットワーク更新後のファイルの再読み込みで一部の変数に 更新前のデータが残っていたのを修正. * ninix-install.pyをunzipコマンドではなくpythonのzipfileを 使用するように変更. * ninix-installの-g(--ghost), -s(--shell), -b(--balloon), -P(--plugin), -K(--kinoko), -N(--nekoninni), -D(--katochan) オプションを削除. install.txtから得られる情報だけでインストール処理するようにした. * ninix-installの-r(--reload), -p(--port)オプションを削除. * 「スタンドアロンのシェル」(他のゴーストのシェルを置き換えて 乗っ取る形で動作するシェル)を廃止. install.txtにacceptが無いシェル(inverse時代のもの?)や それ以外のゴースト/シェルでもオプション指定でスタンドアロンの シェルとしてインストール出来るようになっていた. 既にインストール済みのものについては動作を保証しない. * 「ゴーストwithバルーン」のバルーンのインストール先を仕様通り balloon/以下に変更. * inverse時代のディレクトリ構成になっているアーカイブの サポートをninix-install.pyから全て削除した. Tue February 15 2011 Shyouzou Sugitani * バージョン4.1.3リリース. * ネットワーク更新対象ファイルのファイル名にマルチバイト文字が 含まれている場合に対応. (updates2.dauの中身はShift_JISと仮定して処理.) * ゴースト更新後の再起動で落ちる問題を修正. (サーフェスがセットされる前にExposeイベントが来た場合には 描画処理をスキップするようにした.) * SERIKOのbaseメソッドでサーフェスIDに-1が指定された場合の サーフェスを戻す処理が抜けていたのを修正. Mon February 14 2011 Shyouzou Sugitani * ゴースト更新後の再起動で落ちる問題を修正. (reload_current_sakuraがコールバックに登録されていなかったのを修正.) * SERIKOのoverlayfastに対応.(内部の処理はoverlayと全く同じ.) * pix.pyのget_workarea()で落ちる問題を修正. (GdkWindow.get_geometry()の戻り値の個数を間違えていた.) Sun February 13 2011 Shyouzou Sugitani * SakuraScriptの\![set,wallpaper]に対応. (GNOMEデスクトップ環境向け. python-gconfを使用している. この機能を利用しない場合はpython-gconfのインストールは不要.) Tue February 8 2011 Shyouzou Sugitani * バージョン4.1.2リリース. * サーフェスのマウスドラッグによる移動が終わった時に バルーンの位置を再計算するようにした. * 「きのこ」互換機能を4.1.1でのseriko.pyの変更に合わせて再度修正. * 「きのこ」と「猫どりふ」互換機能のtypoを修正. * サーフェスのマウスドラッグによる移動中は, サーフェスウインドウを 移動する前にgtkのイベントが全て処理されるようにする部分を 実行しないように修正.(4.1で導入した処理.) サーフェスのマウスドラッグによる移動で位置がおかしくなるため. (ちなみに, このgtkのイベント処理が何故必要かはこの部分を コメントアウトした上でサーフェス倍率を100%未満にすると確認出来る.) * サーフェスのマウスドラッグによる移動中にSurfaceWindow.set_position() を呼んでしまう部分が残っていたのを修正. (SurfaceWindow.set_alignment()の中.) * サーフェス倍率が100%以外の場合にバルーンオフセットの計算が 間違っていたのを修正. * アルファチャンネル等のサーフェスやバルーンの描画に影響を与える 設定をユーザーが変えた際の処理を変更. 設定の変更があったかどうかをSurfaceとBalloonクラスでチェックし, 変更があった時だけSurfaceWindowとBalloonWindowクラスに伝える. 値のチェックもSurfaceとBalloonクラスが行ない, 各Windowクラスは チェックしない. また, 各WindowクラスとSERIKOのデフォルト設定もSurfaceと BalloonクラスがWindowクラスのインスタンス生成時に行なう. Sun February 6 2011 Shyouzou Sugitani * バージョン4.1.1リリース. * サーフェスのマウスドラッグによる移動中はサーフェスが変更されても SurfaceWindow.set_position()を呼ばないように修正. Sat February 5 2011 Shyouzou Sugitani * バルーンとinputbox, communicatebox, teachboxの描画をCairoに移行. (バルーンのちらつきはこれで解消したはず.) Wed February 2 2011 Shyouzou Sugitani * easyballoon互換モジュールの描画処理をCairoに移行. * 花柚互換モジュールの描画処理をCairoに移行. * 花柚互換モジュールのbackground.color設定に対応. * 使用率グラフの描画をCairoに移行. * NGMクローンの描画をCairoに移行. * pix.pyのTransparentWindowクラス(gtk.Windowクラスを継承)で gtk.Window同様にウインドウのタイプを指定出来るようにした. * SERIKOアニメーションでのサーフェスの更新とウインドウの移動を分離し, サーフェスの更新を先に行なうようにした. 4.1リリース後最初のコミットで入ったバグの修正. (最初に表示されるサーフェスで最初からアニメーションによる 移動があると落ちる.) * サーフェスの当り判定領域の描画(デバッグ用)をCairoに移行. Tue February 1 2011 Shyouzou Sugitani * satori.pyの関数呼び出しおよびSAORI呼び出しの処理の中で, 文字数を入れるべきところにバイト数が入っていたのを修正. (関数名に多バイト文字が含まれている場合に引数がおかしくなっていた.) Sat January 29 2011 Shyouzou Sugitani * バルーンがちらつく問題を修正.(サーフェスについては4.1で修正済み.) (gtk.DrawingArea.windowではなくgtk.Window.windowに対して set_back_pixmapを呼ぶようにした.) * 「きのこ」互換機能を新しいseriko.pyに合わせて変更. * 「きのこ」と「猫どりふ」互換機能の描画処理をCairoに移行. * 「きのこ」と「猫どりふ」のウインドウも登場する際にフォーカスを 奪わないようset_focus_on_map(False)を追加. * サーフェスのPNG画像先読み機能とGdkPixbufのキャッシュを削除. (今の実装だとこれらの機能が無くてもCPUの使用率はほぼ変わらず, メモリの使用率が低くなるので.) * seriko.pyのタイマー処理を改良. (これまでの実装では割り込みが一定間隔で来ると仮定していたが, 実際にはそうならないので実時間を測るようにした.) * サーフェスのアニメーション処理を変更. 無駄なサーフェスPixbufの更新が発生しないようにした. Fri January 21 2011 Shyouzou Sugitani * バージョン4.1(black magick)リリース. Thu January 20 2011 Shyouzou Sugitani * Serikoのタイマー処理を変更. \iタグでアニメーションを発動した場合にサーフェスが更新されて いかなかったのを修正. * \4タグの動作を変更. これまでは相方から一定距離(画面の1/20)離れた地点まで移動して それ以上は離れなかったが, 元々離れている場合にはそこからさらに 一定距離(画面の1/20)離れる方向に移動するようにした. * バルーンを隠す際にサーフェス同様GtkDrawingAreaを隠して GtkWindowは隠さないようにした. * サーフェスとバルーンのウインドウが登場する際にフォーカスを 奪わないようset_focus_on_map(False)を追加. (この設定は3.9.8bでバルーンから削除したものだが, Gnome標準の ウインドウマネージャMetacity 2.27.2で試した限り意図通りに 動くので, サーフェスも含めて再度設定した.) Wed January 19 2011 Shyouzou Sugitani * 見切れと重なり判定の処理を分離した. * 見切れと重なり判定で, デフォルトサーフェスと異なるサイズの サーフェスが出ている場合に判定を間違うことがあったのを修正. * サーフェスウインドウを移動する前にgtkのイベントが全て 処理されるようにした. (移動の前にウインドウサイズの変更があると, その処理が 終わっているかどうかで移動の結果が変わってしまうため.) Tue January 18 2011 Shyouzou Sugitani * サーフェス自由配置の際のバルーン位置計算にバグがあったのを修正. * ウインドウをワークエリア外に出せない仕様のウインドウマネージャでも 画面の端を越えるウインドウの移動と同様の表現が出来るようにした. (実際にはウインドウを端から削って画面外に出たかのように見せている.) これに合わせて, サーフェスのマウスドラッグによる移動に gtk.Window.begin_move_drag()を"使用しない"ように変更した. (上記の様なウインドウマネージャにおいても, begin_move_dragを使うと 画面の端を越える移動が出来てしまい, 座標の計算が狂うため. begin_move_dragの終了を補足出来ない仕様が致命的.) * ninix-aya起動時にゴーストのアイコンファイルを読み込んでいたのを, 必要になるまで読み込まないように変更. (以下は4.99.xからのバックポート) * 花柚互換モジュールでタイトルの縦書きに対応. * wmovel.dll互換モジュールで不要なgtkのimportを削除. * keymap.pyの辞書に特殊キーの識別子を追加. * \xタグの処理を修正. (改行を付加しない. クリックされてバルーンの 内容を消去した後は一旦バルーンを閉じる.) * pix.pyにpng画像のヘッダ部分だけを読み込んでサイズを取得する機能を 追加.(DGP, DDPにも対応.) * バルーンをダブルクリックした場合にもOnMouseDoubleClickイベントを 発生させていたのを, 発生させないように変更. * '\x'の処理を仕様書通りに修正. Fri January 14 2011 Shyouzou Sugitani * Copyrightを2011年に更新. Mon December 20 2010 Shyouzou Sugitani * バージョン4.0.8リリース. * READMEの「必要なもの」を更新. Sun December 19 2010 Shyouzou Sugitani (以下は4.99.xからのバックポート) * plugin.txtのデフォルト文字コードをEUC-JPからUTF-8に変更. * plugin.pyにプラグインから本体にSSTP送信(SEND/1.4)するための メソッドを追加.(使用方法はサンプルプラグイン「お天気やん」参照.) * OnBalloonChangeイベントのサポートを追加. (ただし, Reference1には空文字列が入っている.) * \&[]タグの処理にエラー処理を追加. * kawari.pyのShioriクラスにsaori_iniの初期化を追加. * タイマー関連のメソッドがgobjectからglibに移動したのに対応. Mon June 21 2010 Shyouzou Sugitani * README: lhaに関する記述を削除. Tue June 15 2010 Shyouzou Sugitani * バージョン4.0.7リリース. * lib/ninix-install.py, doc/extension.txt: lzh形式アーカイブのサポートを削除. Wed June 2 2010 Shyouzou Sugitani * lib/ninix/ngm.py: データベースのネットワーク更新中もゴーストが動作するよう改良. * lib/ninix/ngm.py: データベースの読み込みを高速化. * lib/ninix/ngm.py: Gtk.Windowのdeleteイベントハンドラの戻り値をTrueに修正. (Falseだとハンドラの呼び出し後にGtk.Windowが破壊されてしまう.) * lib/ninix/ngm.py: データベースのネットワーク更新でハングアップすることがあったのを修正. Wed May 19 2010 Shyouzou Sugitani * バージョン4.0.6リリース. * lib/ninix/prefs.py: 初めてninix-ayaを起動した場合にユーザー設定のデフォルト値が セットされない問題(他のクラスが値を取得すると意図せずNoneを 返してしまい落ちる)を修正. * lib/ninix/main.py: コマンドラインオプションの処理にはgetoptモジュールではなく 新しいoptparseモジュール(Python2.3以降で追加)を使用するよう変更. * lib/ninix/plugin.py: ファイルのpermissionを修正.(Thanks to PaulLiuさん) * lib/ninix/main.py: current_sakuraの定義が間違っていたのを修正.(4.0.5でのregression.) 終了時のゴーストが正しく記録されるようになった. Thu April 29 2010 Shyouzou Sugitani * バージョン4.0.5リリース. Wed April 28 2010 Shyouzou Sugitani * lib/ninix/sakura.py: GStreamerの奇妙な振舞いへの対策を追加. (https://bugzilla.gnome.org/show_bug.cgi?id=549879 を参照.) 具体的な動作としては--help(-h)コマンドラインオプションを指定すると GStreamerのヘルプが表示されていたのをninix-ayaのものが出るように修正. Mon April 26 2010 Shyouzou Sugitani * lib/ninix/main.py: 廃止したコマンドラインオプションの処理が残っていたのを削除. * lib/ninix/menu.py: メニューからゴーストの再読み込みを削除. Fri April 23 2010 Shyouzou Sugitani * lib/ninix/dll/httpc.py: HTTP Status Codeが200(OK)以外の場合にはデータを返さないように修正. * クラスのインスタンス生成時に生成元のクラス等他のクラスへの参照を 渡していたのを止めて, 代わりにコールバック関数の入った辞書型変数を 渡すようにした.(lib/ninix/dll/以下のファイルについては除外.) * lib/ninix/balloon.py: バルーンのウインドウにdeleteイベントが送られてもゴーストが終了しない ように変更. * lib/ninix/dll.py: SAORI互換モジュールからPreferencesクラスへの参照を削除し, Sakuraへの 参照を使うようにした. * Pluginに関するコードをmain.pyから分離してplugin.pyを作成した. * lib/ninix/main.py: SSTP Serverの管理をApplicationクラスから分離してSSTPControlerクラスを 作成した. * lib/ninix/main.py: ApplicationクラスをNew-style classにした.(今後全クラスを変更予定.) current_sakuraをApplicationクラスのpropertyにした. current_sakuraのsetter(a function for setting)内で変更をPreferences に伝えるようにした. Fri April 9 2010 Shyouzou Sugitani * doc/saori.txt: osuwari.pyとhttpc.pyに関する記述を追加. * lib/ninix/config.py: Configクラスのgetint, getfloatメソッドをget_with_typesに統合. * lib/ninix/dll.py: SAORIクラスに辞書型変数RESPONSEを作成し, 戻り値を持たない SAORI/1.0のレスポンス(204, 400, 500)を入れた. SAORI互換モジュールはこの変数を使用するように修正. * lib/ninix/main.py: Application.get_plugin_list()で変数の代入前に値を参照している部分が あったのを修正. * lib/ninix/main.py: 'OnShellChanging'イベントの引数に渡す変数名を間違えていたのを修正. (3.9.9でのregression.) * lib/ninix/pix.py: create_pixbuf_from_file()の引数is_pnrとuse_pnaには1/0ではなく True/Falseを渡すように変更. * lib/ninix/prefs.py: Preferences.get_with_type()の引数の順序を変更した. * lib/ninix/sakura.py: 専用バルーンを持つゴーストでバルーンを専用バルーンとは別のバルーンに 変えてから専用バルーンに戻すと落ちる問題を修正. * lib/ninix/balloon.py: BalloonWindowのconfig_getintメソッドを__get_with_scalingに変更. (__get_with_scalingでは戻り値の型を第2引数に渡す必要がある.) * lib/ninix/surface.py: SurfaceWindowのget_config_intメソッドを__get_with_scalingに変更. (__get_with_scalingでは戻り値の型を第2引数に渡す必要がある.) * lib/ninix/prefs.py: デフォルトバルーンに設定されているバルーンの存在確認を省略. (存在していない場合には最初に見付かったバルーンが選ばれる.) Tue April 6 2010 Shyouzou Sugitani * バージョン4.0.4リリース. Mon April 5 2010 Shyouzou Sugitani * doc/extension.txt: 削除した機能について注釈を追加. Sun April 4 2010 Shyouzou Sugitani * lib/ninix/balloon.py, lib/ninix/config.py: バルーンの設定での"--n"形式の値への対応を削除. (ninix 0.1.6で導入されたが, 現在手に入る仕様には記述が見当たらない.) * lib/ninix/aya5.py: システム関数CHRCODEのバグ(変数名の間違い)を修正. * lib/ninix/dll/bln.py: 未使用変数の削除等, コードを整理. * lib/ninix/nekodorif.py: 未使用変数の削除等, コードを整理. * lib/ninix/prefs.py: Preferencesクラスの_get, getint, getfloatメソッドを get_with_typesに統合. * lib/ninix/sakura.py: Bootイベント後にサーフェスが出ていない場合にデフォルトサーフェスを 出す処理がネットワーク更新後の再起動(OnUpdateCompleteイベント)で 機能していなかったのを修正. * httpc.dll互換モジュールhttpc.pyを追加. * lib/ninix/surface.py: サーフェス倍率が100%以外の場合に当り判定領域の座標の計算結果が全て 0になるバグを修正.(バージョン4.0.3でのregression.) Thu April 1 2010 Shyouzou Sugitani * Makefile: 使用していなかったPythonのdistutilsに関する部分を削除. それに合わせてPKG-INFOファイルを削除した. * lib/ninix/surface.py: マウスドラッグでサーフェスを移動した後のサーフェス位置の再計算を configure-eventハンドラ内で行なうようにした. (gtk.Window.begin_move_drag()の後にfocus-in-eventが起きるかどうかは ウインドウマネージャに依存するため. ConfigureNotifyはICCCM 4.1.5でウインドウの移動があれば発生すると 定められている. サーフェスの移動途中でも再計算が起きるため負荷が高くなる可能性が あるが, begin_move_drag()の終了を確実に捕捉出来る方法が無い.) * lib/ninix/dll/aya5.py: システム関数SPLITPATHを実装した. Wed March 31 2010 Shyouzou Sugitani * バージョン4.0.3リリース. Mon March 29 2010 Shyouzou Sugitani * 画像を拡大/縮小する際に8x8ピクセル以下にならないように制限をかけた. (これまでも一部で制限をかけてあったが1x1ピクセルであったり, 制限がかかっていない部分があったりと統一されていなかった.) * lib/ninix/home.py: 「猫どりふ」のkatochan.txtの読み込み処理を修正. (スクリプトが複数指定されている場合でもエラーにならないようにした. スクリプト再生は未対応のまま.) * lib/ninix/dll/aya5.py: 未実装のシステム関数を呼び出そうとして落ちる問題を修正. Fri March 26 2010 Shyouzou Sugitani * サーフェス/バルーンの拡大縮小で100%の場合を特別扱いするのをやめた. * pixbuf, pixmapを明示的に削除するのをやめた. (処理はPythonのガーベジコレクタに任せる.) * lib/ninix/sstp.py: ninix独自のreloadコマンドを削除. * lib/ninix/sakura.py: Sakura.reload()を削除. * lib/ninix/main.py: Application.reload()を削除. (ゴーストのリロードはreload_current_sakura()に統一.) * lib/ninix/config.py: open()をcreate_from_file()に名称変更. * lib/ninix/config.py: new_config()をcreate_from_buffer()に名称変更. * lib/ninix/alias.py: open()をcreate_from_file()に名称変更. * lib/ninix/alias.py: new_alias()をcreate_from_buffer()に名称変更. * lib/ninix/sakura.py: OnBootのReference0が渡されていなかったのを修正. * lib/ninix/sakura.py: OnFirstBootのReference7が渡されていなかったのを修正. * lib/ninix/dll/misaka.py: 辞書の文字コード判定のバグを修正. (今まではたまたま動いていただけ.) * lib/ninix/ngm.py: 保存するDBファイルの文字コードをEUC-JPからUTF-8に変更. (自動的に変換されるのでユーザーは何もする必要はない.) * lib/ninix/main.py, lib/ninix/sakura.py: ninix独自のOnNinixReloading, OnNinixReloadedイベントを削除. * lib/ninix/dll/niseshiori.py: open()をns_open()に名称変更. * lib/ninix/dll/misaka.py: open()をmisaka_open()に名称変更. * lib/ninix/dll/kawari.py: open()をkawari_open()に名称変更. * lib/ninix/dll/bln.py: Balloon.__init__()が巨大になっていたので分割. * lib/ninix/dll/aya5.py: システム関数CHRCODE()の戻り値の文字コード指定が間違っていたのを修正. * lib/ninix/balloon.py: pango.Layout.set_markup()に渡す文字列内の特殊記号を glib.markup_escape_text()を使ってエスケープするように修正. (これまでは"&"等が含まれている文字列に\f[sup]等の指定をすると エラーになっていた.) * lib/ninix/kawari.py: read_local_script()内のtypoを修正. (readline() -> readlines()) Thu February 25 2010 Shyouzou Sugitani * バージョン4.0.2リリース. * lib/ninix/main.py: ゴースト消滅後の交代で落ちる問題を修正. Tue February 23 2010 Shyouzou Sugitani * lib/ninix/surface.py: ドロップされたファイルのパスがURLエンコードされたままになっていたため os.path.exists()で見付けられない場合があったのを修正. * lib/ninix/dll/hanayu.py: DrawingAreaとPixmapのdepthが一致するよう修正. (Composite拡張を使用している環境でグラフが描画されない問題を修正.) * lib/ninix/dll/misaka.py: typoを修正. $insentenceが動作するようになった. * lib/ninix/dll/osuwari.py: typoを修正. * lib/ninix/dll/osuwari.py: アクティブウインドウの取得で落ちることがあるのを修正. * lib/ninix/dll/osuwari.py: except設定への対応開始.(未完成) Mon February 22 2010 Shyouzou Sugitani * Copyrightを2010年に更新. * ninix-updateコマンドを削除. * lib/ninix/dll/mciaudio.py, mciaudior.py: 使用していないモジュールのimportを削除. * lib/ninix/balloon.py: self.__scaleとするべきところがscaleになっていたのを修正. * lib/ninix/dll/hanayu.py: 整数値が必要なところでは明示的整数除算(//)を使用するようにした. * lib/ninix/dll/textcopy.py: gtk.Entryではなくgtk.Clipboardを使用するように変更した. * lib/ninix/update.py: md5をhashlibで置き換えた.(Python2.5以降対応) * lib/ninix/kinoko.py: typoを修正. * lib/ninix/sakura.py: execute_command()を削除. * lib/ninix/seriko.py: Actor.invoke()の引数がActorのサブクラスと一致していなかったのを修正. * lib/ninix/seriko.py: 使用していないモジュールのimportを削除. * lib/ninix/ngm.py: 使用していないモジュールのimportを削除. * lib/ninix/home.py, lib/ninix/main.py: gtkrcの読み込み処理を削除. * lib/ninix/home.py: 「きのこ」と「猫どりふ」の設定ファイルの読み込みでエラー処理が 抜けていたのを修正. * lib/ninix/dll/wmove.py, lib/ninix/dll/bln.py: コードを整理した. 動作自体は変えていない. (if文の場合分けを整理したり, 1つのメソッドにreturnが多数ある状態を解消, etc.) * lib/ninix/main.py: ゴースト消滅の際にファイルを消去するのにos.system()を使っていたのを os.remove()とshutil.rmtree()を使用するように変更した. * lib/ninix/prefs.py: コードを整理した. 動作自体は変えていない. Thu December 24 2009 Shyouzou Sugitani * バージョン4.0.1リリース. * ゴーストの終了時/更新後のリロード時に落ちる問題を修正. Thu December 24 2009 Shyouzou Sugitani * バージョン4.0(jump off into never-never land)リリース. * locale/ja.poを更新した. * ユーザー設定からヘルパーを削除した. それに合わせてdoc/saori.txtを修正した. * れたす(lettuce.dll)互換モジュールを削除した. (要望があればGStreamerを使用して再度実装する.) * 音声ファイルの再生にはGStreamerを使用するようにした. \_v[]タグとMCIAudio, MCIAudioR互換モジュールを変更した. MCIAudioRはループ演奏に対応した. READMEの「必要なもの」にGStreamer Python bindingsを追加した. Fri December 4 2009 Shyouzou Sugitani * Webブラウザの呼び出しにはPythonのwebbrowerモジュールを 使用するようにした.(ユーザー設定からWebブラウザを削除した.) Sun November 29 2009 Shyouzou Sugitani * バージョン3.9.9aリリース. * CommunicateWindowクラス(とそれを継承しているクラス)を修正. (Thanks to addone@users.sourceforge.jp.) Wed July 22 2009 Shyouzou Sugitani * バージョン3.9.9リリース. * 「きのこ」の'ontop'の処理にgtk.Window.set_transient_for()を 使用するようにした. * 「猫どりふ」の見切れをゴースト同様に透明部分も含めたサーフェスの 1/3が画面外に出ると発動するよう変更した. * マウスドラッグによるサーフェスの移動終了直後にサーフェス位置の 再計算を行なうようにした. * locale/ja.poを更新した. Sun July 5 2009 Shyouzou Sugitani * バージョン3.9.8fリリース. * GhostクラスをSakuraクラスに統合した. (ghost.pyはsakura.pyに吸収される形で消滅した.) * NGMクローンからのゴーストの更新方法を変更した. (Sakuraクラスのupdate()メソッドを直接呼ぶのではなく, "\![updatebymyself]\e"をスクリプトキューに入れるようにした.) Fri June 12 2009 Shyouzou Sugitani * バージョン3.9.8eリリース. * 3.9.8dでeasyballoon互換モジュールが落ちるのを修正した. Fri June 12 2009 Shyouzou Sugitani * バージョン3.9.8dリリース. * lib/ninix/pix.py: get_workarea()が現在のデスクトップの情報のみを取得するよう修正した. (複数のデスクトップがある場合に落ちる問題を修正した.) * PreferenceDialogをlib/ninix/prefs.pyに移して, ユーザー設定の管理を 集約した. ユーザー設定の項目も減らした. SHIORIイベントの発生を抑制する項目(PREFS_EVENT_KILL_LIST)と マウスボタンの機能を設定する項目(PREFS_MOUSE_BUTTON1, PREFS_MOUSE_BUTTON3)を削除した. また, メニューから個々のゴーストについてサーフェス倍率とスクリプトの 再生スピードを設定する項目を削除した.(全ゴーストが同じ設定になる.) Mon May 11 2009 Shyouzou Sugitani * バージョン3.9.8cリリース. * Git移行に伴い, 全てのファイルからCVSの$Id$タグを削除した. * 画面の上下方向の有効範囲(タスクバー等を除いた範囲)をユーザーが指定 するための設定値(PREFS_TOP_MARGINとPREFS_BOTTOM_MARGIN)を削除した. 画面の有効範囲はninix.pix.get_workarea()を使用して取得するように 変更した.(上下だけでなく左右方向の有効範囲も得られる.) ("Extended Window Manager Hints"の_NET_WORKAREAを使用している. 詳細は http://standards.freedesktop.org/wm-spec/wm-spec-latest.html を参照.) Wed May 6 2009 Shyouzou Sugitani * バージョン3.9.8bリリース. * sourceforge.jpにGitリポジトリを作成し, ソースコードの管理をGitに 移行した. 今後CVSのリポジトリは更新しない. * lib/ninix/ballon.py: バルーンを出す際にバルーンがフォーカスを奪わないようにするための設定 (バルーンのウインドウに対するset_focus_on_map(False))を削除した. この設定をすると, compiz等のウインドウマネージャがその時点で フォーカスしているウインドウの後方にバルーンを移動するため. その際set_transient_for()で親ウインドウに設定しているサーフェスも "勝手に"後方に移動してしまう. (set_transient_for()を削除するとバルーンだけがフォーカスしている ウインドウの後方に出る. サーフェスは影響を受けないが, バルーンが 見えなくなるのは同じなので意味が無い.) バルーンを出す際にバルーンがフォーカスを奪わないようにしつつ, バルーンのウインドウを前面に持ってくる方法は見付からなかった. この辺の処理はウインドウマネージャ次第で変わってしまい, 確実な方法が無いので, ninix-ayaでは何もしないことにした. * lib/ninix/ballon.py: バルーンウインドウを出した時にウインドウを前面に持って来る処理が 機能していなかったのを修正した. * lib/ninix/ghost.py: cantalkフラグはTrue/Falseではなく1/0を値としてSHIORIに渡すように 修正した. * lib/ninix/balloon.py, lib/ninix/surface.py: Composition拡張機能を使用している場合にはshape_combine_mask()ではなく input_shape_combine_mask()を使用するようにした. * lib/ninix/main.py: ループ処理の無駄が少なくなるように修正した. * lib/ninix/sakura.py: デフォルトのWebブラウザをfirefoxに変更した. Sun January 4 2009 Shyouzou Sugitani * バージョン3.9.8aリリース. * SakuraクラスとGhostクラスの間での処理の分担の整理をした.(継続中) * lib/ninix/balloon.py, lib/ninix/ghost.py, lib/ninix/sakura.py: InputBox, TeachBox, CommunicateBoxが開いているかどうかの状態管理を SakuraクラスからBalloonクラスに移動した. * lib/ninix/ghost.py, lib/ninix/home.py, lib/ninix/main.py, lib/ninix/surface.py: サーフェスのツールチップ表示を実装した.(SSP互換) * lib/ninix/home.py: surfaces.txtの文字コード指定に対応した.(SSP互換) * lib/ninix/surface.py: 見切れと重なりの判定が正しく機能していなかったのを修正した. * _niseshiori.so(暗号化辞書の解読用C言語モジュール)を削除した. (速度は劣るがniseshiori.py内部にPythonで実装されたコードがあるので.) これでninix-aya本体にはC言語モジュールが無くなった. Wed December 24 2008 Shyouzou Sugitani * バージョン3.9.8リリース. * lib/ninix/balloon.py, lib/ninix/dll/bln.py, lib/ninix/hanayu.py, lib/ninix/kinoko.py, lib/ninix/main.py, lib/ninix/menu.py, lib/ninix/nekodorif.py, lib/ninix/ngm.py, lib/ninix/pix.py, lib/ninix/surface.py: gcの処理を削除した. * SakuraクラスとGhostクラスの間での処理の分担の整理をした.(未完成) * lib/ninix/kinoko.py: 存在しないオブジェクト(Skinクラス内のself.surface_pixmap)への参照で 落ちるのを修正した. * lib/ninix/balloon.py, lib/ninix/kinoko.py, lib/ninix/main.py, lib/ninix/menu.py, lib/ninix/nekodorif.py, lib/ninix/sstp.py, lib/ninix/surface.py, lib/ninix/update.py: Sakuraへの参照をGhostへの参照で置き換えた. * lib/ninix/balloon.py, lib/ninix/surface.py: WindowのShape Maskの生成にrender_pixmap_and_maskではなく render_threshold_alphaを使用するよう変更した. (余分なpixmapの生成が無くなり高速になった.) * lib/ninix/main.py: Shellを名前で選択するためのメソッドselect_shell_by_nameを追加した. * lib/ninix/menu.py: 使用していなかったGhost, Shell, Balloon, Plugin, Nekodorif, Kinokoの リストを削除した. * lib/ninix/sstp.py: socketのopenはSakuraクラスを経由せず直接行うように変更した. * lib/ninix/main.py, lib/ninix/menu.py, lib/ninix/sakura.py: range_script_speedをsakura.pyからmenu.pyへ移した. * lib/ninix/main.py, lib/ninix/menu.py, lib/ninix/surface.py: range_scaleをsurface.pyからmenu.pyへ移した. * lib/ninix/pix.py: md5をhashlibで置き換えた.(Python2.5対応) * lib/ninix/pix.py: アルファチャンネル付きPNGファイルの処理をnumpyを使用して高速化した. * lib/ninix/surface.py, lib/ninix/sakura.py: スケーリングによる数値の変換はSurfaceWindowクラスで行うようにした. * lib/ninix/ghost.py, lib/ninix/surface.py, lib/ninix/kinoko.py, lib/ninix/nekodorif.py: Observerクラスへのイベント通知をSurfaceとSurfaceWindowで受け持つ ように変更した. * lib/ninix/balloon.py: スクロールバーの矢印, SSTPマーカー, \_bタグの 画像が表示されなくなっていたのを修正した. Mon December 24 2007 Shyouzou Sugitani * バージョン3.9.7リリース. * Pythonプログラムファイルの文字コード指定を全て小文字に統一. (UTF-8 -> utf-8, EUC-JP -> euc-jp) asciiを指定していたものはutf-8に変更. * lib/ninix/pix.py, lib/ninix/surface.py, lib/ninix/balloon.py: - 透過ウインドウ処理にGTK+2.10の新機能を使用するように変更. - 透過ウインドウの背景が黒で塗られる問題を修正. * lib/ninix/surface.py: - 以前の変更で不要になった処理が残っていたのを削除. - ゴーストのサーフェスのマウスドラッグに使用するボタンを 左ボタン(1番)に変更. - ゴーストのサーフェス移動後の位置の再計算のタイミングを変更. Sun July 29 2007 Shyouzou Sugitani * osuwari.dll互換SAORIモジュールosuwari.pyを追加. Sun July 22 2007 Shyouzou Sugitani * バージョン3.9.6リリース. * OnBallonCloseイベントのサポートを追加. Sat July 21 2007 Shyouzou Sugitani * OnMouseEnterAll, OnMouseLeaveAll, OnMouseEnter, OnMouseLeave イベントのサポートを追加. Sun July 8 2007 Shyouzou Sugitani * バージョン3.9.5リリース. * SERIKOによるサーフェスの書き換えを抑制するオプションを追加. (サーフェスの書き換えが起きないだけで内部でSERIKOは動作している.) * YAYAローダーyaya.pyを追加. Sun April 29 2007 Shyouzou Sugitani * バージョン3.9.4リリース. * locale/ja.poを更新. * メニューコンテキストの配置がSSPに近付くよう変更. Tue April 10 2007 Shyouzou Sugitani * gdk-pixbufを使用している部分のメモリリーク対策をした. * gdk-pixbuf関連のコードをSerikoからSurfaceクラスに移した. Sat March 24 2007 Shyouzou Sugitani * メニューアイコンの管理を各ゴーストからApplicationクラスに移した. Sun December 10 2006 Shyouzou Sugitani * バージョン3.9.3リリース. Sat December 9 2006 Shyouzou Sugitani * lib/ninix/surface.py: Surfaceクラスの終了処理でSerikoを停止する ように修正. Sun October 22 2006 Shyouzou Sugitani * lib/ninix/pix.py: PNAファイルの処理でNumeric Pythonの機能を使う ように修正. Tue October 10 2006 Shyouzou Sugitani * バージョン3.9.2リリース. Mon October 9 2006 Shyouzou Sugitani * lib/ninix/main.py: サーフェスとバールンのアルファチャンネルの 設定値を~/.ninix/preferencesから読み出す部分で, 値はfloatなのに intとして読み出していたのを修正. * lib/ninix/ghost.py, lib/ninix/surface.py: ゴースト終了後もサーフェスやバルーンのデータが残っていたのを修正. Mon October 9 2006 Shyouzou Sugitani * バージョン3.9.1リリース. Sat October 7 2006 Shyouzou Sugitani * lib/ninix/kinoko.py: seriko.pyの変更に追従. Thu October 5 2006 Shyouzou Sugitani * lib/ninix/main.py, lib/ninix/ghost.py, lib/ninix/surface.py, lib/ninix/seriko.py: アニメーションの処理を変更. CPU負荷とコマ飛びを低減. Mon September 4 2006 Shyouzou Sugitani * バージョン3.9リリース. Sat September 2 2006 Shyouzou Sugitani * lib/ninix/dll/misaka.py: python-chardet(http://chardet.feedparser.org/)による文字コードの 自動判定を実装. Shift_JIS以外の文字コードを使用したゴーストに対応. python-chardetをインストールしていなければShift_JISのみ対応. Wed August 30 2006 Shyouzou Sugitani * バージョン3.8.9aリリース. * lib/ninix/balloon.py: \q, \URLタグで落ちる問題を修正. Sun August 27 2006 Shyouzou Sugitani * バージョン3.8.9リリース. Sat August 26 2006 Shyouzou Sugitani * lib/ninix/script.py, lib/ninix/sakura.py, lib/ninix/balloon.py: 下線と字消し線のフォントタグ(\f[underline,], \f[strike,])を実装. * lib/ninix/script.py: 括弧の無い(\p0のような)\pタグに対応. * sys.exit()を"raise SystemExit"で置き換えた. Tue July 18 2006 Shyouzou Sugitani * バージョン3.8.8リリース. Mon July 17 2006 Shyouzou Sugitani * lib/ninix/balloon.py: SSTPの送信元表示のフォントサイズ計算を修正. * lib/ninix/script.py, lib/ninix/sakura.py, lib/ninix/balloon.py: 上付きと下付きのフォントタグ(\f[sup,], \f[sub,])を実装. Sun June 11 2006 Shyouzou Sugitani * バージョン3.8.7リリース. Sat June 10 2006 Shyouzou Sugitani * ゴーストのサーフェスのマウスドラッグによる移動に gtk.Window.begin_move_drag()を使用するように変更. 使用しているウインドウマネージャによっては画面外に出せなく (見切れ状態にできなく)なっていたのを修正. Wed June 7 2006 Shyouzou Sugitani * ゴーストの見切れ処理を調整. 透明部分も含めたサーフェスの1/3が 画面外に出ると発動するよう変更.(これまでは1/4だった.) Sun June 4 2006 Shyouzou Sugitani * lib/ninix/dll/aya5.py: 辞書のコメントの処理を修正. (「さい子」が動くようになった.) Sun May 28 2006 Shyouzou Sugitani * バージョン3.8.6リリース. * コミュニケート複数送信拡張に対応. * コミュニケートの際に送信元のゴーストが喋り終わってからイベントを送信 するように修正. Thu May 25 2006 Shyouzou Sugitani * バージョン3.8.5リリース. Wed May 24 2006 Shyouzou Sugitani * lib/ninix/dll/misaka.py: Lexerの文字列抽出部分を修正. (「花ちゃん」が動かなくなっていたのを修正.) * lib/ninix/surface.py, lib/ninix/balloon.py: Cairoによる描画で背景を透明にする処理が抜けていたのを修正. (このバグで半透明サーフェス/バルーンが機能しなくなっていたと思われる.) Tue April 25 2006 Shyouzou Sugitani * バージョン3.8.4リリース. * 文5互換モジュールaya5.pyを追加. * 文5ローダーaya5.pyを削除. Sun April 16 2006 Shyouzou Sugitani * バージョン3.8.3リリース. Sat April 8 2006 Shyouzou Sugitani * タスクバー上にサーフェスウインドウ2つが表示されるのをゴースト1組で 1つにするために, Sakura側のみが表示されるようにした. * バルーンのgtk.WindowタイプをPOPUPからNORMALに変更. * バルーンがアクティブウインドウより下に表示されるよう調整. (サーフェスがアクティブウインドウより上にある場合は除く.) Sat March 25 2006 Shyouzou Sugitani * バージョン3.8.2リリース. * lib/ninix/dll/bln.py: マウスイベントの処理で落ちる問題を修正. * バルーンの設定ファイルによるフォントサイズの指定とユーザーによる 指定をきちんと区別し, ユーザー指定は3/4倍しないように修正. それに合わせてバルーンフォントのデフォルトサイズ指定を修正. Thu February 2 2006 Shyouzou Sugitani * バージョン3.8.1リリース. Tue January 31 2006 Shyouzou Sugitani * サーフェスとバルーンの描画処理の一部にCairoグラフィックライブラリ (http://cairographics.org/)を使用するよう変更. そのためGTK+(pygtk)2.8以降が必要となった. pna, 本体設定によるサーフェスとバルーンの半透明化が可能.(NEWS参照) Thu January 26 2006 Shyouzou Sugitani * lib/ninix/pix.py: 本体設定でサーフェス/バルーンのアルファ チャンネルを変更すると落ちる問題を修正. Mon January 16 2006 Shyouzou Sugitani * lib/ninix-install.py, lib/ninix/home.py, lib/ninix/pix.py, lib/ninix/surface.py: ddp暗号化ファイルのサポートを追加. Sun January 15 2006 Shyouzou Sugitani * バージョン3.8リリース. * lib/ninix/menu.py, lib/ninix/surface.py: menu.foreground.font.colorの設定に対応. Wed December 28 2005 Shyouzou Sugitani * バージョン3.7.8リリース. * lib/ninix/menu.py, lib/ninix/surface.py: メニューのフォアグラウンド画像も使用するようにした. (フォントカラーの設定は未対応.) Tue December 27 2005 Shyouzou Sugitani * lib/ninix/menu.py: gtk+-2.6以降でメニューのバックグラウンドに 画像が表示されない問題を修正. この変更でgtk+-2.4では画像が表示されなくなるが, これはgtk+側の問題. http://bugzilla.gnome.org/show_bug.cgi?id=169532 (これまでの実装で動いていたのが間違い.) Tue December 27 2005 Shyouzou Sugitani * バージョン3.7.7リリース. Mon December 26 2005 Shyouzou Sugitani * lib/ninix/ghost.py: SHIORIがNOTIFYイベントに対してスクリプトを 返しても破棄するように修正. Mon December 26 2005 Shyouzou Sugitani * バージョン3.7.6リリース. * lib/ninix/surface.py: サーフェスのプリフェッチの際にエイリアスの 情報を壊してしまっていたのを修正. Sun December 25 2005 Shyouzou Sugitani * lib/ninix/dll/bln.py: easyballoonもサーフェスの倍率に合わせて 縮小(拡大)するようにした. ただし「猫どりふ」などとは違い, サーフェスの倍率を変更しても, 既に生成されているバルーンは 生成時点の倍率のままになる. Fri December 2 2005 Shyouzou Sugitani * バージョン3.7.5リリース. * lib/ninix/surface.py: サーフェスの描画処理を修正. (Pixbufの更新をexposeイベントの生成前に実行するよう変更.) Thu December 1 2005 Shyouzou Sugitani * lib/ninix/surface.py: ウインドウ形状の取得をアルファチャンネルの 変更前に行なうように修正. Mon October 17 2005 Shyouzou Sugitani * lib/sstplib.py: ninix-aya終了後, 即座にSSTPサーバのソケットを 削除(TCPソケットのTIME_WAIT状態を回避)するために, ソケットに SO_REUSEADDRオプションを適用. Thu October 6 2005 Shyouzou Sugitani * バージョン3.7.4リリース. Wed October 5 2005 Shyouzou Sugitani * lib/ninix/surface.py, lib/ninix/seriko.py: サーフェスの描画およびキャッシュの処理を変更. - SurfaceWindowクラスのメソッドを整理. - 画像のキャッシュからアクセスの少ないものを消去するコードを削除. - baseメソッドのアニメーションはスクリプトの再生前に画像をキャッシュに 入れるようにした. - サーフェスのキャッシュを廃止し, MAYUNAの設定がある場合のみ使う 着せ替え専用のキャッシュを用意した. * lib/ninix/main.py, lib/ninix/seriko.py, lib/ninix/dll/kawari.py: listのソートのやり方を修正. Mon October 3 2005 Shyouzou Sugitani * lib/ninix/pix.py: 3.7.3で削除した部分をより速い形に改良して再度追加. Thu September 29 2005 Shyouzou Sugitani * バージョン3.7.3リリース. Wed September 28 2005 Shyouzou Sugitani * lib/ninix/pix.py: アルファチャンネル付きのpngファイルをサーフェスに 使用しているゴーストの一部が, 透過色(座標(0, 0)の色)とRGB値が同じで アルファ値が違う色を非透過部分に使用している場合に対応するための コードを削除. (仕様の「同じ色」という表現はおそらくアルファ値まで含めてと思われるが, 「さくら(俺的。)」のsurface9.pngでしか問題の発生が確認されておらず, 他の問題の起きないゴーストの動作がかなり遅くなるため.) * lib/ninix/surface.py: サーフェスウインドウ毎にSERIKOのActorと Controlerのインスタンスを持つように修正. (\0, \1,...で同じサーフェスを表示しても正しくアニメーションするように.) * lib/ninix/seriko.py: インターバルがrandomのアニメーションが 動かなくなっていたのを修正. * lib/ninix/seriko.py: アニメーションの発動処理の無駄を省いた. * lib/ninix/seriko.py: exclusiveの処理を修正. Tue September 27 2005 Shyouzou Sugitani * バージョン3.7.2リリース. * lib/ninix/seriko.py: いくつかのアニメーションが動かなくなっていたのを 修正. * lib/ninix/seriko.py: exclusive指定があるアニメーションで落ちる場合が あったのを修正. Mon September 26 2005 Shyouzou Sugitani * バージョン3.7.1リリース. Sun September 25 2005 Shyouzou Sugitani * lib/ninix/surface.py, lib/ninix/balloon.py, lib/ninix/kinoko.py, lib/ninix/nekodorif.py: サーフェス, バルーン等の縮小でサイズが0に ならないように修正. Thu September 22 2005 Shyouzou Sugitani * lib/ninix/seriko.py: SERIKOのタイマ割り込みの最小間隔をSERIKO/2.0に 合わせて1msecに. Wed September 21 2005 Shyouzou Sugitani * lib/ninix/seriko.py, lib/ninix/surface.py, lib/ninix/kinoko.py, lib/ninix/sakura.py, lib/ninix/ghost.py: SERIKOをゴーストのタイマ割り込みで駆動するのではなく, 個別に タイマ割り込みを設定して処理するように変更. Mon September 12 2005 Shyouzou Sugitani * バージョン3.7リリース. Sun September 11 2005 Shyouzou Sugitani * lib/ninix/balloon.py: バルーン縮小の設定が正しく初期化されて いなかったのを修正. Thu September 8 2005 Shyouzou Sugitani * lib/ninix/dll/mciaudio.py: ファイルパスの指定の際にSAORIの 置かれているディレクトリも考慮するよう修正. * lib/ninix/dll/misaka.py: Lexerの処理を修正. * lib/ninix/dll/mciaudior.py: 絶対パスでファイル名が渡された時は 小文字に変換しないように修正. * lib/ninix/sakura.py: ファイルがドロップされた際の処理をOnFileDrop2に. (OnFileDropping, OnFileDropped, OnDirectoryDropは発生しない.) 本体で設定されているヘルパーへの引き渡しはイベントの結果にかかわらず 実行されなくなっている. Wed September 7 2005 Shyouzou Sugitani * lib/ninix/dll/misaka.py: SAORIのファイル名の処理を修正. * lib/ninix/surface.py: 着せ換えメニュー生成のバグを修正. * lib/ninix/seriko.py: アニメーションパターンの各コマの番号が途中で 抜けている場合に対応. (例. 0pattern0, 0pattern1, 0pattern3, ...) * lib/ninix/kinoko.py: SERIKO互換処理を修正. * lib/ninix/home.py: kinoko.iniで最後の行の末尾に\0が付いている場合が 見付かったのでその対策を追加. (具体的には「マタンゴ」のkinoko.ini.) (他のファイルについても同様の対策が必要かどうかは要検討.) Fri September 2 2005 Shyouzou Sugitani * lib/ninix/config.py: ConfigクラスをUserDictではなくビルトインのdictの サブクラスに変更. * lib/ninix/prefs.py: PreferencesクラスをUserDictではなくビルトインの dictのサブクラスに変更. Wed August 31 2005 Shyouzou Sugitani * PYTHON: コーディングスタイルを調整. Tue August 30 2005 Shyouzou Sugitani * lib/ninix/dll/ssu.py: evaluate_request()の戻り値を修正. * lib/ninix/surface.py, lib/ninix/dll/aya.py, lib/ninix/dll/bln.py, lib/ninix/dll/hanayu.py, lib/ninix/dll/niseshiori.py: 変数名の間違いを修正. Fri August 12 2005 Shyouzou Sugitani * バージョン3.6リリース. Thu August 11 2005 Shyouzou Sugitani * lib/ninix/surface.py, lib/ninix/seriko.py: SERIKOの処理を改良. Tue August 9 2005 Shyouzou Sugitani * バージョン3.5.9aリリース. * lib/ninix/communicate.py: 起動中のゴーストのリストの生成で落ちる 問題を修正. Tue August 9 2005 Shyouzou Sugitani * バージョン3.5.9リリース. Mon August 8 2005 Shyouzou Sugitani * READMEファイルをREADMEとNEWSに分割. * READMEの「必要なもの」にNumerical Pythonの記述を追加. * READMEの「必要なもの」の日本語コーデックに関する記述を修正. (Thanks to jadoさん) Mon August 8 2005 Shun-ichi TAHARA * バルーンフォントの変更が保存されていなかったのを修正. Thu August 4 2005 Shyouzou Sugitani * PYTHON: コーディングスタイルを調整. Wed August 3 2005 Shyouzou Sugitani * PYTHON: プレフィックスやサフィックスを調べるときに, 文字列の スライスを使うのを避け, 代わりにstartswith()とendswith()を使うよう修正. Tue August 2 2005 Shyouzou Sugitani * PYTHON: オブジェクト型の比較には常にisinstance()を使い 型を直接比較しないよう修正. Mon August 1 2005 Shyouzou Sugitani * PYTHON: 文字列連結に+, +=ではなく''.join()を使うよう修正. * PYTHON: stringモジュールではなく文字列メソッドを使うよう修正. Fri July 29 2005 Shyouzou Sugitani * PYTHON: Noneとの比較に==, !=を使っている個所をis, is notを使うよう修正. Thu July 21 2005 Shyouzou Sugitani * バージョン3.5.8リリース. Wed July 20 2005 Shyouzou Sugitani * lib/ninix/surface.py, lib/ninix/ghost.py, lib/ninix/sakura.py: OnBoot等のイベントでスクリプト終了時までにサーフェス定義が無かった場合には \0, \1のみ強制的にサーフェスを表示するようにした. * lib/ninix/surface.py: 起動後サーフェスがまだ指定されていない状態で\iタグが 来た場合に, デフォルトIDに対する指定として受け付けてしまっていた問題を修正. * lib/ninix/sakura.py, lib/ninix/dll/niseshiori.py, lib/ninix/dll/satori.py, lib/ninix/dll/kawari.py: %ms, %mc, %mz等を SHIORI/3.0として処理する上で, IDに\ms, \mc, \mzの様に\を付けるよう修正. Fri July 8 2005 Shyouzou Sugitani * バージョン3.5.7リリース. * lib/ninix/ngm.py: ~/.ninix/ngm/data/MasterList.xmlが無いと落ちる問題を修正. Sat July 2 2005 Shyouzou Sugitani * バージョン3.5.6リリース. * lib/ninix/dll/kawari.py: KISコマンドfindposのサポートを追加.(Ying-Chun Liu) Tue June 21 2005 Shyouzou Sugitani * SHIORI/3.0 basewareversionをSHIORIのロード時に通知するようにした. (Reference2には開発コードを除いた数値のみのバージョン番号が入っている.) * OnShellChangedのReference1, Reference2を追加. * OnShellChangingのReference1を追加. Mon June 20 2005 Shyouzou Sugitani * ポップアップメニューを出すかどうかを決めるリクエストの処理を追加. (sakura.popupmenu.visible, kero.popupmenu.visible, char2.popupmenu.visiblel, char3.popupmenu.visiblel, ...) * \![set,windowstate,stayontop], \![set,windowstate,!stayontop]を実装. * \![set,windowstate,minimize]を実装. Sat June 18 2005 Shyouzou Sugitani * lib/ninix/dll/misaka.py: エラー発生箇所の情報が出力されない場合があるのを 修正.(Ying-Chun Liu) Sun June 12 2005 Shyouzou Sugitani * バージョン3.5.5リリース. Sat June 11 2005 Shyouzou Sugitani * OnMouseClickイベントの処理をSHIORI/3.0仕様に準拠. Fri June 10 2005 Shyouzou Sugitani * 本体設定に「喋る時手前に出てくる」設定を追加. 設定されている場合には 喋り始める時に一回だけサーフェスとバルーンを手前に出す. この変更に合わせて 喋っている間常にサーフェスとバルーンを手前に出していた処理を削除. Thu June 9 2005 Shyouzou Sugitani * lib/ninix/ghost.py: ゴースト起動時のOnDisplayChangeは喋らないように修正. * lib/ninix/sakura.py: \v(手前に出てくる)タグを実装. * lib/ninix/script.py: \sタグでサーフェスIDが数値で指定されていて, 先頭に0が 付いている場合は0を削除する("0001"なら"1"に置き換える)ようにした. Wed May 25 2005 Shyouzou Sugitani * バージョン3.5.4リリース. * lib/ninix/sakura.py: \_uタグの処理を実装. (Ying-Chun Liu) Mon May 23 2005 Shyouzou Sugitani * lib/ninix/sakura.py: SHIORIの再読み込み時にNOTIFY otherghostnameが 送りなおされていなかった問題を修正. Fri May 20 2005 Shyouzou Sugitani * バージョン3.5.3リリース. Thu May 19 2005 Shyouzou Sugitani * サーフェスとバルーンの透過率(アルファチャンネル)設定を追加.(未テスト) Tue March 22 2005 Shyouzou Sugitani * バージョン3.5.2リリース. Mon March 21 2005 Shyouzou Sugitani * lib/ninix/home.py, lib/ninix/main.py: ゴースト側で指定されたバルーン名が バルーンのインストールディレクトリ名の場合に対応. * lib/ninix/sstp.py, lib/ninix/main.py: CheckQueueコマンドを拡張. キューに残っている全てのリクエストの数も返すようにした. Fri March 18 2005 Shyouzou Sugitani * バージョン3.5.1リリース. Thu March 17 2005 Shyouzou Sugitani * 本体設定に「喋り終わると裏へ沈む」設定を追加. Tue March 15 2005 Shyouzou Sugitani * gtk.timeout_add(), gtk.timeout_remove()(共にdeprecated)を gobject.timeout_add(), gobject.souce_remove()で置き換えた. * lib/ninix/main.py: 使用率トップのゴーストのai.pngを使用率グラフの背景に 使用するようにした. Sat March 12 2005 Shyouzou Sugitani * バージョン3.5(clover key)リリース. Fri March 11 2005 Shyouzou Sugitani * lib/ninix/dll/misaka.py: $_Constant指定の変数への代入の扱いを変更. * lib/ninix/dll/misaka.py: 関数の引数は{}が無くても評価するように修正. Thu March 10 2005 Shyouzou Sugitani * lib/ninix/dll/misaka.py: if構文でelseが省略されている場合に対応. * lib/ninix/dll/misaka.py: シンボル名に記号などを許すように修正. Mon February 28 2005 Shyouzou Sugitani * lib/ninix/menu.py: メニューのバックグラウンドが指定されている場合に 着せ替えメニューの表示で落ちる問題を修正. Sat February 19 2005 Shyouzou Sugitani * lib/ninix/surface.py: マウスによるサーフェスの移動で座標が0より小さくなる 場所に移動できなくなっていたのを修正. Fri February 18 2005 Shyouzou Sugitani * lib/ninix/surface.py: バルーンの位置計算を修正. Mon February 14 2005 Shyouzou Sugitani * バージョン3.4(bagbiting)リリース. Sun February 13 2005 Shyouzou Sugitani * lib/ninix/aya.py: 「文」のバージョン判定を修正. * 複数キャラクタに対応. Mon February 7 2005 Shyouzou Sugitani * バージョン3.3.7リリース. Sun February 6 2005 Shyouzou Sugitani * lib/ninix/surface.py: サーフェスウインドウを閉じるとゴーストが終了する よう修正.(これまでは幽霊(?)ゴースト化していた.) * lib/ninix/balloon.py: バルーンの内容の描画を調整. Sat February 5 2005 Shyouzou Sugitani * lib/ninix/main.py: 単体のシェルを使用すると落ちる問題(typo)を修正. * lib/ninix/kinoko.py: ウインドウを閉じると落ちる問題(typo)を修正. * lib/ninix/nekodorif.py: ウインドウを閉じると落ちる問題(typo)を修正. Fri February 4 2005 Shyouzou Sugitani * lib/ninix/main.py: \![change,ghost]による自分自身への交代が正しく処理 される よう修正. * lib/ninix/dll/aya5.py: ゴーストの辞書がShift_JIS以外の文字コードの場合にも 動作するように修正. * lib/ninix/dll/aya.py: aya_shiori3.dicがShift_JIS以外の文字コードの場合に バージョン判定に失敗するのを修正. * ninix-installl, ninix-updateが動かなくなっていたのを修正. * lib/ninix/main.pyからバージョン情報をversion.pyとして分離. * lib/ninix/menu.py: メニューのサイドバーとフォントカラー変更の実装を修正. Thu January 27 2005 Shyouzou Sugitani * バージョン3.3.6リリース. Mon January 24 2005 Shyouzou Sugitani * locale/ja.poを更新. * lib/ninix/main.py, lib/ninix/ghost.py: 本体設定でPNAファイルを使用するか どうかを設定できるようにした. * lib/ninix/balloon.py: PNAファイルによるバルーンのアルファチャンネル設定に 対応. * lib/ninix/ghost.py: サーフェスのリセットで自由配置が無効になるバグを修正. * lib/ninix/surface.py: サーフェスのリセットの際に必要以上にオーバーレイ等を 消去してしまうのを修正. Sun January 23 2005 Shyouzou Sugitani * lib/ninix/surface.py: サーフェスの描画を調整. Thu January 20 2005 Shyouzou Sugitani * バージョン3.3.5リリース. Wed January 19 2005 Shyouzou Sugitani * lib/ninix/surface.py: 強制ガーベジコレクション処理を削除. * lib/ninix/pix.py, lib/ninix/surface.py: PNAファイルによるサーフェスのアルファチャンネル設定に対応. (オーバーレイ等についても対応. 本体の透過処理にはXサーバが Composition拡張機能を持ち, 適切に設定されていることが必要. 本体の透過処理についての動作は未確認.) Mon December 20 2004 Shyouzou Sugitani * バージョン3.3.4リリース. Sun December 19 2004 Shyouzou Sugitani * lib/ninix/ngm.py: ネットワーク更新機能を実装. (ゴースト側の更新機能を使用.) Thu December 16 2004 Shyouzou Sugitani * locale/ja.poを更新. * lib/ninix/ngm.pyをgettext化. Mon December 13 2004 Shyouzou Sugitani * lib/ninix/ngm.py: 検索機能を実装. Sun December 12 2004 Shyouzou Sugitani * lib/ninix/ngm.py: GUIを仮実装. Sat December 4 2004 Shyouzou Sugitani * lib/ninix/ngm.py: openngmをベースに実装を開始. Wed November 24 2004 Shyouzou Sugitani * バージョン3.3.3リリース. Tue November 23 2004 Shyouzou Sugitani * lib/ninix-update.py: --listオプションが機能しなくなっていたのを修正. Mon November 22 2004 Shyouzou Sugitani * lib/ninix-lookup.py, lib/ninix/netlib.py, lib/ninix/httplib.py, bin/ninix-lookup.inを削除. * lib/ninix/update.py: Python標準のhttplib.HTTPConnectionを使用するよう変更. * lib/ninix-update.py: update.pyの変更に合わせて修正. Wed November 10 2004 Shyouzou Sugitani * locale/ja.poを更新. * NGMクローンlib/ninix/ngm.pyを追加.(機能は未実装) Tue November 9 2004 Shyouzou Sugitani * doc/examples/gtkrcを削除. Sun October 24 2004 Shyouzou Sugitani * バージョン3.3.2リリース. Sat October 23 2004 Shun-ichi TAHARA * lib/ninix/sakura.py, lib/ninix/script.py, lib/ninix/sstp.py: スクリプトエラーからの復帰処理を追加. Wed October 20 2004 Shyouzou Sugitani * lib/ninix/main.py: gtk.CList(deprecated)ではなくgtk.TreeViewを使うよう変更. Sun October 17 2004 Shyouzou Sugitani * lib/ninix/main.py: ゴーストの起動時点でのサーフェス倍率とスクリプトウエイトの デフォルト設定を反映させるよう修正. * lib/ninix/main.py: gtk.Combo(deprecated)ではなくgtk.ComboBoxを使うよう変更. Wed October 13 2004 Shyouzou Sugitani * ポップアップメニューの生成にはGtkItemFactory(deprecated)ではなく GtkUIManagerを使うよう変更. Mon October 11 2004 Shyouzou Sugitani * lib/ninix/kinoko.py: ontop設定対応にGTK+2.4の機能を使うように変更. * lib/ninix/pix.py, lib/ninix/kinoko.py, lib/ninix/nekodorif.py, lib/ninix/menu.py: 2.2以前のGTK+, pygtkに対応するためのコードを削除. Sun October 10 2004 Shyouzou Sugitani * バージョン3.3.1リリース. Sat October 9 2004 Shyouzou Sugitani * lib/ninix/dll/kawari8.py: _kawari8.soのマルチゴースト対応のために変更. (従来の_kawari8.soでは動作しません.) Wed October 6 2004 Shyouzou Sugitani * バージョン3.3(slayer)リリース. Tue October 5 2004 Shyouzou Sugitani * lib/ninix/satori.py: トーク展開の中でトークを呼び出す場合にはReferenceを リセットするよう修正. Mon October 4 2004 Shyouzou Sugitani * lib/ninix/sakura.pyをsakura.pyとghost.pyに分割. Sun October 3 2004 Shyouzou Sugitani * lib/ninix/surface.py: サーフェスを隠す際にはGtkDrawingAreaを隠して GtkWindowは隠さないようにした. Tue September 28 2004 Shyouzou Sugitani * バージョン3.2(codewalker)リリース. Tue September 28 2004 Shyouzou Sugitani * lib/ninix/satori.py: トーク展開の中でトークを呼び出す場合には\0側と\1側の 切り替え状態を継承しないように修正. Sat September 25 2004 Shyouzou Sugitani * locale/ja.poを更新. Fri September 24 2004 Shyouzou Sugitani * バージョン3.1.8リリース. Thu September 23 2004 Shyouzou Sugitani * lib/ninix/main.py, lib/ninix/sakura.py, lib/ninix/surface.py: サーフェスの当たり判定領域の表示/非表示を本体設定から変更できるようにした. Wed September 22 2004 Shyouzou Sugitani * lib/ninix/main.py: ホームディレクトリが同じninix-ayaの多重起動を禁止. Tue September 21 2004 Shyouzou Sugitani * バージョン3.1.7リリース. * lib/ninix/main.py: SSTP EXECUTE/1.0 CheckQueueで再生中のスクリプトも カウントするように修正. Mon September 20 2004 Shyouzou Sugitani * lib/ninix/surface.py, lib/ninix/seriko.py: 表示されているのと同じサーフェスが指定された場合にそれまでのアニメーション パターンが残ったままリセットされない問題を修正. (「猫刻」のSakura側サーフェスで起きていた問題の修正.) * lib/ninix/dll/satori.py: OnCloseが返すスクリプトに'\-'を付加するよう修正. * lib/ninix/dll/satori.py: 辞書フォルダが変更されてもmasterにあるreplace.txtと replace_after.txtを適用するようにした. Sat September 18 2004 Shyouzou Sugitani * lib/ninix/main.py, lib/ninix/sakura.py: '\-'タグが機能しなくなっていたのを 修正. * lib/ninix/main.py, lib/ninix/sakura.py: ゴーストの終了はOnCloseイベントで SHIORIが返すスクリプトの中にある'\-'タグによって行なうよう修正. Sat September 18 2004 Shyouzou Sugitani * バージョン3.1.6リリース. Fri September 17 2004 Shyouzou Sugitani * lib/ninix/kinoko.py: ontop設定に仮対応. * lib/ninix/kinoko.py: アニメーションでターゲットゴーストのサーフェスや その位置が変わった時にもスキンの位置を合わせるようにした. * lib/ninix/sstp.py, lib/ninix/main.py: SSTPのEXECUTE/1.0にCheckQueueコマンドを 追加. Senderに一致するクライアントからのリクエストが何個キューに残っているかを 返す. (SSTP Bottleクライアントが利用するのを想定した機能.) Wed September 15 2004 Shyouzou Sugitani * バージョン3.1.5リリース. Tue September 14 2004 Shyouzou Sugitani * lib/ninix/sakura.py: GhostクラスからObserverへ状態変化を通知する機構を改良. それに合わせてサーフェス等の状態変化の情報がGhostクラスに集まるように変更. * lib/ninix/dll.py: SAORI DLL互換モジュールがSakuraクラスにアクセスできるように する機能を削除. 代わりにGhostクラスへのアクセス方法を提供するよう変更. * lib/ninix/kinoko.py: ターゲットゴーストのアイコン化に合わせてスキンを隠すよう にした. Tue September 7 2004 Shyouzou Sugitani * lib/ninix/kinoko.py: ポップアップメニューの設定(settings)を選択すると落ちる 問題を修正.(Thanks to kawaharaさん) Mon September 6 2004 Shyouzou Sugitani * バージョン3.1.4リリース. Sun September 5 2004 Shyouzou Sugitani * lib/ninix/nekodorif.py: 画面上端/下端からの距離設定を登場位置に反映させるよう にした. * lib/ninix/nekodorif.py: ターゲットゴーストの倍率に合わせてサーフェス倍率を 変えるようにした. * lib/ninix/nekodorif.py: サーフェスをマウスドラッグで移動可能にした. * lib/ninix/nekodorif.py: omni.txtによる自由移動の設定に対応. Mon August 30 2004 Shyouzou Sugitani * バージョン3.1.3リリース. Sun August 29 2004 Shyouzou Sugitani * 「猫どりふ」互換機能を改良. 実際に物を落としてSHIORIイベントを発生させることが可能になった. Wed August 18 2004 Shyouzou Sugitani * バージョン3.1.2リリース. Tue August 17 2004 Shyouzou Sugitani * lib/ninix/dll/aya.py: 「文」のバージョン判定を修正. Mon August 16 2004 Shyouzou Sugitani * lib/ninix-install.py: 「猫どりふ」スキン/落下物のインストールに対応. Tue August 3 2004 Shyouzou Sugitani * バージョン3.1.1リリース. Mon August 2 2004 Shyouzou Sugitani * lib/ninix-install.py: 「きのこ」スキンのインストールに対応. * 「きのこ」互換機能を改良. * 「猫どりふ」互換機能を改良. * lib/ninix/sakura.py: Ghostクラスに他のオブジェクト(Observer)へ状態の変化を 通知する機構を仮実装.(現在は「きのこ」への通知に使用.) Thu July 15 2004 Shyouzou Sugitani * バージョン3.1(heavy wizardry)リリース. Wed July 14 2004 Shyouzou Sugitani * 「きのこ」互換機能kinoko.pyを追加.(未完成) * 「猫どりふ」互換機能nekodorif.pyを追加.(未完成) * lib/ninix/dll/satori.py: カッコ展開の結果を返す際に先頭と末尾の空白を 削除するよう修正.(「翼の庭」でクシーイベントが発生しない問題への対策.) Tue June 22 2004 Shyouzou Sugitani * whrandom(deprecated)ではなくrandomモジュールを使用するよう変更. Mon June 7 2004 Shyouzou Sugitani * バージョン3.0(magic smoke)リリース. Thu June 3 2004 Shyouzou Sugitani * lib/ninix/balloon.py: バルーン上でのマウス移動イベントの処理を修正. Mon May 31 2004 Shyouzou Sugitani * lib/ninix/sakura.py: ninix-installが動かなくなっていたのを修正. Wed May 26 2004 Shyouzou Sugitani * バージョン2.9.9リリース. Sun May 23 2004 Shyouzou Sugitani * lib/ninix/main.py, lib/ninix/sakura.py, lib/ninix/surface.py: ゴーストのアイコン化と復帰の際にはIfGhost指定の無いSSTPの送信対象となる ゴーストを適当に選択し直すようにした. * lib/ninix/sakura.py: スクリプトキューの処理でもcantalkフラグをチェックする ように修正. Sat May 22 2004 Shyouzou Sugitani * lib/ninix/main.py, lib/ninix/sakura.py: 一時起動と交代もしくは複数の交代で 交代先として同じゴーストが選択された場合にエラーが発生するのを修正. Thu May 20 2004 Shyouzou Sugitani * lib/ninix/balloon.py: バルーンの倍率が設定されない場合があるのを修正. Wed May 19 2004 Shyouzou Sugitani * lib/ninix/main.py: 一部の設定で$NINIX_HOME/preferencesに設定値が無い場合に エラーが発生して起動しない問題を修正.(デフォルト値の設定個所を修正.) Tue May 18 2004 Shyouzou Sugitani * lib/ninix/main.py: SSTPサーバの受信処理のためのタイマー割り込みをApplication に追加して, Ghostのタイマー割り込みから処理を削除. * lib/ninix/main.py, lib/ninix/sakura.py: SSTP SEND/1.4のスクリプト再生が 停止してしまうバグを修正. Mon May 17 2004 Shyouzou Sugitani * バージョン2.9.8リリース. Sun May 16 2004 Shyouzou Sugitani * lib/ninix/dll/aya5.py: SAORI互換モジュール呼び出しに対応. (_aya5.soについてもSAORI互換モジュール呼び出しに対応したものが必要.) Sat May 15 2004 Shyouzou Sugitani * lib/ninix/sakura.py, lib/ninix/menu.py: \![open,configurationdialog]で エラーが発生するのを修正. * lib/ninix/sakura.py: サーフェスウインドウのアイコンが設定されなくなっていた のを修正. Fri May 14 2004 Shyouzou Sugitani * バージョン2.9.7リリース. Thu May 13 2004 Shyouzou Sugitani * locale/ja.poを更新. * lib/ninix/sstp.py, lib.ninix/main.py, lib/ninix/sakura.py: SSTP SEND/1.4の処理でスクリプト再生が終了するまでは次のリクエストのスクリプト 処理を(ゴーストの一時起動も含めて)開始しないようにした. * lib/ninix/sstp.py, lib.ninix/main.py: SSTP SEND/1.4のリクエストのIfGhostで 指定されたゴーストがいない場合に, スクリプトを他のゴーストで再生するかどうかを 設定できるようにした.(本体設定の「色々」->「SSTP 設定」) Mon May 10 2004 Shyouzou Sugitani * バージョン2.9.6リリース. Sun May 09 2004 Shyouzou Sugitani * lib/ninix/main.py: ゴーストの召喚で2重に起動処理を呼び出していたのを修正. * lib/ninix/sakura.py, lib/ninix/communicate.py: 起動中ゴーストのリストが 正しく更新されていなかったのを修正. * lib/ninix/surface.py, lib/ninix/seriko.py: アニメーションで別のパターンを 発動させる場合(start, alternativestart)は次のパターンを即開始するように修正. (長い間原因不明だった「白子&アフ郎」でのちらつきが修正された.) Sat May 08 2004 Shyouzou Sugitani * lib/ninix/surface.py: \s[]タグで指定されたサーフェスIDがアニメーション パターンだった場合にタグ処理の時点で(アニメーションの開始前に)デフォルト サーフェスが表示されてしまうのを修正. この修正の関係で存在しないサーフェスが指定された場合の動作が変更になった. (これまでデフォルトサーフェスを表示していたのが表示しているサーフェスを 変更しないようになった.) * lib/ninix/seriko.py: baseメソッドで指定されたサーフェスのIDがアニメーション 開始前のサーフェスIDに一致した時に表示されない問題を修正. (「フサギコ漫談」のフッサールとギコの登場アニメーションなど.) Fri May 07 2004 Shyouzou Sugitani * バージョン2.9.5リリース. * lib/ninix/main.py, lib/ninix/sakura.py: ゴーストの一時起動を実装. IfGhost指定付きSSTPを処理する場合に当該ゴーストがインストールされていて 起動していない状態の時にはゴーストを起動してSSTPを処理する. このモードではSHIORIリクエストやネットワーク更新などは機能せず, SSTPの 処理が終わるとゴーストは自動的に終了する. Thu May 06 2004 Shyouzou Sugitani * lib/ninix/main.py, lib/ninix/sakura.py: ゴーストの再読み込みの処理を修正. ゴーストの停止を確実に実行するようにしたのと交代/シェル変更の判定を修正. Fri April 30 2004 Shyouzou Sugitani * バージョン2.9.4リリース. Wed April 28 2004 Shyouzou Sugitani * lib/ninix/surface.py: 画像合成(element)でメソッドにbaseが指定された場合の 処理を追加.(仕様が不明なので適当に処理.) * lib/ninix/sakura.py: 「BTH小っちゃいってことは便利だねっ」を動作させる時だけ SHIORIリクエストのヘッダでSenderをSSPと詐称.(所長さん, 竜王さんゴメンなさい.) 一部SSP独自のSakura Scriptタグに対応できてないので動作は不完全. * lib/ninix/aya.py: 文3ゴーストがサポートされなくなってしまっていたのを修正. Tue April 27 2004 Shyouzou Sugitani * lib/ninix/main.py: ninix 0.8で未実装のまま使われることのなかったISCP関係の コードを削除. Tue April 27 2004 Shyouzou Sugitani * バージョン2.9.3リリース. * lib/ninix/surface.py: 拡大側のサーフェス倍率を追加.(動作確認無し) Mon April 26 2004 Shyouzou Sugitani * lib/ninix/dll/aya5.pyを追加. 動作させるには_aya5.soモジュールが必要. (Thanks to linjianさん) * lib/ninix/dll/aya.py: 栞判定で文4のみサポートするように制限. Wed April 21 2004 Shyouzou Sugitani * lib/ninix/sstp.py, lib/ninix/main.py: SSTPのEXECUTEにGetNamesコマンドを追加. インストールされている起動可能な全ゴーストの名前(sakura.name)を返す. (SSTP Bottleクライアントが利用するのを想定した機能.) Tue April 20 2004 Shyouzou Sugitani * lib/ninix/main.py, lib/ninix/sakura.py: 起動時に全ゴーストについて Ghostのインスタンスを作成して情報を管理するようにするための変更を開始. * lib/ninix/sakura.py: SHIORIリクエストの文字コード設定をSakuraからGhostに移動. * lib/ninix/balloon.py, lib/ninix/surface.py, lib/ninix/sakura.py, lib/ninix/menu.py, lib/ninix/main.py: サーフェス縮小, バルーン縮小のパラメータをそれぞれSurface, Balloonに移した. * lib/ninix/sakura.py: ネットワーク更新後に落ちる問題を修正. Tue April 13 2004 Shyouzou Sugitani * バージョン2.9.2リリース. Mon April 12 2004 Shyouzou Sugitani * lib/ninix/sstp.py, lib/ninix/main.py: IfGhostによるSSTPの振り分けを実装. * lib/sstplib.py, lib/ninix/sstp.py, lib/ninix/sakura.py, lib/ninix/main.py: UNIXドメインソケット方式のDirectSSTPサーバを削除して新しいcommunicate.pyを 使用するように変更. * lib/ninix/communicate.py: 複数ゴースト起動の実装に合わせてゴースト間 コミュニケーションの方法を簡素化. Sun April 11 2004 Shyouzou Sugitani * locale/ja.poを更新. * lib/ninix/surface.py: サーフェスのアイコン化のイベントハンドラを修正. * lib/ninix/main.py, lib/ninix/sstp.py, lib/ninix/sakura.py: SSTPサーバのインスタンスの管理をSakuraからApplicationに移動. * lib/ninix/main.py, lib/ninix/sakura.py: ゴーストの消滅指示確認ダイアログを ApplicationからGhostのメンバに変更. * lib/ninix/main.py, lib/ninix/sakura.py, lib/ninix/menu.py: ゴーストの複数起動をサポート. * lib/ninix/surface.pt, lib/ninix/balloon.py: サーフェス, バルーンにfinalize メソッドを追加. 各ウインドウの破壊を実行. * lib/ninix/balloon.py: フォントの設定を全体の設定ファイル $NINIX_HOME/preferencesに移動. pango_fontrcは廃止. Sun April 4 2004 Shyouzou Sugitani * lib/ninix/sakura.py: HISTORYファイルの保存処理をSakuraからGhostに移動. * lib/ninix/sakura.py: CommunicateのインスタンスをSakuraからGhostに移動. * lib/ninix/sakura.py: 消滅回数と起動時間の記録をSakuraからGhostに移動. * lib/ninix/sakura.py: イベント処理関数の引数を可変個にした. * lib/ninix/sakura.py, lib/ninix/surface.py: サーフェス上でのマウスホイール イベントの処理をSakuraからSurfaceに移動. Sat April 3 2004 Shyouzou Sugitani * バージョン2.9.1リリース. Fri April 2 2004 Shyouzou Sugitani * lib/ninix/balloon.py: バルーン上でのマウス移動イベントの生成と処理を サーフェスと同じように変更. * lib/ninix/sakura.py, lib/ninix/surface.py: キー入力イベントでのキーコードの 変換処理を最初にイベントを受け取るSurfaceに移動. * lib/ninix/sakura.py, lib/ninix/surface.py: 見切れと重なり判定をSakuraから Surfaceに移動. * lib/ninix/sakura.py, lib/ninix/surface.py: サーフェス上でのマウスの移動 イベントの処理方法を変更. Thu April 1 2004 Shyouzou Sugitani * lib/ninix/main.py: サーフェス倍率と表示ウエイトのデフォルト設定が保存されて いなかったのを修正. Wed March 31 2004 Shyouzou Sugitani * バージョン2.9(firebottle)リリース. Tue March 30 2004 Shyouzou Sugitani * lib/ninix/dll/misaka.py: 文字コード変換のバグを修正. * Python2.xの新機能を使って一部のコードを書き直した. Mon March 29 2004 Shyouzou Sugitani * Applicationからゴーストを構成するクラスのインスタンスへのアクセスを Ghostへ集約. * Ghost以外のゴーストを構成するクラスのインスタンス生成をApplicationから Ghostに移した. Wed March 24 2004 Shyouzou Sugitani * これまでは他のゴーストの専用バルーンも含めた全バルーンが選択できたのを そのゴーストの専用バルーンと汎用バルーンに制限. (デフォルトバルーンは汎用バルーンからのみ選択可能.) * lib/ninix/menu.py: メニューの各項目のアップデート方法を変更. * lib/ninix/menu.pyを追加. ポップアップメニューの処理をsurface.pyから移した. Tue March 16 2004 Shyouzou Sugitani * lib/ninix/seriko.py: SERIKO/2.0およびMAYUNA/1.x in SERIKO/2.0に対応. Mon March 15 2004 Shyouzou Sugitani * lib/ninix/main.py, lib/ninix/sakura.py, lib/ninix/surface.py: 表示ウエイトとサーフェス倍率のメニューの管理をMenuクラスに移した. それと合わせてこれらのデフォルト設定は本体設定で行なうように変更. (将来の複数ゴースト起動の実装を考えての変更.) Tue March 9 2004 Shyouzou Sugitani * バージョン2.7.2リリース. Sun March 7 2004 Shyouzou Sugitani * lib/ninix/sakura.py: 2.0.0までのpygtkのgtk.Menu.popup()のバグ対策を修正. Sat March 6 2004 Shyouzou Sugitani * lib/ninix/surface.py: pix.create_blank_pixbuf()を使用するよう変更. * lib/ninix/pix.py: 2.0.0までのpygtkのバグへの対策が入ったcreate_blank_pixbuf 関数を追加. Wed March 3 2004 Shyouzou Sugitani * lib/ninix/mayuna.pyをlib/ninix/seriko.pyに統合. Tue March 2 2004 Shyouzou Sugitani * lib/ninix/kawari.py: ターミナル出力をUTF-8に変更. * lib/ninix/niseshiori.py: ターミナル出力をUTF-8に変更. * lib/ninix/niseshiori.py: 文字コードをUTF-8に変更. Mon March 1 2004 Shyouzou Sugitani * lib/ninix/dll/aya.py: デバッグ出力はdebugの値が設定されている時のみターミナル に出すよう修正. Sun February 29 2004 Shyouzou Sugitani * 互換栞モジュールのshow_descriptionメソッドで表示されるCopyrightを更新. Sat February 28 2004 Shyouzou Sugitani * doc/extension.txt, doc/kawari.txt, doc/saori.txt: 文字コードをUTF-8に変更. Thu February 26 2004 Shyouzou Sugitani * ChangeLogとREADMEの文字コードをUTF-8に変更. * Pythonコードのファイルに文字コード指定とCopyright表記を追加. Sun February 22 2004 Shyouzou Sugitani * 互換SAORIモジュールでdll.pyのテンプレートクラスを利用するよう変更. * lib/ninix/dll.py: Saoriクラスのテンプレートクラスを追加. Sat February 21 2004 Shyouzou Sugitani * lib/ninix/dll/hanayu.py: 文字コードをUTF-8に変更. * lib/ninix/dll/mciaudior.py: 文字コードをUTF-8に変更. Thu February 19 2004 Shyouzou Sugitani * lib/ninix/entry_db.py, lib/ninix/script.py: 文字コードをUTF-8に変更. Wed February 18 2004 Shyouzou Sugitani * lib/ninix/main.py, lib/ninix/balloon.py, lib/ninix/dll/bln.py, lib/ninix/dll/hanayu.py: pygtk-1.99.14以前のpango.Layout.set_text()の仕様に対応するためのコードを削除. Mon February 16 2004 Shyouzou Sugitani * lib/ninix/dll/misaka.py: 文字列操作の修正忘れ2個所を修正. Thu February 12 2004 Shyouzou Sugitani * バージョン2.7.1リリース. Wed February 11 2004 Shyouzou Sugitani * lib/ninix/htmllib.pyを削除. * lib/ninix/sakura.py: 文字コードの変更が必要なhtmllibを捨ててPython2.3で 追加されたhtmlentitydefs.name2codepointを使用するようにした. Python2.3以前の環境では"\&[id]"は正しく変換されない. Tue February 10 2004 Shyouzou Sugitani * lib/ninix/sakura.py: ファイルの文字コードをUTF-8に変更. * lib/ninix/surface.py: サーフェスのPixbufをキャッシュから廃棄した際には 強制的にガーベジコレクションを実行するようにした. * lib/ninix/dll/misaka.py: ターミナル出力の文字コードをUTF-8に変更. * lib/ninix/dll/misaka.py: 内部文字コードをUnicodeに変更.(ファイルはUTF-8.) Mon February 9 2004 Shyouzou Sugitani * lib/kanjilib.pyを削除. * lib/ninix/dll/aya.py, lib/ninix/dll/satori.py, lib/ninix/dll/misaka.py: kanjilibを使用しないように変更. Sun February 8 2004 Shyouzou Sugitani * lib/ninix/sakura.py: sakura.py, surface.py, balloon.pyに分割. * lib/ninix/sakura.py: Menuクラスを新設. Tue February 3 2004 Shyouzou Sugitani * lib/ninix-install.py: オーナードローメニュー用画像などの一部のファイルが インストールされなかったのを修正. Mon February 2 2004 Shyouzou Sugitani * lib/ninix/sakura.py: オーナードローメニュー用画像をポップアップメニューで 利用するようにした. * lib/ninix/home.py, lib/ninix/main.py, lib/ninix/sakura.py: サーフェスの置かれているパスを後から取得できるよう変更. * lib/ninix/sakura.py: サーフェスウインドウのタイトルをsakura.name, keronameに それぞれ設定するようにした. Mon February 2 2004 Shyouzou Sugitani * バージョン2.7リリース. * lib/ninix/main.py: -Rオプションを削除. * lib/ninix/main.py: Pythonのトレースバック出力専用ダイアログを追加. Mon February 2 2004 Shyouzou Sugitani * バージョン2.6リリース. Fri January 30 2004 Shyouzou Sugitani * lib/ninix/dll/kawari.py: 辞書のパスの文字コードが正しく変換されていなかった のを修正. Thu January 29 2004 Shyouzou Sugitani * lib/ninix/dll/satori.py: 内部呼び出し, SAORI呼び出しの引数計算の戻り値の型を 修正. * lib/ninix/dll/bln.py: 引数のチェックを追加. Tue January 27 2004 Shyouzou Sugitani * lib/ninix/dll/hanayu.py: hanayu.txtの読み込みを修正. * lib/ninix/sakura.py: バルーンの画像設定のエラー処理を修正. Mon January 26 2004 Shun-ichi TAHARA * lib/ninix/dll/satori.py: 改行処理を修正. Sun January 25 2004 Shyouzou Sugitani * lib/ninix/seriko.py: メソッドの省略時にはbaseとして処理するように変更. * lib/ninix/seriko.py: インターバルが負値の場合には絶対値を使うように変更. * lib/ninix/dll/satori.py: expand()の再帰で意図せずに\0側から\1側に切り替わって しまう問題を修正. * lib/ninix/dll/satori.py: 「会話時サーフェス戻し」の処理をスクリプト生成の時に 行なうよう変更. Thu January 22 2004 Shyouzou Sugitani * lib/ninix/dll/satori.py: 「会話時サーフェス戻し」の動作を再度修正. Thu January 15 2004 Shyouzou Sugitani * lib/ninix/dll/saori_cpuid.py: OSとプラットフォーム情報の取得に対応. Tue January 13 2004 Shyouzou Sugitani * バージョン2.5.8リリース. Mon January 12 2004 Shyouzou Sugitani * lib/ninix/sakura.py: Python2.3でバルーン配置がおかしくなる問題を修正. * lib/ninix/sakura.py: descript.txtだけでなくsurfaces.txtの sakura.balloon.offset[xy], kero.balloon.offset[xy]にも対応. Sun January 11 2004 Shyouzou Sugitani * lib/ninix/sakura.py, locale/ja.po: メニューアイテムにアクセラレータを設定. Sat January 10 2004 Shyouzou Sugitani * lib/ninix/sakura.py: 表示する文字が含まれていないスクリプト(改行のみなど)の 場合にはバルーンを出さないようにした. * lib/ninix/dll/satori.py: 「会話時サーフェス戻し」の動作を以前のものに戻した. Fri January 9 2004 Shun-ichi TAHARA * lib/ninix/dll/satori.py: タグ処理を修正. Thu January 8 2004 Shyouzou Sugitani * lib/ninix/dll/hanayu.py: radar形式のグラフに対応. * lib/ninix/dll/satori.py: 内部呼び出し, SAORI呼び出しの引数計算に対応. Wed January 7 2004 Shyouzou Sugitani * lib/ninix/dll/satori.py: SAORIの複数返値に対応. Tue January 6 2004 Shyouzou Sugitani * lib/ninix/sakura.py: 一部のアニメーションが動かなかったのを修正. Mon January 5 2004 Shun-ichi TAHARA * lib/ninix/main.py: 子プロセス(プラグイン)処理の修正. * lib/ninix/main.py: セッション周りの修正. Fri December 26 2003 Shyouzou Sugitani * バージョン2.5.7リリース. Sun December 21 2003 Shun-ichi TAHARA * lib/ninix/main.py, locale/ja.po: デフォルトバルーンの設定に 「常にこのバルーンを使う」チェックボックスを追加. Fri December 19 2003 Shyouzou Sugitani * lib/ninix/dll/ssu.py: 関数追加. Thu December 18 2003 Shun-ichi TAHARA * lib/ninix-install.py: archiveディレクトリの作成を修正. Wed December 17 2003 Shyouzou Sugitani * lib/ninix/dll/ssu.py: 関数追加. Wed December 17 2003 Shun-ichi TAHARA * lib/ninix/dll/satori.py: タグ処理を再度修正. Tue December 16 2003 Shyouzou Sugitani * lib/ninix/dll/satori.py: 括弧内の改行を無視するようにした. * lib/ninix/main.py: バルーン切り替えの際にSakuraを再起動しないように変更. Mon December 15 2003 Shyouzou Sugitani * ssu.dll互換モジュールssu.pyを追加. Mon December 15 2003 Shun-ichi TAHARA * lib/ninix/dll/satori.py: 乱数の範囲指定に負の値が入っている場合に対応. Mon December 15 2003 Atzm Watanabe * lib/ninix/dll/aya.py: _in_, !_in_の両辺の型をチェックするよう修正. Fri December 12 2003 Shyouzou Sugitani * バージョン2.5.6リリース. Fri December 12 2003 Shun-ichi TAHARA * lib/ninix/dll/satori.py: タグ処理を再度修正. * lib/ninix/sakura.py: 明示的にサーフェス指定が来ない限りサーフェスを出さない よう変更. * lib/ninix/sakura.py: メニューラベルのアクセラレータ指定に対応. * lib/ninix/sakura.py: メニューラベルのリソース取得範囲を拡張. Thu December 11 2003 Shyouzou Sugitani * lib/nini/dll/satori.py: SAORI互換モジュール呼び出しに対応. Thu December 11 2003 Shun-ichi TAHARA * lib/ninix/dll/satori.py: 改行の挿入/削除処理を修正. * lib/ninix/dll/satori.py: カッコ付きのセリフが消えていたのを修正. * lib/ninix/dll/satori.py: 「次から○〜△回目のトーク」形式の予約トークに対応. Wed December 10 2003 Shyouzou Sugitani * lib/ninix/dll/satori.py: 辞書フォルダの切り替えタイミングを変更. * lib/ninix/dll/satori.py: デフォルトサーフェスの記録をサーフェス加算値よりも 後になるように修正. Wed December 10 2003 Shun-ichi TAHARA Shyouzou Sugitani * lib/ninix/dll/satori.py: 辞書フォルダと起動回数を保存するようにした. * lib/ninix/dll/satori.py: 前回終了時サーフェスを2重に記録していたのを新しい値 のみ保存するよう修正. Tue December 9 2003 Shun-ichi TAHARA Shyouzou Sugitani * lib/ninix-install.py: パス名に含まれるバッククオートをエスケープするように 修正. Fri December 5 2003 Shyouzou Sugitani * バージョン2.5.5リリース. * lib/ninix/dll/niseshiori.py: %m?を展開できるようにした. Thu December 4 2003 Shyouzou Sugitani * saori_cpuid.dll似非互換モジュールsaori_cpuid.pyを追加.(動作テスト用) * lib/ninix/sakura.py: %etの展開でエラーが発生するのを修正. * lib/ninix/dll/kawari.py, lib/ninix/dll/niseshiori.py: メタ文字列の展開が機能 していなかったのを修正. Wed December 3 2003 Shun-ichi TAHARA * lib/ninix/dll/kawari.py, lib/ninix/dll/niseshiori.py, lib/ninix/sakura.py: デバッグ出力のエンコード処理を修正. Wed December 3 2003 Shyouzou Sugitani * lib/ninix/sakura.py: passivemode中に最小化された場合はイベントは発生させず, サーフェスとバルーンの状態を維持するよう修正. * lib/ninix/sakura.py: passivemode中にSSTPメッセージを受けてしまう問題が直って いなかったのを修正. * lib/ninix/dll/aya.py: システム関数ERASEVARIABLEをサポート. Tue December 2 2003 Shyouzou Sugitani * lib/ninix/dll/hanayu.py: タイトルが空文字列の時にエラーが発生するのを修正. * lib/ninix/dll/hanayu.py: 明示的にフォントファミリーを設定するよう修正. Tue December 2 2003 Shun-ichi TAHARA * lib/ninix/sakura.py: サーフェスの当り判定のIDを0〜255に拡張. * lib/ninix/sakura.py: エンコーディングまわりを修正.(Patch#3408) cjkcodecs(要1.0.2)にも対応, iconvcodecはダメ. Mon December 1 2003 Shyouzou Sugitani * バージョン2.5.4リリース. * lib/ninix/dll/niseshiori.py: 「ポータル」, 「おすすめ」用URLの取得を修正. * lib/ninix/sakura.py: フォントサイズの計算を調整. Sun November 30 2003 Shyouzou Sugitani * lib/ninix/sakura.py: 選択肢の処理を変更. Sun November 30 2003 Shyouzou Sugitani * バージョン2.5.3リリース. Sat November 29 2003 Shyouzou Sugitani * lib/ninix/main.py: ゴースト交代後に古いポップアプメニュー関連のオブジェクトが 廃棄される前にApplicationクラスのメニューアイテムをdetachするよう修正. * lib/ninix/sakura.py: BalloonWindow.motion_notify()の表示範囲のチェック忘れを 修正. Sat November 29 2003 Shun-ichi TAHARA Shyouzou Sugitani * lib/ninix/sakura.py: バルーンのフォントサイズの設定が実際の表示に反映される よう修正.(Patch#3380) Fri November 28 2003 Shyouzou Sugitani * lib/ninix/sakura.py: バルーンフォント設定の変更が即反映されるように修正. * lib/ninix/sakura.py: %usernameの展開で最初にSHIORIで設定されている値を問い合 わせるようにした. * lib/ninix/sakura.py: Ghost.get_event_response()がNoneを返してしまう問題を修正 (空文字列を返すようにした). Wed November 26 2003 Shun-ichi TAHARA * lib/ninix/sakura.py: Communicate Boxの入力後にSSTP COMMUNICATEが発生する時 UnicodeErrorが起きるのを修正.(Patch#3377) * lib/ninix/sakura.py: Teach Boxからの入力でUnicodeErrorが発生するのを修正.(Patch#3377) * lib/ninix/sakura.py: Communicate Boxへの入力の際にXIMの変換確定のEnterで入力 処理が呼ばれてしまい, 空文字列が入力されてしまうのを修正.(Patch#3377) * lib/ninix/main.py: デフォルトバルーン設定のバグを修正.(Patch#3379) Tue November 25 2003 Shyouzou Sugitani * lib/ninix/dll/satori.py: 内部関数call, loopを実装. Mon November 24 2003 Shyouzou Sugitani * lib/ninix/dll/satori.py: 内部関数remember, setを実装. Fri November 14 2003 Shyouzou Sugitani * lib/ninix/dll/satori.py: 前回終了時サーフェスの取得をサポート. * lib/ninix/dll/satori.py: 辞書情報の取得をサポート. Thu November 13 2003 Shyouzou Sugitani * バージョン2.5.2リリース. Wed November 12 2003 Shyouzou Sugitani * lib/ninix/dll/satori.py: デフォルトサーフェスの設定が機能していなかったのを 修正して, サーフェス戻しの実装方法を変更. Tue November 11 2003 Shyouzou Sugitani * lib/ninix/dll/satori.py: アンカー辞書をサポート. * lib/ninix/dll/satori.py: タイマーと予約トークもセーブするようにした. * lib/ninix/dll/satori.py: 辞書フォルダをサポート. * lib/ninix/dll/satori.py: 予約トークをサポート. * lib/ninix/dll/satori.py: OnTalkイベントをサポート. * lib/ninix/dll/satori.py: 文の途中でコメント(#)を使えるようにした. * lib/ninix/dll/satori.py: サーフェス加算値をサポート. * lib/ninix/dll/satori.py: 自動セーブ, 手動セーブをサポート. * lib/ninix/dll/satori.py: OnUpdateReadyの(R0)に1加算するようにした. Tue November 11 2003 Shyouzou Sugitani * バージョン2.5.1リリース. Sun November 9 2003 Shyouzou Sugitani * lib/ninix/dll/satori.py: 変数と文と単語群の存在確認をサポート. Fri November 7 2003 Shyouzou Sugitani  * lib/ninix/dll/satori.py: セーブデータの暗号保存をサポート. Thu November 6 2003 Shyouzou Sugitani * lib/ninix/dll/satori.py: OnRecommandedSiteChoiceイベントをサポート. * lib/ninix/dll/satori.py: 「ポータル」, 「おすすめ」用のURLリスト取得の サポートを追加. * lib/ninix/sakura.py: 「ポータル」, 「おすすめ」の中の項目を選択した際に OnRecommandedSiteChoiceイベントを発生させるようにした. Tue November 4 2003 Shyouzou Sugitani * lib/ninix/dll/satori.py: OnSatoriBoot, OnSatoriCloseのサポートを追加. * lib/ninix/dll/satori.py: 改行の挿入位置を調整. * lib/ninix/dll/satori.py: satori_conf.txtが暗号化されている場合に対応. * lib/ninix/dll/satori.py: 選択ID, 選択ラベル, 選択番号を取得可能に. * lib/ninix/sakura.py: OnChoiceSelectedとOnChoiceEnterのReferenceを勝手に拡張. * lib/ninix/sakura.py: OnChoiceEnterイベントのサポートを追加. Tue November 4 2003 Shyouzou Sugitani * バージョン2.5リリース. Mon November 3 2003 Shyouzou Sugitani * lib/ninix/dll/satori.py: 最終トークからの経過秒を取得可能に. * lib/ninix/dll/satori.py: OnSatoriLoad, OnSatoriUnloadをサポート. * lib/ninix/dll/satori.py: 自動挿入ウェイトの倍率をサポート. * lib/ninix/dll/satori.py: さくらスクリプトを自動挿入ウェイトの計算対象にしない よう修正. * lib/ninix/dll/satori.py: マウスホイール反応をサポート. * lib/ninix/dll/satori.py: なで反応の感度を調整. * lib/ninix/dll/satori.py: cantalkが0の場合の処理を追加. 自発喋りのカウントを 行わないようにした. またタイマのカウントは実行するが発動は遅らせるようにした. * lib/ninix/dll/satori.py: 選択肢(\qタグ)の形式を変更. * lib/ninix/dll/satori.py: スコープ切り換えの際に改行を追加するようにした. * lib/ninix/dll/satori.py: スコープ切り換えの際の改行の再配置を削除. Tue November 4 2003 Shyouzou Sugitani * バージョン2.4リリース. Sun October 26 2003 Shyouzou Sugitani * バージョン2.3.8リリース. Fri October 24 2003 Shyouzou Sugitani * locale/ja.poを更新. * lib/ninix/main.py: サーフェス倍率の最小値を10%から40%に変更. * lib/ninix/sakura.py: サーフェスに合わせてバルーンも縮小できるように変更. Mon October 20 2003 Shyouzou Sugitani * lib/ninix/main.py: 本体設定にデフォルトバルーンの設定を追加. ポップアップメニューのバルーンの項目は起動中のゴーストのバルーンを一時的に変更 するのみにした. Thu October 16 2003 Shyouzou Sugitani * バージョン2.3.7リリース. Wed October 15 2003 Shyouzou Sugitani * lib/ninix/sakura.py: 起動時点ではサーフェスはファイルチェックのみ行ない, ファイルからのgtk.gdk.Pixbufの作成は必要になってから行うように変更. 作成したPixbufはキャッシュに入れられ, 参照されない状態が続くと破棄される. * lib/ninix/pix.py: Segfaultを引き起こすためgtk.gdk.Pixbuf作成後に行なっていた ガーベジコレクションの実行を削除. * 全ての画像読み込みをpix.py経由に変更. Wed October 15 2003 Shyouzou Sugitani * バージョン2.3.6リリース. Tue October 14 2003 Shyouzou Sugitani * メモリリークを起こすgtk.gdk.pixbuf_new_from_file()のかわりに gtk.gdk.PixbufLoaderを使用するよう変更. また, pixbufの作成の後にガーベジコレクションを実行するようにした. Fri October 10 2003 Shyouzou Sugitani * バージョン2.3.5リリース. Thu October 9 2003 Shyouzou Sugitani * lib/ninix/sakura.py: passivemode中にSSTPを受信した場合にはpassivemodeを抜ける まで再生を始めないよう修正. Wed October 8 2003 Shyouzou Sugitani * lib/ninix/sakura.py: サーフェス移動中にゴーストの動作を停止させないよう変更. * lib/ninix/dll/wmove.py: wmove.dll互換モジュール追加. Tue October 7 2003 Shyouzou Sugitani * lib/ninix/sstp.py: UNIXドメインソケット版のSSTPサーバを追加した際に入ったバグ を修正. * lib/ninix/dll/aya.py: SAORIの戻り値(Value*)の処理を修正. * lib/ninix/dll/aya.py: 四則演算の際の型変換のルールを修正. Sat October 4 2003 Shyouzou Sugitani * lib/ninix/sakura.py: ポップアップメニューの「ポータル」, 「おすすめ」を実装. * lib/ninix/dll/kawari.py: SHIORI判定の戻り値を修正. Fri October 3 2003 Shyouzou Sugitani * バージョン2.3.4リリース. Thu October 2 2003 Shyouzou Sugitani * lib/ninix/sakura.py: ポップアップメニューに項目を追加.(機能自体は未実装.) それに伴ないlocale/ja.poを更新. * lib/ninix/sakura.py: Shellのdescript.txtのseriko.alignmenttodesktopに対応. * lib/ninix/sakura.py: 全てのデスクトップに居座るようにする設定をポップアップ メニューに追加.(ウインドウマネージャによっては正しく機能しないことがある.) Tue September 30 2003 Shyouzou Sugitani * バージョン2.3.3リリース. Mon September 29 2003 Shyouzou Sugitani * lib/ninix/main.py, lib/ninix/sakura.py: ポップアップメニューのサーフェス倍率と表示ウェイトの項目をApplicationクラスで 管理するよう変更. * lib/ninix/main.py, lib/ninix/sakura.py, lib/ninix/dll/bln.py: 起動後に本体設定で画面下端からの距離を調整できるようにした. (従来通り-Rオプションも使用可能で, オプションを指定した場合はそれが優先される.) また画面上部に移動するゴースト向けに画面上端からの距離も指定できるようにした. easyballoon互換モジュールの位置計算もこの設定の影響を受ける. Mon September 29 2003 Shyouzou Sugitani * バージョン2.3.2リリース. Fri September 26 2003 Shyouzou Sugitani * lib/ninix/sakura.py: 前回の修正で問題があったためその部分は元に戻した. (Python2.3で出る警告は無視しても問題無し.) * lib/ninix/sakura.py, lib/ninix/dll/hanayu.py: gtk.Window.begin_move_drag()の 引数の型を修正. * lib/ninix/dll/satori.py: Python2.3の仕様変更(Boolean型の追加)で動作に問題が 発生していたのを修正. Thu September 25 2003 Shyouzou Sugitani * バージョン2.3.1リリース. Wed September 24 2003 Shyouzou Sugitani * lib/ninix/dll/kawari.py: Python2.3の仕様変更(Boolean型の追加)で動作に問題が 発生していたのを修正. * lib/ninix/sakura.py: ゴーストの再読み込みが機能しなくなっていたのを修正. * lib/ninix/sakura.py: Python2.3で警告が出ていたのを修正. Wed September 24 2003 Shyouzou Sugitani * バージョン2.3リリース. Tue September 23 2003 Shyouzou Sugitani * locale/ja.poを更新. * ポップアップメニューの内部構造の変更を終了. Mon August 18 2003 Shyouzou Sugitani * lib/ninix/dll/bln.py: 本体と同じプロセスで動作するように戻した. * lib/ninix/dll/bln.py: actionにvibrateメソッドのサポートを追加. * lib/ninix/dll/bln.py: スクリプト・アップデートおよびスクリプト・アペンドを サポート. * lib/ninix/dll/bln.py: \c, \b?, \_q, \l タグのサポートを追加. * lib/ninix/dll/bln.py: font.boldをサポート. Sun August 17 2003 Shyouzou Sugitani * lib/ninix/dll/bln.py: leftcenter, rightcenter, centertop, centerbottomの位置 指定に対応. * lib/ninix/dll/bln.py: OnEBMouseMoveの通知周期を500msに変更. * lib/ninix/dll/bln.py: OnEBMouseClickの通知タイミングをプレス時からリリース時へ 変更. * lib/ninix/dll/bln.py: action.referernce3のサポートを追加. Thu August 7 2003 Shyouzou Sugitani * lib/ninix/main.py, lib/ninix/sakura.py: ポップアップメニューの作成と管理を 移動. Tue July 29 2003 Shyouzou Sugitani * lib/ninix/main.py: マルチスレッド化のための初期化処理を削除. Mon July 28 2003 Shyouzou Sugitani * バージョン2.2リリース. Sat July 26 2003 Shyouzou Sugitani * lib/ninix/kawari.py: SAORIリクエストにCharsetエントリを追加. * lib/ninix/misaka.py: SAORIリクエストにCharsetエントリを追加. * lib/ninix/misaka.py: SHIORIリクエストの文字コード変換を修正. * lib/ninix/aya.py: SHIORIリクエストの文字コード変換を修正. Fri July 25 2003 Shyouzou Sugitani * バージョン2.1.5リリース. * lib/ninix/sakura.py: 最小化/復帰した際のイベントが2重に送られていたのを修正. Thu July 24 2003 Shyouzou Sugitani * lib/sstplib.py, lib/ninix/sstp.yp: アイコン化されている状態の時はSSTPサーバが エラー512(Invisible)を返すように修正. * lib/ninix/communicate.py: 古いghost.dbが残っていた場合のエラー処理を追加. * lib/ninix/sakura.py: cantalkフラグが0の時はスクリプトを破棄するように変更. Wed July 23 2003 Shyouzou Sugitani * lib/ninix/main.py: マルチスレッド化のための初期化処理を追加. Mon July 21 2003 Shyouzou Sugitani * SHIORIリクエストで文字コードを取得するよう変更. codeselect.pyは削除. * 放置状態だったPython栞のサポートを削除. * lib/ninix/sakura.py: oldtype指定の付いたSHIORIモジュールのサポートを削除. * lib/ninix/dll/kawari.py, lib/ninix/dll/niseshiori.py, libninix/dll/satori.py: Shioriクラスにrequestメソッドを追加し, finalizeメソッドをunloadに改名. oldtype指定を削除. * lib/ninix/dll/niseshiori.py: リクエストの引数の数値を文字列に変換してしまって いたのを修正. Sun July 20 2003 Shyouzou Sugitani * 本体のターミナル出力をUTF-8に変更. * SAORIモジュールがcodeselct.pyを使用しないよう変更. Sat July 19 2003 Shyouzou Sugitani * バージョン2.1.4リリース. Fri July 18 2003 Shyouzou Sugitani * lib/ninix-install.py, lib/ninix/config.py: インストールディレクトリの文字コードをUTF-8に変換するよう変更. * lib/ninix-install.py, lib/ninix/update.py, lib/ninix/config.py: ファイル名に関して文字コード変換を行なわないよう変更. Thu July 17 2003 Shyouzou Sugitani * locale/zh_TW.poを追加. (Chieh-Nan Wang) * lib/ninix/sakura.py: 旧形式の互換SHIORIへのリクエストで文字コード変換ができて いなかったのを修正. * lib/ninix/dll/niseshiori.py: 送られたSHIORIリクエストの文字コード変換を修正. * lib/ninix/dll/kawari.py: 送られたSHIORIリクエストの文字コード変換を修正. Wed July 16 2003 Shyouzou Sugitani * lib/ninix/dll/kawari.py: find()の実行後に文字コードを初期化するよう修正. Wed July 16 2003 Shyouzou Sugitani * バージョン2.1.3リリース. Tue July 15 2003 Shyouzou Sugitani * lib/ninix/dll/kawari.py: 文字コードの初期化忘れを修正. * lib/ninix/sakura.py: バルーンに使用するウインドウの種類を変更. * lib/ninix/sakura.py: コミュニケートウインドウの移動に関する処理を変更. * lib/ninix/sakura.py: SHIORIリクエストの文字コード変換を修正. * lib/ninix/dll/hanayu.py: ウインドウの移動に関する処理を変更. Mon July 14 2003 Shyouzou Sugitani * lib/ninix/sakura.py: サーフェス・バルーン・コミュニケートウインドウに対しての サイズ変更を拒否するよう設定. * lib/ninix/dll/kawari.py: 文字コードの設定を修正. Sat July 12 2003 Shyouzou Sugitani * バージョン2.1.2リリース. Fri July 11 2003 Shyouzou Sugitani * lib/ninix/dll/satori.py: モジュール間のやりとりで使用する文字コードを codeselectを通して指定するように変更.(EUC-JPを使用.) * lib/ninix/dll/kawari.py: 内部文字コードをUnicodeに変更. * lib/ninix/dll/kawari.py: モジュール間のやりとりで使用する文字コードを codeselectを通して指定するように変更.(辞書に合わせて変化.) * lib/ninix/dll/niseshiori.py: 内部文字コードをUnicodeに変更. * lib/ninix/dll/niseshiori.py: モジュール間のやりとりで使用する文字コードを codeselectを通して指定するように変更.(UTF-8を使用.) * lib/ninix/sakura.py: 旧形式互換SHIORIインタフェースの文字コード設定を修正. Thu July 10 2003 Shyouzou Sugitani * ウインドウが最小化された場合と復帰した場合のイベント(OnWindowStateMinimize, OnWindowStateRestore)を生成するようにした. Wed July 09 2003 Shyouzou Sugitani * lib/ninix-install.py: pnaファイルもインストールするよう修正. Wed July 09 2003 Shyouzou Sugitani * バージョン2.1.1リリース. Mon July 07 2003 Shyouzou Sugitani * 互換SAORI内部で使用する文字コードをEUC-JPからUnicodeに変更. * SSTPサーバ内部で使用する文字コードをEUC-JPからUnicodeに変更. * ninix用プラグインの定義ファイルplugin.txtでEUC-JP以外の文字コードを使用可能に した.(デフォルトはEUC-JPなので既存のプラグインの変更は不要.) Thu July 03 2003 Shyouzou Sugitani * 本体内部で使用する文字コードをEUC-JPからUnicodeに変更. * lib/ninix/dll/misaka.py: Lexerクラスで使用している正規表現を再度修正. * locale/ja.po: lib/ninix/main.pyのメッセージを追加. * lib/ninix/main.py: gettext化により埋め込まれた日本語メッセージを置き替え. Sun June 29 2003 Shyouzou Sugitani * lib/ninix/sakura.py: ゴースト起動時にOnDisplayChangeを送信するようにした. Fri June 27 2003 Shyouzou Sugitani * locale/ja.po: lib/ninix/sakura.pyのメッセージを追加. * lib/ninix/sakura.py: gettext化により埋め込まれた日本語メッセージを置き替え. Thu June 26 2003 Shyouzou Sugitani * lib/ninix/sakura.py, lib/ninix/main.py: gtk.Window.set_wmclass()を使用しない ようにした. Wed June 25 2003 Shyouzou Sugitani * バージョン2.1リリース. Mon June 23 2003 Shyouzou Sugitani * lib/ninix/home.py: fontrc関連部分を削除. * lib/config/fontrc: 削除. * lib/config/gtkrc: doc/examplesに移動. * lib/ninix-install.py: gtkrc, fontrcファイルのインストールを削除. Sun June 22 2003 Shyouzou Sugitani * lib/ninix/main.py: ポップアップメニューのゴースト選択で現在起動中のゴーストを 選択できないようにした. * lib/ninix/sakura.py: ゴーストのアイコンをサーフェスウインドウのアイコンとして 使うようにした. * Makefile: ファイルのインストール先ディレクトリ名を変更. Fri June 20 2003 Shyouzou Sugitani * lib/ninix/dll/misaka.py: Lexerクラスで使用している正規表現を再修正. * lib/ninix/main.py: ゴーストのアイコンをポップアップメニューで使うようにした. Thu June 19 2003 Shyouzou Sugitani * lib/ninix/dll/misaka.py: Lexerクラスを一部変更.(「フサギコ漫談」対応のため.) * gettext化を開始. Mon June 16 2003 Shyouzou Sugitani * lib/ninix/sakura.py: 暗号化PNGをサーフェスに使用できるようにした. * lib/ninix/pix.py: 暗号化PNGの解読機能を追加. Fri June 13 2003 Shyouzou Sugitani * lib/ninix/home.py: サーフェス画像として暗号化PNGを使えるようにした. * lib/ninix-install.py: サーフェス画像として暗号化PNGをインストールできるようにした. * lib/ninix/main.py: ポップアップメニューでシェルの名前の文字コードが変換されて いなかったのを修正. * lib/ninix/dll/aya.py: 辞書読み込みのバグ修正. Sun June 1 2003 Shyouzou Sugitani * lib/ninix/main.py: 着せ替えメニューをポップアップメニューから分離して画面上に 置いておけるようにした. Fri May 30 2003 Shyouzou Sugitani * バージョン2.0リリース. Thu May 29 2003 Shyouzou Sugitani * lib/ninix/sakura.py, lib/ninix/main.py, lib/ninix/pix.py: 着せ替え機能SERIKO/1.3,1.7,1.8(MAYUNA/1.0,1.1,1.2)対応. * lib/ninix/mayuna.py 追加. Tue May 27 2003 Shyouzou Sugitani * READMEを更新. * lib/ninix/main.py: バージョン情報を修正. * lib/ninix/sakura.py: \![*]タグによるSSTPマーカーの表示で位置がずれていたのを 修正. * lib/ninix/dll/kawari8.py: SAORI互換モジュールのロード状態を管理するよう修正. Thu May 22 2003 Shyouzou Sugitani * バージョン1.9.11リリース. Wed May 21 2003 Shyouzou Sugitani * lib/ninix/sakura.py: overlayのオフセットが負の値の時に画像合成でエラーが出て いたのを修正. Tue May 20 2003 Shyouzou Sugitani * lib/ninix-update.py, lib/ninix/update.py: 本体のネットワーク更新機能の変更で ninix-updateコマンドが動作しなくなっていたのを修正. Mon May 19 2003 Shyouzou Sugitani * lib/ninix/dll/bln.py: 文字の表示がちらつかないよう表示速度を調整. * lib/ninix/dll/bln.py: bln.txtの読み込みでエラーが発生すると, bln.pyのunloadが 正しく行なわれなくなるのを修正. Sun May 18 2003 Shyouzou Sugitani * lib/ninix/sakura.py: Unicodeへの文字コード変換を行なっている箇所でエラーが 出た場合の処理をそれぞれ設定. Sun May 18 2003 Shyouzou Sugitani * バージョン1.9.10リリース. * lib/ninix/sakura.py: 文字コード変換が1箇所抜けていたのを修正. * lib/ninix/dll/aya.py: 関数の検索方法の変更でシステム関数FUNCTIONEX, SAORIが 動かなくなっていたのを修正. Fri May 16 2003 Shyouzou Sugitani * バージョン1.9.9リリース. * lib/ninix/main.py, lib/ninix/sakura.py: 新しいバルーンフォント設定が機能する よう修正. Thu May 15 2003 Shyouzou Sugitani * lib/ninix/sakura.py: このファイル内にあるSakura, Ghost以外のクラスはUnicodeで 文字列をやりとりするように変更. * lib/ninix/home.py: バルーンフォントの設定ファイルとしてpango_fontrcを追加. * lib/ninix/sakura.py: バルーンの文字の表示にをPangoを使用するよう変更. * lib/ninix/dll/bln.py: 文字を複数回重ね書きしてしまっていたのを修正. * lib/ninix/dll/bln.py: 画像の描画方法を変更. Sun May 11 2003 Shyouzou Sugitani * バージョン1.9.8リリース. * lib/ninix/main.py, lib/ninix/dll/bln.py, lib/ninix/dll/hanayu.py: pygtk-1.99.14以前のpango.Layout.set_text()の仕様に対応. Sat May 10 2003 Shyouzou Sugitani * lib/ninix/profile.py: 削除. * lib/ninix/home.py, lib/ninix/sakura.py, lib/ninix/main.py: ゴーストの消滅回数と起動時間を各ディレクトリに置かれたHISTORYファイルに記録する ように変更. Fri May 09 2003 Shyouzou Sugitani * modules/_image.cおよび関連コードを削除. * lib/ninix/sakura.py, lib/ninix/dll/bln.py, lib/ninix/dll/hanayu.py: 画像ファイルの読み込み全てで_image.soではなくpix.pyを使用するように変更. * ib/ninix/pix.py: 新規追加. 画像ファイルを読み込んでgtk.gdk.Pixbuf, gtk.gdk.Pixmapを作成する関数を実装. Thu May 08 2003 Shyouzou Sugitani * バージョン1.9.7リリース. * lib/ninix-install.py, lib/ninix/sakura.py, lib/ninix/home.py: 残っていたxpm形式の画像ファイルサポートのコードを完全に削除. * lib/ninix/sakura.py: _image.soモジュールを使わずにサーフェス画像ファイルを 読み込むように変更.(この変更でpygtk-1.99.14でも動作するようになった.) Wed May 07 2003 Shyouzou Sugitani * バージョン1.9.6リリース. * README, doc/saori.txt, doc/kawari.txt を更新. * lib/ninix/sakura.py: 1.99.16より古いpygtkだとGdkWindow.set_back_pixmap()の バグで動作しない問題を修正. * lib/ninix-install.py: install.txtのnameエントリが一致しない場合に上書きしても 良いかどうか確認するよう修正. * lib/ninix/main.py: ゴーストを消滅させる際にディレクトリとHISTORYファイルを 残すよう変更. Mon May 05 2003 Shyouzou Sugitani * バージョン1.9.5リリース. Sun May 04 2003 Shyouzou Sugitani * lib/ninix/dll/aya.py: 字句解析/構文解析関連メソッドをリクエスト処理の負荷を できるだけ小さくする方向で再度大幅に変更. Sat May 03 2003 Shyouzou Sugitani * lib/ninix/sakura.py: バルーンの文字表示位置を調整. Thu May 01 2003 Shyouzou Sugitani * バージョン1.9.4リリース. * lib/ninix/sakura.py: \![open,inputbox,,,<初期値>] で入力の初期値を指定できる ように修正. Mon Apr 28 2003 Shyouzou Sugitani * lib/ninix/dll/aya.py: システム関数の引数の型チェックを厳しくした. * lib/ninix-install.py: CROW同梱ゴーストのインストール機能を削除. Sun Apr 27 2003 Shyouzou Sugitani * lib/ninix/dll/aya.py: DLLの名前が変更されている場合にaya_variable.cfgの名前も それに合わせるようにした. * lib/ninix/dll/aya.py: 字句解析/構文解析を強化. * lib/ninix/sakura.py: CommunicateWindow(およびサブクラス)のkey_pressメソッドで 2重にイベントが発生していたのを修正. Fri Apr 25 2003 Shyouzou Sugitani * バージョン1.9.3リリース. * lib/ninix/sakura.py: バルーンの再配置が正しく行われない場合があったのを修正. * lib/ninix-install.py: install.txtのrefresh, refreshundeletemaskエントリ対応. * lib/ninix/dll/aya.py: SakuraScriptのメタ文字列先頭の%を誤って取ってしまう バグを修正. * lib/ninix/dll/aya.py: AyaFunctionクラスのevaluate_*メソッドを高速化. * lib/ninix/dll/aya.py: AyaFunctionクラスのparseメソッドを改良. Thu Apr 24 2003 Shyouzou Sugitani * バージョン1.9.2リリース. Wed Apr 23 2003 Shyouzou Sugitani * lib/ninix/main.py: メニューが画面に入り切らない場合の処理はGTK+に任せる ことにしてメニューのリサイズ処理を削除. Tue Apr 22 2003 Shyouzou Sugitani * lib/ninix/dll/aya.py: 重複している処理を省くなどして高速化. Mon Apr 21 2003 Shyouzou Sugitani * lib/ninix/sakura.py: マウス移動検出の処理を変更. ボタンが押されていない 状態でサーフェス上をマウスカーソルが移動するとOnMouseMoveイベントが発生する ようにした. イベント処理のタイマ割込みとGTK+のイベント生成を連動させることで無駄なイベント の発生を抑えている. * lib/ninix/sakura.py: サーフェスの当り判定領域でマウスカーソルが変わるように 変更. Mon Apr 21 2003 Shyouzou Sugitani * バージョン1.9.1リリース. * lib/ninix/dll/aya.py: 栞判定を強化. DLLの名前が変更されている場合に対応. * lib/ninix/home.py: 栞判定メソッドにDLLの名前を渡すようにした. Sun Apr 20 2003 Shyouzou Sugitani * lib/ninix/main.py: バルーンフォント設定以外の本体設定が機能するよう修正. * lib/ninix/sakura.py: バルーンの描画方法を一部変更. * lib/ninix/sakura.py: サーフェスオーバーレイの座標が負の場合にエラーが出る のを修正. Fri Apr 18 2003 Shyouzou Sugitani * lib/ninix/sakura.py: バルーンの位置計算のバグを修正. Thu Apr 17 2003 Shyouzou Sugitani * バージョン1.9リリース. * lib/ninix/sakura.py: サーフェス・バルーンの位置計算を調整. * lib/ninix/sakura.py: 見切れ・重なり判定を調整. * lib/ninix/sakura.py: サーフェス配置パラメータを SurfaceWindow クラスに移した. Wed Apr 16 2003 Shyouzou Sugitani * lib/ninix/sakura.py: \_b[], \_v[]タグの処理でファイル名を小文字に 変換するのを忘れていたのを修正. * ドキュメントの配置を変更. Sun Apr 13 2003 Shyouzou Sugitani * lib/ninix/sakura.py: バルーンの位置計算を修正. * lib/ninix/sakura.py, lib/ninix/main.py: サーフェス縮小機能を追加. * lib/ninix/sakura.py: バルーンのスクロールボタンをマウスホイールで 操作可能に. * lib/ninix/main.py, lib/ninix/sakura.py: -pオプションを廃止. * lib/utf8.py: 削除. * lib/ninix/dll/niseshiori.py: utf-8の処理にunicode()を使用するよう 変更. * src/: 削除. * lib/ninix-install.py: pngからxpmへの変換(-xpmオプション)を削除. * gtkhack/: 削除. * GTK+2.0ベースに変更. Sat Apr 12 2003 Shyouzou Sugitani * バージョン1.0リリース. * lib/ninix/dll/hanayu.py: 線の太さのデフォルト値を修正. * lib/ninix/sakura.py: \_b[] タグの処理を修正. 2003/04/08 period 30 - ネットワーク更新を途中でキャンセル可能にした.(update.py, sakura.py) - ネットワーク更新でエラーが発生した場合や途中キャンセルされた場合にはファイル を更新前の状態に戻すようにした.(update.py) - AyaFunction.evaluate_statement() にエラー処理を追加.(aya.py) - 送信すべきイベントが無い場合にもイベントを本体に送信しようとしてエラーになっ ていたのを修正.(bln.py) - 文字の表示位置設定のバグを修正.(hanayu.py) - 描画のちらつきを抑えるためのモジュール _gtkhack を追加. - Shiori.reload() を追加.(satori.py) - 本体による AI トークの頻度調整を削除.(main.py, sakura.py) 2003/03/03 period 29 - 再生するファイルのパス指定を修正.(mciaudio.py) - mciaudior.dll 互換モジュール追加. - おるすばんバルーンを出す前に通常バルーンを消去するようにした.(bln.py) - おるすばんバルーンが本体にイベント送信しないようにした.(bln.py) 2003/03/01 period 28 - ゴーストの交代時に自由配置等の設定をリセットするようにした.(sakura.py) - SERIKO の interval エントリ yen-e と talk,n に対応.(sakura.py, seriko.py) - base メソッドのアニメーションが最後まで動作するようにした. (sakura.py, seriko.py) - res_reference* をリクエスト毎に削除するようにした.(aya.py) - AyaFunction.get_block() を修正.(aya.py) - ループ回数制限撤廃.(aya.py) - case 〜 when の候補値として文字列の範囲指定を使用可能にした.(aya.py) - セキュリティーログは既存のファイルがあればそれに追加するようにした.(aya.py) - セキュリティーログの書き込みの際にファイルロックするようにした.(aya.py) - システム関数 TOBINSTR, TOHEXSTR, BINSTRTONUM, HEXSTRTONUM を追加.(aya.py) - 数値の2進/16進表記に対応.(aya.py) - return ステートメントに対応.(aya.py) - セキュリティ設定が正しく行なわれない場合があったのを修正.(aya.py) - while ループ内で break, continue を使用可能にした.(aya.py) - マルチステートメントの処理方法を変更.(aya.py) - for ループに対応.(aya.py) - SERIKO の "option,exclusive" に対応.(seriko.py, sakura.py) - 本体との間のタイミングを調整.(bln.py) - スクリプトの処理が終わるまではネットワーク更新後の再読み込みを実行しないよう に変更.(sakura.py) 2003/02/10 period 27 - KIS コマンド saoriregist, saorierase, callsaori, callsaorix のサポートを追加. (kawari.py) - kawari.ini での SAORI 登録に対応.(kawari.py) - \![set,alignmenttodesktop,free] に対応.(sakura.py) - 見切れ・重なり判定でY軸方向も考慮するようにした.(sakura.py) - \![set,alignmenttodesktop,free], \![set,alignmentondesktop,*] がシンクロ ナイズドセッション中に実行された場合, 両方のサーフェスに作用するようにした. (sakura.py) - '\_v[filename]' のサポートを追加.(sakura.py) - 互換 SAORI 呼び出しのバグを修正.(kawari.py) - ゴーストが8体以上居る状態でゴーストリストの作成でエラーが発生するのを修正. (sakura.py) - 旧 API 互換栞のためのゴースト間コミュニケーションのサポートを追加.(sakura.py) - ゴースト間コミュニケーション機能を一部実装.(kawari.py) - マッチエントリ検索以外のゴースト間コミュニケーション機能を実装.(kawari.py) - Saori.timeout_id の初期化処理を追加.(bln.py) - マッチエントリ検索の処理を追加.(kawari.py) - ゴースト間コミュニケーション機能を実装.(niseshiori.py) - case 〜 when 〜 others ステートメントの処理を追加.(aya.py) - 領域コメントに対応.(aya.py) - システム関数 GETLASTERROR を追加.(aya.py) - システム関数 ISINSIDE, IASC を追加.(aya.py) - case 〜 when の候補値として範囲指定を使用可能にした.(aya.py) - ファイル操作系システム関数で絶対パス指定を可能にした.(aya.py) - check_path 関数によるファイル操作のチェックを修正.(aya.py) - セキュリティ機能を実装.(aya.py) - システム関数 STRSTR を修正.(aya.py) - "OnTranslate" イベントを発生させるようにした.(sakura.py) - inputbox を再調整.(sakura.py) - 複雑な設定がされている場合のセキュリティチェックを高速化.(aya.py) - AyaFunction.parse() の処理結果に __TYPE_LITERAL を追加.(aya.py) - ローカル以外からの "\!" で始まるタグの実行を拒否するようにした.(sstp.py) - KIS コマンド split を修正.(kawari.py) 2003/01/19 period 26 - ファイル操作システム関数でファイル名を小文字に変換するように修正.(aya.py) - システム関数 ROUND を修正.(aya.py) - 文字列の連結に string モジュールの join メソッドを使うようにした.(aya.py) - aya_variable.cfg の読み込みに成功したかどうかを AyaGlobalNamespace クラスの load_database メソッドの戻り値として返すようにした.(aya.py) - 高速化のために辞書の読み込み時点でできるだけ解析を済ませるようにした.(aya.py) - 変数・関数を探す際の名前空間のサーチ順序を変更.(aya.py) - 効率の悪いループやメソッド呼び出しを修正.(aya.py) - AyaFunction クラスの evaluate_string メソッドのヒストリ処理を修正.(aya.py) - AyaVariable クラスの put メソッドを修正.(aya.py) - play コマンドで再生/一時停止をトグルできるようにした.(mciaudio.py) - 出力確定子の処理を修正.(aya.py) - 画像にアルファチャンネルが設定されている場合の処理を追加.(_image.c) - KIS コマンド array のサポートを追加.(kawari.py) - モジュール名の取り出し部分のバグ修正.(dll.py) - nooverlap の処理の問題を修正.(bln.py) - ファイルチェックを強化.(hanayu.py) - 戻り値が無い場合のヘッダを修正.(bln.py, hanayu.py, mciaudio.py, textcopy.py) - KIS コマンド split のサポートを追加.(kawari.py) 2002/12/30 period 25 - フォント関連パラメータの使い方を一部修正.(sakura.py, bln.py, hanayu.py) - Python 1.5 の環境でエラーが出ないように fcntl.lockf() の第一引数を修正. (communicate.py)(Thanks to あべさん) - 本体でヘルパーに設定されているコマンドをファイルの再生に使うように変更. (lettuce.py, mciaudio.py) - みんと(mint.dll)と名前が混ざっていたのを修正.(lettuce.py) - string.join() の引数が一箇所間違っていたのを修正.(aya.py) 2002/12/24 period 24 - line_strip() の使用を控えるようにした.(aya.py) - 不要な 'otherghostname' イベントを発生させないようにした.(sakura.py) - 'otherghostname' イベントを 'NOTIFY' で送るように修正.(sakura.py) 2002/12/16 period 23 - ninix-install に CROW 同梱ゴーストのインストール機能を追加. それに合わせてゴースト固有バルーンの検索の際にゴーストの descript.txt の内容も チェックするように変更(main.py) - ゴースト間コミュニケーションのための communicate.py を追加. - COMMUNICATE/1.1 のサポートを追加.(sstp.py) - 起動しているゴーストのデータベース更新機能を実装.(sakura.py) - 定期的に 'otherghostname' イベントを発生させるようにした.(sakura.py) - ゴースト間コミュニケーション用のメッセージ送信機能を実装.(sakura.py) - 'otherghostname' イベント用に Reference の処理を拡張.(sakura.py) - misaka.py をゴースト間コミュニケーションに対応させた. - リンク対象となるテキストが空の場合の処理を追加.(sakura.py) - inputbox を「仕様書通り」に使っているゴーストに対応.(sakura.py) - CROW 同梱ゴースト対応の際に入った, 固有バルーンを持たないゴーストへの切り換え の場合にゴースト名の入った変数を上書きしてしまうバグを修正.(main.py) 2002/12/07 period 22 - 不要になったコードを削除.(ninix-install.py) - show_description() の表示内容に Copyright を追加.(aya.py, kawari8.py) - 旧 API の互換栞を ninix/dll に移動. API はそのままで Shiori クラスを追加. 呼び出しは新 API 互換栞と同様に dll.py 経由で行なう. (niseshiori.py, kawari.py, satori.py, ninix-update.py, home.py, sakura.py) - 栞の終了処理を追加.(ninix-update.py) - ロードされていない状態で unload() が呼ばれても問題ないよう修正.(hanayu.py) - 選択肢がバルーンの表示領域内にあるかどうかの判定条件を修正.(sakura.py) - 文字列を囲むダブルクォートが片方抜けている場合の処理を追加.(aya.py) - DirectSSTP 機能として UNIX ドメインソケット版の SSTP サーバを追加. (sstplib.py, sstp.py, main.py, sakura.py) - bln.py をマルチプロセス化. - バルーンクリックイベントが2重に発生していたのを修正.(bln.py) - バルーンクリックイベントが発生しない場合があったのを修正.(bln.py) - マウス移動イベントが全く発生していなかったのを修正.(bln.py) - X 座標方向のバルーンの位置計算を修正.(bln.py) - DirectSSTP のレスポンスについては標準エラー出力にメッセージを出さないように した.(sstp.py) - ネットワーク更新のファイル数を0オリジンに変更.(update.py) - DirectSSTP 用のソケットディレクトリ名を socket に変更.(main.py) - SSTP の Sender フィールドに ninix が使われていた箇所を ninix-aya に変更. (ninix-install.py, ninix-update.py, sakura.py) - バージョン情報を ninix-aya のものに変更.(main.py) - pygtk で GTK+ のバージョンを指定するようにした.(main.py, bln.py) (Thanks to にっしーさん) - 以前の変更で Python SHIORI の判定が抜け落ちてしまっていたのを修正.(home.py) - サーフェスが充分離れている場合は '\4' が来ても移動しないよう修正.(sakura.py) 2002/11/16 period 21 - バルーンの設定情報の優先順位を調整.(sakura.py) - ロードされていない SAORI へのリクエストに対する応答を修正.(kawari8.py) - InputBox が ESC キーでキャンセルされた場合にもイベントを発生させるように変更. (sakura.py) - CommunicateBox のモーダル設定を解除.(sakura.py) - \![set,alignmentondesktop,top], \![set,alignmentondesktop,bottom] に対応. (sakura.py) - 時々サーフェスが出てこないバグを修正.(sakura.py) - スクリプトの表示の際にバルーンも前面に出すようにした.(sakura.py) - OnKeyPress を新仕様と旧仕様の混成仕様に変更. ただし, キーマップは不完全. (keymap.py, sakura.py) - 一行に複数の選択肢とテキストを混在させられるように変更.(sakura.py) - サーフェスとバルーンが重なった場合にちらつくのを抑えるために前面に出す動作を 調整. 変化があった時だけ前面に出てくるようにした.(sakura.py) - 選択肢が複数の行にまたがっても良いように変更.(sakura.py) - '\x' の位置に改行を入れるように変更.(sakura.py) - \![open,configurationdialog] に対応.(sakura.py) - サーフェスのドラッグの際にはサーフェスを前面に出すようにした. - バルーンの方向を決定する方法を変更. - '\4', '\5' に対応. ただし, alignmentondesktop は考慮していないので Y 座標の 方向の移動は無し.(sakura.py) - '\![*]' に対応.(sakura.py) - バルーン切り換えの後は強制的にサーフェスを出すようにした.(main.py) - バルーンの位置を調整.(sakura.py) - '\4', '\5' がウインドウを10ピクセルずつ移動させるように変更.(sakura.py) - 栞判定を改良.(misaka.py) - バルーン内の表示領域に関する情報が更新されている間は選択肢がマウスの移動に 反応しないように修正.(sakura.py) - '\_a[symbol]' に対応.(sakura.py) - '\4' の移動距離を調整.(sakura.py) - '\5' の移動先を調整.(sakura.py) - 全てのスクリプトがトランスレータを通るように修正.(sakura.py) - 選択肢の範囲チェックを修正. 選択肢の先頭が表示領域内でも途中から外に出ている場合がある.(sakura.py) 2002/10/27 period 20 - 花柚(hanayu.dll)互換 SAORI モジュール hanayu.py を追加. - れたす(lettuce.dll)互換 SAORI モジュール lettuce.py を追加. - タイムクリティカルセクション中もイベントを処理するよう変更.(sakura.py) - 多バイト文字列操作関数, 外部汎用 DLL 呼び出し関数, ファイル操作関数のテストを 行ない, 見付かったバグを修正.(aya.py) - 辞書の暗号化機能を追加.(aya.py) - \![(un)lock,reapint] に対応.(sakura.py) - hanayu.txt の読み込みのバグを修正.(hanayu.py) - スクリプトの表示の際にサーフェスを前面に出すようにした.(sakura.py) - \![vanishbymyself] に対応.(sakura.py) - \![enter,passivemode], \![leave,passivemode] に対応.(sakura.py) - '\x' からの復帰の際に下向き矢印を消去するようにした.(sakura.py) - \_b[filename,x,y] に対応.(sakura.py) - メニューのネットワーク更新と消滅指示のボタンはメニュー表示の度に更新するよう にした. 消滅指示の表示/非表示の切り換えを反映させるため.(sakura.py) - \n[half] に対応.(sakura.py) - '_in_', '!_in_' の処理を修正.(aya.py) - passive mode でバルーンの消去が機能しないように修正.(sakura.py) - draw_last_line() の \n[half] の処理を修正.(sakura.py) - passive mode と \![lock,repaint] の動作を調整.(sakura.py) 2002/10/04 period 19 - misaka.py を dll/ に移動. API を変更し互換 SAORI にも対応. - 梶山 API の互換栞の栞判定で互換栞が見つかっても100しか返さないようにした. - eval_globals() で sentences の中に関数が出てきた場合にその関数を実行するよう にした.(misaka.py) - 互換栞が無かった場合にシェルとして使用できるようにするコードを復活.(home.py) - 単体のバルーンの情報(1次情報)とゴースト同梱のバルーンまで含めたバルーン全て の情報(2次情報)を分離.(home.py, main.py) - 消滅指示後のゴースト切り換え時に全ファイルを再読み込みしていたのを必要最小限 (次に起動するゴースト)の読み込みしか行なわないように変更.(main.py) - 消滅指示後に切り換わるゴーストがランダムに選択されなくなっていたのを修正. (main.py) - search_ghosts() を特定のゴーストのディレクトリを指定して呼び出せるようにした. (home.py) - ゴースト起動・変更時のイベントに反応が無い場合の動作を変更.(sakura.py) - タイマ割込みの制御を Sakura から Ghost に移動.(sakura.py) - ネットワーク更新の処理を Sakura から Ghost に移動.(sakura.py) - Sakura からの Application のメソッドの呼び出しは Ghost に任せるようにした. (sakura.py, main.py) - 現在のゴーストの情報を再読み込みするためのメソッドを追加.(main.py) - ネットワーク更新が完了したらゴーストの情報を再読み込みするようにした. (sakura.py) 2002/09/22 period 18 - DLL 互換モジュールを管理するクラスは main.py でインスタンスを生成するように 変更.(main.py, sakura.py, dll.py, aya.py) - 互換 SHIORI で互換 SAORI を使用する場合の処理の一部を dll.py に移して互換 SHIORI 側の処理の負担を軽減.(main.py, dll.py, aya.py) - 互換 SAORI から Sakura のインスタンスへのアクセスを可能にした.(dll.py) - easyballoon(bln.dll) 互換 SAORI モジュール bln.py を追加. - unload() の戻り値を修正.(mciaudio.py, bln.py) - 互換 SAORI の状態の管理は SHIORI 毎に微妙に差があるため互換 SHIORI の責任で 行なうようにした.(dll.py, main.py, aya.py, ninix-update.py) - kawari8.py を互換 SAORI に対応させた. - ウインドウを構成するウィジットを見直し.(bln.py) - ウインドウの初期座標が負の値の場合にも正しく表示されるよう修正.(bln.py) - タイムアウトの処理を修正.(bln.py) - スクリプトの表示が終わるまでは指定された寿命が来てもウィンドウを破棄しない ようにした.(bln.py) - ウィンドウの移動距離の計算を修正.(bln.py) - 変数名の誤りを修正.(bln.py) - textcopy.dll 互換 SAORI モジュール textcopy.py を追加. - gtk を import する DLL 互換モジュールは環境変数 DISPLAY をチェックするよう に修正.(bln.py, textcopy.py) - ninix-update.py と sakura.py で梶山 API の互換栞について栞判定を再度行なっ ていたのを修正. これで栞判定を行なう場所は home.py 内に限定された. - import したモジュールに目的のクラスが無い場合にはそのモジュールを削除する ようにした.(dll.py) - DLL 互換モジュールのサーチパスは __init__ の際に指定されたものに限定. (dll.py, sakura.py, aya.py, kawari8.py, ninix-update.py) - DLL 互換モジュールのサーチパスの指定を変更.(main.py) - 新しい栞判定を導入開始.(home.py) - 栞判定の変更により不要になった _kawari8.so の import 時のメッセージを削除. _kawari8.so が無ければ栞判定のスコアが 0 になる.(kawari8.py) - 新互換栞のロード後にモジュールの名前等を表示できるようにした.(sakura.py) Shiori クラスの show_description を呼び出すが, このメソッドは必須ではない. - Shiori クラスに show_description メソッドを実装.(aya.py, kawari8.py) - kawari8.py の栞判定の結果の1桁目を変更.(kawari8.py, sakura.py) - サーフェス・バルーンの位置の計算を修正.(sakura.py) - *Actor で無限ループに陥るのを修正.(seriko.py) - OnSurfaceRestore イベントは SHIORI にイベントを送るだけで, 本体側でサー フェスを戻さないよう変更.(sakura.py) - Sakura スクリプトで最初のサーフェス指定が来るまではサーフェスを表示しな いように変更. もし, メッセージ表示が先に来た場合はその時点でデフォルトが 出る.(sakura.py) - 毎回ロード時に _kawari8.so をリロードするようにした.(kawari8.py) - ドラッグ中にサーフェスをデフォルトに戻さないようにした.(sakura.py) - 見切れ判定を調整.(sakura.py) - 再読み込みの後で OnGhostChanged を発生させるようにした.(sakura.py) - 再読み込み後に発生させるイベントを OnBoot に変更.(sakura.py) - OnGhostChanged に反応が無い場合には OnBoot を呼ぶようにした.(sakura.py) - '\x' による一時停止時にバルーンに下向き矢印を出させるようにした.(sakura.py) 2002/09/02 period 17 - DLL 互換モジュールのデフォルトサーチパスの指定を必須にした.(dll.py) - DLL 互換モジュールを要求する際にサーチパスを追加出来るように変更.(dll.py) - DLL 互換モジュールインタフェースを使用した SHIORI 互換モジュールのサポートを 追加.(sakura.py) - aya.py を lib/ninix/dll に移動. DLL 互換モジュールインタフェースに対応. - home.py における「文」ゴースト判定の方法を aya.txt の有無で判定するよう変更. ただし, 一時的な措置. - ninix-install, ninix-update から旧 aya.py 関連のコードを削除. - ninix-update を DLL 互換モジュールインタフェースに対応させた. - ninix-install でゴーストの全ファイルをインストールするように変更. - 新互換栞とのインタフェースを SHIORI/3.0 に変更.(sakura.py) - SHIORI API wrapper を削除し, SHIORI/3.0 のみのサポートに変更.(aya.py) - ninix-update を新互換栞でも利用できるよう SHIORI/3.0 に対応させた. - test() を修正.(aya.py) - SHIORI 判定を刷新. ただし, 各 SHIORI の判定ルーチンは従来のまま. (home.py, ninix-update.py, main.py, sakura.py, dll.py, aya.py) - shiori_name には DLL 名ではなく互換栞の名前を入れるように変更.(dll.py) - 華和梨8の判定を追加.(home.py) - 新形式互換栞 kawari8.py を dll/ に追加. - DLL 互換モジュールのリクエストがディレクトリ名を含んでいる場合に対処.(dll.py) - SHIORI 判定関数のリストを作成.(home.py) - SAORI リクエストは Shift_JIS で送るように修正.(aya.py) - 代入の際に不要な整数から実数への変換をしないようにした.(aya.py) - load() に戻り値を設定するのを忘れていたので修正.(aya.py, kawari8.py) - 新互換栞の load() が失敗の場合には旧互換栞を探すように変更. (sakura.py, ninix-update.py) - システム関数 MSTRLEN, MSTRSTR, MSUBSTR, MERASE, MINSERT を実装.(aya.py) 2002/08/16 period 16 - saori.py から SAORI DLL 互換機能の実装を分離. - saori.py を dll.py に変更. SAORI だけでなく SHIORI も扱うようにした. - lib/ninix/dll/mciaudio.py に mciaudio.dll 互換機能を移した. - コミュニケートウインドウは ESC が押された場合のみ消えるよう変更.(sakura.py) - システム変数 systemuptickcount を実装.(aya.py) - システム関数 FWRITE2 を実装.(aya.py) - ファイル名を小文字に変換するよう修正.(mciaudio.py) - 不要な import を削除.(mciaudio.py) - get_actors()のsurface番号ゼロパディングバグ修正.(seriko.py) Thanks: あべさん 2002/08/07 period 15 - 右辺の型によらず代入が実行されるように修正. - 変数比較の際の型チェックが実数と整数の比較にまで適用されていたのを修正. 2002/08/06 period 14 機能追加: - SAORI互換機能 (saory.py) 追加. - ユーザーからゴーストへのコミュニケート対応(aya のみ). - 「和音」のメニューからの MIDI 演奏に対応. デバッグ: - 簡易配列を拡張する処理の条件判定が逆になっていたのを修正. - 変数への代入の際に既に変数が存在するかどうかの判定を忘れていたのを追加. - 変数を操作する場合, 事前に AyaVAriable.reset メソッドが実行されるようにした. これにより文字列の演算による簡易配列としての構造の変化に対応. 2002/07/30 period 13 - クラス Aya に SHIORI API の request() を実装. - クラス Aya を Aya と AyaWrapper に分割. AyaWrapper で ninix 本体からの SHIORI/1.x, 2.x のリクエストを SHIORI/3.0 形式にして Aya に送るようにした. - Aya の応答から ninix 本体の要求する値を取り出すためのメソッド get_value を AyaWrapper に実装. - Aya のベースを Ver.4 仕様に変更. - リクエスト値の取得のためのシステム関数(REQ.*)を全て実装. - Ver.4 の OnRequest を使用するようになったので, 不要になったトークチェインを 動作させるためのコードは削除. - Ver.3 互換のためのコードを追加.("# Ver.3" のコメントの個所.) ただし, 応答のヘッダ生成は省略. (AyaWrapper の get_value で区別している.) - システム関数 LETTONAME に引数のチェックを追加. - Aya.request() を修正. Ver.4 における応答の内容は栞機能辞書(aya_shiori3.dic) に完全に任せることにして, Aya.request() のリクエストヘッダー解析ではリターン しないようにした. - システム関数 INSERT で挿入バイト位置が負数の場合には先頭に挿入するように修正. - AyaFunction.evaluate_string() が文字列を評価していく際に評価する文字の位置を 正しく扱えていなかったのを修正. - 文字列結合出力を実装. - AyaFunction.evaluate() の辞書の評価方法を変更. 基本的に AyaFunction.evaluate_statement() を使用して評価するようにした. この変更で四則演算, 文字列結合出力を完全にサポート. - AyaFunction.evaluate_statement() 内で型変換が正しく行なわれない場合が あったのを修正. - 比較演算で両辺の値の型をチェックするようにした. - obsolete なシステム変数 ghostexcount を削除. - コメントの追加など微調整. - AyaNamespace の変更で set_separator メソッドの追加を忘れていたのを修正. - random.randrange() の第2引数を修正. # Deprecated になった random.randint() とは範囲が違っているのを見落としてた. - decrypt() の入力を1文字だけに変更し decrypt_char() にした. さらにこれと対を成す encrypt_char() を作成. - os.path.join() の2番目以降の引数に渡されるパス名が相対パスであることが保証 されるように修正. - システム関数 FOPEN で作成されるファイル辞書のキーをノーマライズされた絶対 パスに変更. 2002/07/19 period 12 - 関数の内部ブロックの変数が外側のブロックの名前空間にまで伝わってしまって いたのを修正. 2002/07/18 period 11 - 「文」Ver.4 対応開始. - ダブルクォーテーションのエスケープ処理を削除. (Ver.4) - マルチステートメントで出力確定子の後にも ';' が必要になった. (Ver.4) - システム関数 CUTSPACE を実装. - 「文」Ver.3 で削除された古いシステム変数についてサポートを終了. ただし ghostexcount はコミュニケートが実装されるまで残す. - システム関数 ISFUNCTION を実装. - ファイルを書き込み可能状態でオープンする際にはパスに親ディレクトリを指す '..' が含まれていないことを確認するようにした. 読み取りのみの場合は3つまで許可.(~/.ninix の中に収まる範囲.) ファイル名が固定の場合にはチェックしていない. - システム変数 systemup* を実装. ただし, 中身はシステムではなくゴーストを起動してからの経過時間. - 未実装関数の戻り値も仕様書に記載されている型に合わせた. - 起動時に OnLoad を呼ぶようにした. (Ver.4) - 単項演算子 '+', '-' が正しく評価されない場合があったのを修正. - システム関数 FDELETE, FRENAME, FSIZE, MKDIR, RMDIR, FENUM を実装. これらの関数もパスのチェックをするようになっている. - 関係演算子 !_in_ を実装. - システム関数 FCOPY, FMOVE を実装. - 終了時に OnUnload を呼ぶようにした. (Ver.4) - AyaFunction の evaluate メソッドが常に結果を文字列に変換して返す動作を変更. 連結が必要な場合にのみ文字列に変換するようにした. - システム関数リストの LOGGING の引数の数が間違っていたのを修正. - システム関数 LOGGING の出力形式を Ver.4 仕様に変更. 2002/07/07 period 10 - AYA 内部イベント On_ID の処理を実装. - SHIORI/1.0 API の処理も AYA 内部イベント On_ID に変換するようにした. 2002/06/31 period 9 - システム変数のリストに systemuptime を追加. - aya.txt の処理に logmode を(項目のみ)追加. - テスト用のメソッドを実装. - 文字列型のメソッド find() は python1.5 に無いので string module の find() を使用するようにした. 2002/06/30 period 8 - 多項式演算を実装. 代入演算子に含まれる演算を通常の演算とは別に処理していたのを一本化. この変更で '/=' で 0除算の場合のエラー処理を忘れていた問題は無くなった. - システム関数の引数の数をチェックしない場合の条件式が間違っていたのを修正. - AyaVariable クラスに合わせてシステム関数 ARRAYSIZE を修正. - こまごまとした見た目の修正を少々. - マルチステートメントの処理でデクリメントを出力確定子と間違えていたのを修正. - is_inc_or_dec() を修正してデクリメントが動作するようにした. - ブロックの評価結果が空の場合にも選択肢のリストに加えられていたのを修正. - if で条件文を羅列せずにリストを活用するように変更. - evaluate_statement() での演算子の検索方法を変更. - evaluate_statement() で型変換が正しく行なわれない場合があったのを修正. - プリプロセッサを強化. '#globaldefine' をサポート. - トークチェインのサポートを追加. - reference[n] に値が設定されない場合があったのを修正. - 'OnSecondChange' が正しく処理されていなかったのを修正. - aya.py を単独のスクリプトとしても呼び出せるようにした. - トークチェインの制御は aya_shiori3.dic に任せるようにした. - トークチェインに使用する変数の初期化とトークチェインの終了処理を追加して 動作を本家に合わせた. 2002/06/13 period 7 - システム関数 RAND, ASC とシステム変数 random, ascii を修正. - スクリプト中で random, ascii が使えなかったのを修正. - スクリプト中の簡易配列とヒストリーのインデックスに変数が使用できるように修正. - 暗号化辞書対応. 暗号化辞書の解読は外部モジュールに頼らずに aya.py 内部で行なうことにした. - aitalkinterval が 0 の時には時間経過による OnAiTalk が発生しないように修正. - 型変換のエラーメッセージに変換結果を代入した変数を使用している個所があった のを修正. - TONUMBER2 の引数にイリーガルな文字列が渡された場合のエラー処理を追加. - エラー処理の分離のため辞書の読み込みは aya.txt の解析の後に行なうようにした. - 文字列と簡易配列の2つの型の変数を統合. string = "a" : string -> "a", string[0] -> "a" array = "a,b,c" : array -> "a,b,c", array[0] -> "a" - サイズを越えた要素への代入があると簡易配列が自動的に拡張されるようにした. - 文字列と簡易配列の統合に合わせ aya_variable.cfg のフォーマットを更新(v1.1). v1.0 フォーマットの読み込みも問題無く行なわれる. - システム関数 LOG, LOG10 の引数が 0 の場合には 0 を返すようにした. - nonoverlap / sequential が内側のブロックに間違って適用されていたのを修正. - ゴースト終了時のみ aya_variable.cfg のセーブを行なうようにした. - aya.py の内部で保持する変数を全て AyaVariable クラスにした. AyaStringArray はこのクラスに統合されたので削除. - 文字列終端の '"' の付け忘れの場合を考慮し, 確認してから削るよう変更. 2002/06/09 period 6 - 数値を関数の戻り値にできるようにした. - 関数のオプション nonoverlap / sequential を実装. - マルチステートメントに対応. - システム関数 NAMETOVALUE 関連の if 節の位置を修正. システム関数をオーバーロード可能な状態に保つため.(仕様に規定は無い.) - 保存されている変数の値の読み込みを aya.txt を読む前に実行するように変更. これまでのコードだと aya.txt の設定が変更された場合でも保存されていた値で 上書きしてしまって変更が反映されなくなっていた. ただし, ユーザーによるしゃべり頻度の設定は aitalkinterval を書換えることで 行なわれるので, aitalkinterval のみ保存されている値の方を優先. - aya_variable.cfg が消失した場合に発生する問題の対処で OnGhostChanged の場合 を忘れていたので追加. - 暗号化辞書対応. ただし, 外部モジュールは未実装なので実際には機能せず. - 0 除算の結果を強制的に 0 にするようにした. - 剰余演算子 %, %= を追加. - システム関数 RAND とシステム変数 rand の返す値の範囲を修正. - 整数だけでなく実数も使えるように拡張. - 変数の値を名前空間から拾ってくる部分で存在判定にバグがあったのを修正. # フォーラムで書いたバグ(#1)の修正です. - プリプロセスでの置換処理 #define を実装. - システム関数の呼出しで引数の数をチェックをするようにした. - コメント処理のコードが複数箇所に存在したのをメソッドとして実装. この変更で残っていた全角空白の処理忘れが修正された. - メソッド find_not_quoted を追加. マルチステートメントの処理やコメントの削除等の改良に使用. - 有効な要素が存在しない関数は空文字列を返すよう変更.(仕様に規定が追加された.) - 簡易配列の実装を変更. それに合わせてシステム関数 ARRAYSIZE も修正. - aya_variable.cfg のフォーマットを変更. 古い形式(バージョン番号が無い)はデータ型の決定で問題があるので, 読み込まずに破棄することにした. - 実数の出力フォーマットを調整. - システム関数をスクリプトの中から呼べるようにした. - 引数の無いシステム関数が処理されない問題を修正. - 以下のシステム関数を実装. CALLBYNAME, LOGGING, TOUPPER, TOLOWER, TONUMBER2, TOSTRING2, FLOOR, CEIL, ROUND, SIN, COS, TAN, LOG, LOG10, POW, SQRT, SETSEPARATOR, FOPEN, FCLOSE, FREAD, FWRITE, ISINTEGER, ISREAL 2002/05/28 period 5 (canceled) - 「文」Ver.3 文法に対応 - SHIORI/1.0 APIに対応 2002/05/22 period 4 - 配列以外で使われている '[]' で問題が起きないように修正. 2002/05/21 period 3 - history, 簡易配列の序数が数値以外の場合のエラー処理を追加. - 辞書ファイル名を小文字に変換して読むようにした. - 簡易配列で範囲外の序数を指定した場合に仕様通り空文字列を返すよう にした. - 仕様に従って switch の条件文に文字列が来た場合の処理を switch 0 と同じにした. - typo をいくつか修正. - 変数の保存先を aya_variable.cfg に変更.(ファイル形式は独自) - システム関数 RAND, ASC を実装. - ninix-install の再実行で aya_variable.cfg が消失した場合に発生す る問題に対処. - システム関数 ARRAYSIZE を実装. - 「和音」がエラーで固まるのを防ぐために ghostexcount は常に0を返 すようにした. (COMMUNICATE実装までの暫定措置) 2002/05/20 period 2 - 文 ver.3 一部対応 - 全角スペースを空白と見なしていなかった問題の修正 - 既定以外のキーの処理が抜けていたため追加 2002/05/10 period 1 - first release ninix-aya-5.0.9/COPYING0000644000175000017500000004311013416507430012647 0ustar shyshy GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc. 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Library General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) year name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. , 1 April 1989 Ty Coon, President of Vice This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Library General Public License instead of this License. ninix-aya-5.0.9/README0000644000175000017500000001300013416507430012467 0ustar shyshy-------------------------- ninix-aya(機能拡張版ninix) -------------------------- これは何か ---------- ninix-aya(以前は「文」互換モジュール等 for ninixと呼ばれていました)は、 UNIX系OS対応デスクトップアクセサリninixに拡張機能を追加したパッケージです。 また、オリジナルninixに収録されていない改良、バグフィックスも併せて 収録しています。 現在はUNIX系OSに限らずWindowsでも動作するようになっています。 バージョン4.500.x以降(5.x系)から使用開発言語がRubyに変更になりました。 必要なもの ---------- 本ソフトウェアを動作させるには以下のソフトウェアが必要です。 動作確認済みのバージョンより古いものでは動かない場合があります。 - Ruby (http://www.ruby-lang.org/) 本ソフトウェアの開発言語です。 バージョン 2.5.1, 2.3.3 での動作を確認しています。 * Windows環境ではRuby InstallerおよびDevkitをインストールしてください。 - NArray(http://masa16.github.io/narray/) 大規模な多次元数値配列の計算を、簡単かつ高速に実現するRubyのクラスです。 バージョン 0.6.1.2, 0.6.1.1 での動作を確認しています。 - Ruby/GTK3 (http://ruby-gnome2.osdn.jp/) Gtk+3をRubyから利用するための拡張ライブラリです。 バージョン 3.2.7, 3.1.0 での動作を確認しています。 - GTK+ (http://www.gtk.org/) クロスプラットフォームの GUI ライブラリです。 バージョン 3.22.30, 3.22.11 での動作を確認しています。(3.22以上が必須です。) 下記GStreamerを使用する場合はRuby/GStreamerもインストールが必要です。 - GStreamer (http://gstreamer.freedesktop.org/) このソフトウェアはninix-ayaを動作させるのに必須ではありません。 音声ファイルの再生に使用しています。 バージョン 1.14.1, 1.10.4 での動作を確認しています。 - Rubyzip(https://github.com/rubyzip/rubyzip) Rubyからzipファイルを読み書きするためのモジュールです。 バージョン 1.2.1, 1.2.0 での動作を確認しています。 - Ruby gettext(http://ruby-gettext.github.io/) Rubyのソフトをローカライズするためのパッケージです。 バージョン 3.2.9, 3.2.2 での動作を確認しています。 - CharlockHolmes (http://github.com/brianmario/charlock_holmes) このソフトウェアはninix-ayaを動作させるのに必須ではありません。 Shift_JIS以外の文字コードを使用している「美坂」使用ゴーストを動作させる場合に必要です。 バージョン 0.7.5, 0.7.3 での動作を確認しています。 インストール ------------ Linux(Debianなど)ではディストリビューションで用意されている パッケージの使用を推奨します。 以下ではソースアーカイブからインストールする方法について説明します。 (UNIX系OSを想定しています。) 配布サイトよりソースアーカイブの最新版を入手し、展開します。 Makefileには インストール先ディレクトリ (prefix)、 ローカライズファイルのインストール先ディレクトリ(localedir)、 華和梨8やYAYAといった栞の.soファイルのパス(shiori_so_dir) などを指定する項目があります。 これらを環境に応じて変えてください。 後は、以下のように make install コマンドを実行すればインストール完了です。 # make install ninix-ayaを起動するためのスクリプトが一緒にインストールされますので、 それを実行してください。 $ ninix Windows環境では、アーカイブを展開して lib/ninix_main.rb をRuby インタプリタで実行してください。 ※ gemによる必要なもののインストール手順 (あらかじめDevKitをインストールしておく必要があります。) gem install narray gem install gtk3 gem install gstreamer gem install gettext gem install rubyzip ライセンス ---------- Copyright (C) 2001, 2002 by Tamito KAJIYAMA Copyright (C) 2002-2007 by MATSUMURA Namihiko Copyright (C) 2002-2019 by Shyouzou Sugitani Copyright (C) 2002, 2003 by ABE Hideaki Copyright (C) 2003-2005 by Shun-ichi TAHARA This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License (version 2) as published by the Free Software Foundation. It is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 連絡先 ------ 当ソフトウェアは杉谷とさくらのにえが開発および配布をしております。 ご連絡は 杉谷 さくらのにえ または http://osdn.jp/projects/ninix-aya/ 上のフォーラムへどうぞ。 (過去の開発記録の一部は http://nie.counterghost.net/ にあります。) リンク ------ あれ以外の何か with "任意" のページ(ninix配布サイト) http://www.geocities.co.jp/SiliconValley-Cupertino/7565/ ninix-aya開発プロジェクト http://ninix-aya.osdn.jp/ ninix-aya配布サイト http://osdn.jp/projects/ninix-aya/ 以上 ninix-aya-5.0.9/exe/0000755000175000017500000000000013416507430012376 5ustar shyshyninix-aya-5.0.9/exe/ninix-aya0000644000175000017500000000005113416507430014212 0ustar shyshy#!/usr/bin/env ruby require 'ninix_main' ninix-aya-5.0.9/ninix-aya.gemspec0000644000175000017500000000332113416507430015056 0ustar shyshyrequire_relative 'lib/ninix/version' Gem::Specification.new do |spec| spec.name = 'ninix-aya' spec.version = Version.NUMBER #spec.date = '' spec.summary = "Interactive fake-AI Ukagaka-compatible desktop mascot program" spec.description = <<-EOF "Ukagaka", also known as "Nanika", is a platform on which provides mascot characters for the user's desktop. "Ninix" is an Ukagaka-compatible program, but stop developing for a long time. "Ninix-aya" is derived from "Ninix" and improved a lot. EOF spec.authors = ["Tamito KAJIYAMA", "MATSUMURA Namihiko", "Shyouzou Sugitani", "Shun-ichi TAHARA", "ABE Hideaki", "linjian", "henryhu"] spec.email = 'shy@users.osdn.me' spec.files = Dir["lib/*.rb"] spec.files += Dir['lib/ninix/*.rb'] spec.files += Dir['lib/ninix/dll/*.rb'] spec.files += Dir['locale/*/LC_MESSAGES/*.mo'] spec.files += %w(COPYING ChangeLog README README.en README.ninix SAORI) spec.bindir = "exe" spec.executables << 'ninix-aya' spec.homepage = 'https://ja.osdn.net/projects/ninix-aya/' spec.license = 'GPL-2.0' spec.add_runtime_dependency 'narray', '~> 0.6', '>=0.6.1.1' spec.add_runtime_dependency 'gtk3', '~> 3.1', '>=3.1.0' #spec.add_runtime_dependency 'gstreamer', '~> 3.1', '>=3.1.0' spec.add_runtime_dependency 'gettext', '~> 3.2', '>=3.2.2' spec.add_runtime_dependency 'rubyzip', '~> 1.2', '>=1.2.0' #spec.add_runtime_dependency 'charlock_holmes', '~> 0.7', '>=0.7.3' #spec.executables << 'ninix-aya' spec.required_ruby_version = '~> 2.0' #spec.requirements << 'Ghosts and Balloon' end